haskell - 打印表达式时最小化括号

标签 haskell recursion expression arithmetic-expressions

我有一个简单的算术表达式数据结构,我希望能够打印它。为了简单起见,我在这里做了一个例子,其中包含 3 个二进制运算,加法、乘法和除法。定义如下所示:

module ExprPrint where

import Text.Printf

data Expr = Lit Int
          | Add Expr Expr
          | Mul Expr Expr
          | Div Expr Expr

instance Show Expr where
  show (Lit x) = show x
  show (Add e1 e2) = printf "(%s) + (%s)" (show e1) (show e2)
  show (Mul e1 e2) = printf "(%s) * (%s)" (show e1) (show e2)
  show (Div e1 e2) = printf "(%s) / (%s)" (show e1) (show e2)

我的目标是打印数据结构,同时删除所有多余的括号。当然,我上面实现的天真显示功能包括太多了。所以我想做的是制作 Show实例考虑操作的优先级(DivMul 超过 Add)和关联性(AddMul 是关联的,而 Div 是左关联的)。

这里有些例子:
one = Lit 1

-- Shows "((1) + (1)) + (1)" but should be 1 + 1 + 1
addAssoc = show $ Add (Add one one) one
-- Shows "((1) * (1)) * (1)" but should be 1 * 1 * 1
mulAssoc = show $ Mul (Mul one one) one
-- Shows "((1) / (1)) / (1)" but should be 1 / 1 / 1
divAssoc = show $ Div (Div one one) one
-- Shows "(1) / ((1) / (1)) but should be 1 / (1 / 1)
divAssoc2 = show $ Div one (Div one one)

-- Show "((1) * (1)) + (1)" but should 1 * 1 + 1
addPrec = show $ Add (Mul one one) one
-- Show "(1) + ((1) * (1))" but should show 1 + (1 * 1)
addPrec2 = show $ Add one (Mul one one)

在节目实例中是否有“容易”考虑到这一点?我认为我可以通过考虑所有情况来做到这一点,但这将是功能的爆炸式增长。有没有一些算法或已知的方法来处理这个?

希望有人指点一下!

谢谢。

最佳答案

show 中的一个实例不足以避免多余的括号,因为它没有任何关于可用优先级的信息。您需要根据 showsPrec 编写您的实例相反,它确实像这样:

module ExprPrint where

import Text.Show

data Expr = Lit Int
          | Add Expr Expr
          | Mul Expr Expr
          | Div Expr Expr

instance Show Expr where
  showsPrec prec (Lit x) = showsPrec prec x
  showsPrec prec (Add e1 e2) = showParen (prec >= 7) $ showsPrec 7 e1 . showString " + " . showsPrec 7 e2
  showsPrec prec (Mul e1 e2) = showParen (prec >= 8) $ showsPrec 8 e1 . showString " * " . showsPrec 8 e2
  showsPrec prec (Div e1 e2) = showParen (prec >= 8) $ showsPrec 8 e1 . showString " / " . showsPrec 8 e2

我为您的优先级选择了 6 和 7,因为这是 Haskell 自己使用的 + , * , 和 div运营商,但应该很明显您将如何选择不同的运营商。

至于关联性,通常没有完美的方法可以做到这一点,但是您可以在您的情况下通过一些优先级调整来伪造它,因为数学没有任何具有不同关联性的相同优先级的运算符。这是一个如何做到这一点的例子(我添加了 Exp ,优先级为 8,以给出一个右关联方式的例子):
module ExprPrint where

import Text.Show

data Expr = Lit Int
          | Add Expr Expr
          | Mul Expr Expr
          | Div Expr Expr
          | Exp Expr Expr

instance Show Expr where
  showsPrec prec (Lit x) = showsPrec prec x
  showsPrec prec (Add e1 e2) = showParen (prec >= 7) $ showsPrec 6 e1 . showString " + " . showsPrec 7 e2
  showsPrec prec (Mul e1 e2) = showParen (prec >= 8) $ showsPrec 7 e1 . showString " * " . showsPrec 8 e2
  showsPrec prec (Div e1 e2) = showParen (prec >= 8) $ showsPrec 7 e1 . showString " / " . showsPrec 8 e2
  showsPrec prec (Exp e1 e2) = showParen (prec >= 9) $ showsPrec 9 e1 . showString " ^ " . showsPrec 8 e2

这仍然不完美,因为它仍然不知道 Add 的关联属性。或 Mul , 所以 Mul one (Mul one one)将显示为 1 * (1 * 1)而不是 1 * 1 * 1 ,但我认为没有任何可能的方法来解决这个问题,因为除法不共享该属性,但由于它与乘法具有相同的优先级,因此您无法在 showsPrec 中区分它们.

实际上,您可以通过向下窥视并重新关联来欺骗更多:
module ExprPrint where

import Text.Show

data Expr = Lit Int
          | Add Expr Expr
          | Mul Expr Expr
          | Div Expr Expr
          | Exp Expr Expr

instance Show Expr where
  showsPrec prec (Lit x) = showsPrec prec x
  showsPrec prec (Add e1 (Add e2 e3)) = showsPrec prec (Add (Add e1 e2) e3)
  showsPrec prec (Add e1 e2) = showParen (prec >= 7) $ showsPrec 6 e1 . showString " + " . showsPrec 7 e2
  showsPrec prec (Mul e1 (Mul e2 e3)) = showsPrec prec (Mul (Mul e1 e2) e3)
  showsPrec prec (Mul e1 e2) = showParen (prec >= 8) $ showsPrec 7 e1 . showString " * " . showsPrec 8 e2
  showsPrec prec (Div e1 e2) = showParen (prec >= 8) $ showsPrec 7 e1 . showString " / " . showsPrec 8 e2
  showsPrec prec (Exp e1 e2) = showParen (prec >= 9) $ showsPrec 9 e1 . showString " ^ " . showsPrec 8 e2

我认为这是完美的。您的所有测试用例现在都通过了。

关于haskell - 打印表达式时最小化括号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61159436/

相关文章:

linux - Ubuntu 上 Haskell (GHC) 中的 ThreadDelay 问题

haskell - Haskell 编程中的数据类型

haskell - "bracket (mallocBytes n) free"和 "allocaBytes"有什么区别?

C 树 XML 序列化

java - 链表串联

language-agnostic - 表达与陈述

c# - 如何在我的自定义集合中模仿 IQueryable<T>.Include(Expression<Func<T,TProperty>> path) 的功能?

python - 如何在Python中删除正则表达式(re)的重复结果

haskell - 使输入变量可从整个代码访问

c - 递归到迭代 - Scheme to C