【问题标题】:Haskell calculator - order of operationsHaskell 计算器 - 运算顺序
【发布时间】:2014-01-19 01:13:24
【问题描述】:

我对 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 还很陌生,不知道如何写这篇文章。

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

【问题讨论】:

  • "这个问题似乎离题了,因为它缺乏足够的信息来诊断问题。更详细地描述您的问题或在问题本身中包含一个最小示例。 "
  • 你能提供一些示例输入和输出吗?输出与您的预期有何不同?
  • LeftParenthesis 不是Expression,它只是一个解析标记。一旦您意识到这一点,您将看到表达式需要以不同的方式捕获(例如,下面的答案中的树),其中操作的优先级将以相当简单的方式出现。

标签: haskell stack calculator


【解决方案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,它可以让您省去大部分工作和记账。

【讨论】:

    【解决方案2】:

    您需要解决此问题的是 http://www.wcipeg.com/wiki/Shunting_yard_algorithm 中所述的 Edsger Dijstra 的调车场算法。你可以看到我的实现at the bottom of this file

    如果您将自己限制为 +、-、* 或987654324@ 构建正确的树。如果您必须处理大量运算符或者它们是用户定义的,这将变得笨拙。

    【讨论】:

      猜你喜欢
      • 2023-01-11
      • 2014-01-30
      • 1970-01-01
      • 2020-02-12
      • 1970-01-01
      • 1970-01-01
      • 2013-02-09
      • 1970-01-01
      • 2010-12-02
      相关资源
      最近更新 更多