Haskell计算器 - 操作顺序

时间:2014-01-19 01:13:25

标签: haskell stack calculator

我是Haskell的新手,我需要制作一个有效的计算器,它会给出如下表达式的答案:2 + 3 *(5 + 12) 我有一些东西可以设法或多或少地计算,但我的操作顺序有问题。我不知道该怎么做。这是我的代码:

import Text.Regex.Posix
import Data.Maybe

oblicz :: String -> Double
oblicz str = eval (Nothing, None) $ map convertToExpression $ ( tokenize str )


eval :: (Maybe Double,Expression)->[Expression]->Double

eval (Nothing, _) ((Variable v):reszta) = eval (Just v, None) reszta
eval (Just aktualnyWynik, None) ((Operator o):reszta) = eval ((Just aktualnyWynik), (Operator o)) reszta



eval (Just aktualnyWynik, (Operator o)) ((Variable v):reszta) = eval (Just $ o aktualnyWynik v , None) reszta


eval (aktualnyWynik, operator) (LeftParenthesis:reszta) 
    = eval (aktualnyWynik, operator) ((Variable (eval (Nothing, None) reszta)):(getPartAfterParentheses reszta))


eval (Just aktualnyWynik, _) [] = aktualnyWynik
eval (Just aktualnyWynik, _) (RightParenthesis:_) = aktualnyWynik

data Expression =     Operator (Double->Double->Double)
                    | Variable Double
                    | LeftParenthesis
                    | RightParenthesis
                    | None

tokenize :: String -> [String]
tokenize expression = getAllTextMatches(expression =~ "([0-9]+|\\(|\\)|\\+|-|%|/|\\*)" :: AllTextMatches [] String)

convertToExpression :: String -> Expression                 
convertToExpression "-" = Operator (-)
convertToExpression "+" = Operator (+)
convertToExpression "*" = Operator (*)
convertToExpression "/" = Operator (/)
convertToExpression "(" = LeftParenthesis
convertToExpression ")" = RightParenthesis
convertToExpression variable = Variable (read variable)

getPartAfterParentheses :: [Expression] -> [Expression]
getPartAfterParentheses [] = []
getPartAfterParentheses (RightParenthesis:expressionsList) = expressionsList
getPartAfterParentheses (LeftParenthesis:expressionsList) = getPartAfterParentheses (getPartAfterParentheses expressionsList)
getPartAfterParentheses (expression:expressionsList) = getPartAfterParentheses expressionsList

我想也许我可以创建两个堆栈 - 一个用数字,一个用运算符。在阅读表达式时,我可以在一个堆栈上推送数字,在另一个堆栈上推送运算符当它是一个操作符时,我会检查堆栈上是否有东西,是否检查是否应该从堆栈中弹出它并进行数学计算 - 就像在onp表示法中一样。

不幸的是,正如我所说,我对haskell非常陌生,并且不知道怎么写这个。

任何提示或帮助都会很好:)

2 个答案:

答案 0 :(得分:1)

在不同的堆栈上推送东西确实感觉非常难以做到,而且这在Haskell中通常并不好。 (堆栈可以实现为列表,它以纯粹的功能方式工作。即使真正的可变状态也可以很好,如果只是作为优化,但如果需要一次修改多个对象,那么这不完全是愉快。)

最好的方法是建立一个表示表达式的

type DInfix = Double -> Double -> Double  -- for readability's sake

data ExprTree = Op DInfix ExprTree ExprTree
              | Value Double

评估此树基本上是evalTree (Op c t1 t2) = c (evalTree t1) (evalTree t2),即立即ExprTree->Double

要构建树,关键点:正确获取运算符修复。不同的运营商有不同的固定性。我将这些信息放在Operator字段中:

type Fixity = Int
data Expression = Operator (Double->Double->Double) Fixity
                | ...

然后需要例如。

...
convertToExpression "+" = Operator (+) 6
convertToExpression "*" = Operator (*) 7
...

(这些是Haskell本身对运营商的固定性。您可以在GHCi中:i +查看它们。)

然后你要建造树。

toExprTree :: [Expression] -> ExprTree

明显的基本情况:

toExprTree [Variable v] = Value v

您可以继续

toExprTree (Variable v : Operator c _ : exprs) = Op c (Value v) (toExprTree exprs)

但实际上这不对:例如4 * 3 + 2它会给4 * (3 + 2)。我们实际上需要将4 *置于剩余的表达式树中,尽管固定度较低。所以树也需要了解它

data ExprTree = Op DInfix Fixity ExprTree ExprTree
              | Value Double

mergeOpL :: Double -> DInfix -> Fixity -> ExprTree -> ExprTree
mergeOpL v c f t@(Op c' f' t' t'')
   | c > c'  = Op c' f' (mergeOpL v c f t') t''
mergeOpL v c f t = Op c f (Value v) t

还有待处理的是处理括号。你需要采用一个完整的匹配括号表达式并为其指定一个树形固定,比如tight = 100 :: Fixity


作为一个注释:这样的标记化 - 手动解析工作流程非常麻烦,无论你做多少功能。 Haskell拥有强大的解析器组合库,如parsec,它可以完成大部分工作和记账。

答案 1 :(得分:1)

你需要解决这个问题的是Edsger Dijstra的分流码算法,如http://www.wcipeg.com/wiki/Shunting_yard_algorithm所述。您可以看到我的实施at the bottom of this file

如果你将你的自我限制在+, - ,*,/你也可以使用通常的技巧来解决问题,在编译器例子的大多数介绍中简单地解析成两个不同的非终端,称为termproduct来构建正确的树。如果你必须处理很多操作员或者他们是用户定义的,这就变得难以处理。