Parsing 使用Haskell查找字符串中最前面的数字。函数分析器
这个问题源于我想创建一个函数解析器,这个解析器将接受任何带有Parsing 使用Haskell查找字符串中最前面的数字。函数分析器,parsing,haskell,math,functional-programming,Parsing,Haskell,Math,Functional Programming,这个问题源于我想创建一个函数解析器,这个解析器将接受任何带有n变量的数学函数,并将其解析为可以计算的树结构 他回答说。我之所以想这样做,是因为我正试图找出一种更好的方法来做一些数值分析类型的东西,而不用Matlab 我不确定是否有其他人有这个问题,但目前我有一个工作树类,但我有一个问题解析成树形式的函数。我的第一个问题在于我无法找出如何遍历字符串并找到函数中的最大数。理想情况下,我会调用一个函数createFunction,它的签名是createFunction::String->functio
n
变量的数学函数,并将其解析为可以计算的树结构
他回答说。我之所以想这样做,是因为我正试图找出一种更好的方法来做一些数值分析类型的东西,而不用Matlab
我不确定是否有其他人有这个问题,但目前我有一个工作树类,但我有一个问题解析成树形式的函数。我的第一个问题在于我无法找出如何遍历字符串并找到函数中的最大数。理想情况下,我会调用一个函数createFunction
,它的签名是createFunction::String->function
,例如f(x)=34*x+92*x-3*x
,甚至是34*x+92*x-3*x
,但我不知道如何最好地解析它,因为使用read只能得到34的3(我想。)我想我该怎么做,我做了一个缓冲区,直到我没有得到一个数字,然后读取缓冲区中的数字,或者我猜是一个列表,但这看起来很糟糕,而且与函数式编程的精神不符。我只是在概念化问题并从功能上解决它时遇到了一个问题
如果Haskell中有一种方法可以用x值替换x并这样做,那将是一个有趣的发现。我检查了一些东西,但对我来说似乎没有什么意义,也不了解如何有效地使用它。我只需要有人给我指出一个好的方向。我想正则表达式就足够了,但我不确定。这是我的答案y代码适用于任何需要了解我正在做什么的人
我明确的期望是将类似于34*x-4
的东西转化为一棵树,它的根节点是一个减法符号,左边的子节点是一个乘法符号,有两个34和x的叶子,而根减法符号的右边子节点是4e、 使用一棵树可能有点过头了。这棵树根本不需要解决这个问题,但我认为这是最好的方法
operator.hs:
module Operator (Operator (Add, Sub, Mult, Div)) where
data Operator = Add | Sub | Mult | Div
deriving (Eq,Show)
tree.hs:
module Tree (Tree (Leaf, Branch), fromFloat) where
import Operator (Operator(Add,Sub,Mult,Div))
data Tree = Leaf Float | Branch Operator Tree Tree
deriving (Eq,Show)
fromFloat :: Float -> Tree
fromFloat = Leaf
function.hs:
module Function(fromFloat) where
import Tree
data Function = Function String Int Tree
deriving (Eq,Show)
用函数式语言从头开始编写解析器的一种相当标准的方法是编写函数,将“剩下的输入”作为参数,尝试解析该输入的初始部分,并返回解析结果加上“剩余的输入”。因为您通常希望允许解析器“优雅地失败”(见下文),您通常将此返回值包装在
中,可能是或其他内容中
例如,尝试解析字符串开头的整数的解析函数如下所示:
import Data.Char
import Data.List
number :: String -> Maybe (Int, String)
number str = case span isDigit str of
([], _) -> Nothing -- no digits found
(a, rest) -> Just (read a, rest)
这就是它的工作原理:
> number "34*x-4"
Just (34,"*x-4")
> number "x+2"
Nothing
使用这种方法编写简单多项式表达式的解析器并不太困难。下面是一个单字符变量名的解析器:
-- read a single-character variable name
var :: String -> Maybe (Char, String)
var (x:rest) | isAlpha x = Just (x, rest)
| otherwise = Nothing
由此,您可以为形式为“2*x”、“y”或“18”的“术语”构造解析器
请注意,我们如何使用初始number str
解析器(即最顶端的案例的Nothing
分支)的潜在故障来尝试解析没有系数的变量(例如,“y”
)的替代方案。这就是为什么允许解析器“优雅地失败”是有帮助的
现在,我们可以为术语的和和和差编写一个解析器。这里笨拙地使用helper函数go
,确保“2*x-4*y+3”
被解析为(2*x-4*y)+3
,而不是2*x-(4*y+3)
如果使用更明显的递归方法来定义poly
,而不使用go
辅助程序,则可能发生这种情况:
-- read a polynomial as a sum of terms
data Poly = Single Term | Plus Poly Term | Minus Poly Term
deriving (Show)
poly :: String -> Maybe (Poly, String)
poly str = case term str of
Just (t, rest) -> go (Single t) rest
Nothing -> error "expected term"
where go :: Poly -> String -> Maybe (Poly, String)
go p ('+':str1) = case term str1 of
Just (t', str2) -> go (Plus p t') str2
Nothing -> error "expected term"
go p ('-':str1) = case term str1 of
Just (t', str3) -> go (Minus p t') str3
Nothing -> error "expected term"
go p str4 = Just (p, str4)
使用上述代码,您可以获得:
> poly "34*x-4"
Just (Minus (Single (TermPoly 34 'x')) (TermConstant 4),"")
> poly "34*x+92*x-3*x"
Just (Minus (Plus (Single (TermPoly 34 'x')) (TermPoly 92 'x')) (TermPoly 3 'x'),"")
> poly "x+y"
Just (Plus (Single (TermPoly 1 'x')) (TermPoly 1 'y'),"")
但是,它不处理前导减号:
> poly "-2*x+y"
*** Exception: expected term
CallStack (from HasCallStack):
error, called at PolyParser.hs:27:20 in main:Main
它也不处理x*5
或2*5*x
或括号、空格或许多其他情况
此外,这种方法的最大缺点是将简单的组件解析器组合成更大的解析器是多么复杂。例如,术语
在概念上非常简单:解析一个数字
乘以一个变量
,或者解析一个单独的数字
或单独的变量
,但我们都有这些复杂的问题case
语句组合number
和var
并解析'*'
字符。这就是“解析器组合器”或“一元解析器”变得重要的地方,因为它们为组合解析器提供了一种简单明了的语法
事实上,这也是为什么没有经验丰富的Haskell程序员从头开始编写解析器的原因。有许多优秀且功能强大的单子解析库可用。它们需要一些时间来学习使用,但非常值得。一个功能相当全的表达式语言Megaparsec解析器大约需要30分钟才能在60多分钟内编写完成行并可以处理复杂表达式(如下面的main
函数中的示例):
导入数据。无效
导入文本.Megaparsec
导入Text.Megaparsec.Char
将限定的Text.Megaparsec.Char.Lexer作为L导入
导入控制.Monad.combinants.Expr
类型Parser=Parsec空字符串
--空间处理标准样板
空格::解析器()
空格=L.空格空格1为空
词素::解析器a->解析器a
词素=L.词素空间
符号::字符串->解析器字符串
symbol=L.symbol空间
--解析数字
数字::分析器浮点
number=lexeme$try L.float from整数L.decimal
--解析标识符(例如,“x_4”)
标识符::分析器字符串
identifier=lexeme$(:)(letterChar'')many(alphaNumChar'')
--表达式的抽象语法树
数据表达式
=Num Float
|变量字符串
|否定表达式
|BinOp BinOp Expr Expr
派生(显示)
数据二进制
=广告
> poly "-2*x+y"
*** Exception: expected term
CallStack (from HasCallStack):
error, called at PolyParser.hs:27:20 in main:Main
import Data.Void
import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L
import Control.Monad.Combinators.Expr
type Parser = Parsec Void String
-- standard boilerplate for space handling
spaces :: Parser ()
spaces = L.space space1 empty empty
lexeme :: Parser a -> Parser a
lexeme = L.lexeme spaces
symbol :: String -> Parser String
symbol = L.symbol spaces
-- parse a number
number :: Parser Float
number = lexeme $ try L.float <|> fromIntegral <$> L.decimal
-- parse an identifier (e.g., "x_4")
identifier :: Parser String
identifier = lexeme $ (:) <$> (letterChar <|> char '_') <*> many (alphaNumChar <|> char '_')
-- abstract syntax tree for expressions
data Expr
= Num Float
| Var String
| Negate Expr
| BinOp BinOp Expr Expr
deriving (Show)
data BinOp
= Add | Sub | Mult | Div | Power
deriving (Show)
-- parse a "term": number, identifier, or expression in parentheses
term :: Parser Expr
term = Num <$> number
<|> Var <$> identifier
<|> between (symbol "(") (symbol ")") expr
-- parse an expression combining terms with operators
expr :: Parser Expr
expr = makeExprParser term
[ [binaryR "^" Power]
, [Prefix (Negate <$ symbol "-")]
, [binary "*" Mult, binary "/" Div]
, [binary "+" Add, binary "-" Sub]
]
where binary name f = InfixL (BinOp f <$ symbol name)
binaryR name f = InfixR (BinOp f <$ symbol name)
-- parse a whole string as an expression
fullExpr :: Parser Expr
fullExpr = spaces *> expr <* eof
main = parseTest fullExpr "(pi*r^2 - 4*pi*r) / (c^2 - a^2 - b^2)"