在Haskell中生成逻辑表达式的真值表

3

第一部分是一个评估函数,其类型签名如下:

evaluate :: Logic Expr -> [(Variable, Bool)] -> Bool

这个函数接收一个逻辑表达式和一组赋值对作为输入,根据提供的布尔赋值返回表达式的值。赋值列表是一个不同的列表,其中每个对包含一个变量和它的布尔赋值。也就是说,如果你向函数传递表达式 A ∧ B 和赋值 A = 1 和 B = 0,你的函数必须返回 0(这来自数字逻辑设计,0 对应 false,1 对应 true)。
这是我迄今为止所做的:
type Variable =  Char

data LogicExpr = V Variable
                 | Negation  LogicExpr
                 | Conjunction LogicExpr LogicExpr
                 | Disjunction  LogicExpr LogicExpr 
                 | Implication  LogicExpr LogicExpr 


evaluate :: LogicExpr -> [(Variable,Bool)] -> Bool

evaluate (V a) ((x1,x2):xs) | a==x1 = x2
                            | otherwise = (evaluate(V a)xs)

evaluate (Negation a) l | (evaluate a l)==True = False
                        | otherwise = True

evaluate (Conjunction a b) l = (evaluate a l)&&(evaluate b l)

evaluate (Disjunction a b) l = (evaluate a l)||(evaluate b l)

evaluate (Implication a b) l
    | (((evaluate b l)==False)&&((evaluate a l)==True)) = False
    | otherwise = True

下一步是定义generateTruthTable,它是一个函数,接受逻辑表达式作为输入,并以分配对的列表形式返回表达式的真值表。也就是说,如果你将表达式E = A ∧ B传递给该函数,你的函数必须返回A = 0,B = 0,E = 0 | A = 0,B = 1,E = 0 | A = 1,B = 0,E = 0 | A = 1,B = 1,E = 1。我不太熟悉语法,所以不知道如何返回列表。

6
如果你去掉“[紧急]”这个词,我认为会有更多人关注这件事。 - Lucas Jones
当我还在上大学的时候,我的最好朋友是一个袖珍的日程安排器。他从不忘记作业,并按最近的截止日期来安排优先级(据说这样最高效)。 - Jason Catena
3
请忽略那些粗鲁的张贴。这并不代表典型的经历。 - Ethan Heilman
在SO或任何与教育相关的论坛上,为提供作业问题者一些部分答案和提示,让OP自己完成大部分工作是很正常的。回复的粗鲁程度有所不同,但你必须学会在互联网上的任何论坛上都要有一颗坚强的心脏。不要因为人们表达了激烈的不同意见而过于兴奋。 - ephemient
太遗憾了,没有一种方法可以标记答案(而不是帖子)为“与编程无关”或“主观和争议性” :) - Jimmy
1
你正在学习四种语言,而对列表中最酷的语言关注最少。:\ - Rayne
2个回答

14
标准库函数,代码重用。此外,您的括号使用和间距真的很奇怪。
evaluate (V a) l =
    case lookup a l
      of Just x -> x
         Nothing -> error $ "Unbound variable: " ++ show a
-- same as
evaluate (V a) l = maybe (error $ "Unbound variable: " ++ show a) id $ lookup a l

evaluate (Negation a) l = not $ evaluate a l

evaluate (Implication a b) l = evaluate (Negation a `Disjunction` b) l

现在,你想要一个generateTruthTable吗?那很容易,只需取布尔变量的所有可能状态,并将评估的表达式附加到每个状态的末尾即可。
generateTruthTable :: [Variable] -> LogicExpr -> [[(Variable, Bool)]]
generateTruthTable vs e = [l ++ [('E', evaluate e l)] | l <- allPossible vs]

如果你有一个可以生成所有可能状态的函数,那该多好啊。
allPossible :: [Variable] -> [[(Variable, Bool)]]

根据我的直觉,这似乎应该是一个catamorphism。毕竟,它需要查看列表中的所有内容,但返回的是一个不同结构的东西,而且它可能可以简单地分解,因为这是一门入门级的计算机科学课程。(我不在乎课程编号是什么,这是入门级的东西。)

allPossible = foldr step initial where
    step v ls = ???; initial = ???

现在,foldr :: (a -> b -> b) -> b -> [a] -> b,所以前两个参数必须是step :: a -> b -> binitial :: b。现在,allPossible :: [Variable] -> [[(Variable, Bool)]] = foldr step initial :: [a] -> b。嗯,这一定意味着a = Variableb = [[(Variable, Bool)]]。那么对于stepinitial来说意味着什么呢?

    step :: Variable -> [[(Variable, Bool)]] -> [[(Variable, Bool)]]
    initial :: [[(Variable, Bool)]]

有趣。不知何故,需要一种方法从变量状态列表中“步进”,并向其中添加一个单独的变量,以及一些完全没有变量的初始列表。
如果你的思维已经成功地转换到函数式编程范式中,这应该已经足够。如果没有,无论你在这里得到了什么指导,几个小时后作业就要交了,你基本上是没救了。祝好运,如果作业提交后仍然卡住了,你应该问你的教授或者在这里提出非紧急问题。
如果您在使用语言时遇到基本的可用性问题(“什么是语法”,“运行时语义是什么”,“是否有预先存在的xxx功能”等):
  • Haskell 98 Language and Libraries是免费提供的,基础语言和库的权威定义。 Haskell wiki上还提供了更多链接。
  • 对于98年后的语言扩展,请参阅GHC documentation
  • GHC、Hugs和其他现代Haskell实现还提供比Haskell 98规定的标准库更丰富的标准库。hierarchical libraries的完整文档也可在线获得。
  • Hoogλe是一个专门搜索扩展Haskell标准库的搜索引擎。Hayoo!类似,但还涵盖了HackageDB,这是一个远远超出标准发行版的Haskell库集合。

我希望您的课程提供了类似的资源,但如果没有,以上所有内容都可以通过谷歌搜索轻松发现。

如果有适当的参考资料,任何值得自己的salt程序员应该能够在几个小时内掌握任何新语言的语法,并在几天内对运行时有一个工作理解。当然,掌握新范式可能需要很长时间,而且对学生施加相同的标准有点不公平,但这就是班级的目的。

在Stack Overflow上提出关于更高级问题的问题可能会得到较少的答案,但也会受到远少于其他问题的恶意:) 在大多数人眼中,作业问题被归类为“替我做我的工作!”。


剧透

请勿作弊。但是,为了让您体验Haskell可以实现多么棒的功能...

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
{-# LANGUAGE OverlappingInstances, PatternGuards #-}

module Expr (Ring(..), (=:>), Expr(..), vars, eval, evalAll) where

import Control.Monad.Error

infixl 5 =:>, :=>
infixl 6 +:, -:, :+, :-
infixl 7 *:, :*

class (Eq a) => Ring a where
    (+:) :: a -> a -> a; (-:) :: a -> a -> a; x -: y = x +: invert y
    (*:) :: a -> a -> a; invert :: a -> a; invert x = zero -: x
    zero :: a; one :: a
(=:>) :: (Ring a) => a -> a -> a
(=:>) = flip (-:)

instance (Num a) => Ring a where
    (+:) = (+); (-:) = (-); (*:) = (*)
    invert = negate; zero = 0; one = 1

instance Ring Bool where
    (+:) = (||); (*:) = (&&)
    invert = not; zero = False; one = True

data Expr a b
  = Expr a b :+ Expr a b | Expr a b :- Expr a b
  | Expr a b :* Expr a b | Expr a b :=> Expr a b
  | Invert (Expr a b) | Var a | Const b

paren :: ShowS -> ShowS
paren ss s = '(' : ss (')' : s)

instance (Show a, Show b) => Show (Expr a b) where
    showsPrec _ (Const c) = ('@':) . showsPrec 9 c
    showsPrec _ (Var v) = ('$':) . showsPrec 9 v
    showsPrec _ (Invert e) = ('!':) . showsPrec 9 e

    showsPrec n e@(a:=>b)
      | n > 5 = paren $ showsPrec 0 e
      | otherwise = showsPrec 7 a . ('=':) . ('>':) . showsPrec 5 b

    showsPrec n e@(a:*b)
      | n > 7 = paren $ showsPrec 0 e
      | otherwise = showsPrec 7 a . ('*':) . showsPrec 7 b

    showsPrec n e | n > 6 = paren $ showsPrec 0 e
    showsPrec _ (a:+b) = showsPrec 6 a . ('+':) . showsPrec 6 b
    showsPrec _ (a:-b) = showsPrec 6 a . ('-':) . showsPrec 6 b

vars :: (Eq a) => Expr a b -> [a]
vars (a:+b) = vars a ++ vars b
vars (a:-b) = vars a ++ vars b
vars (a:*b) = vars a ++ vars b
vars (a:=>b) = vars a ++ vars b
vars (Invert e) = vars e; vars (Var v) = [v]; vars _ = []

eval :: (Eq a, Show a, Ring b, Monad m) => [(a, b)] -> Expr a b -> m b
eval m (a:+b) = return (+:) `ap` eval m a `ap` eval m b
eval m (a:-b) = return (-:) `ap` eval m a `ap` eval m b
eval m (a:*b) = return (*:) `ap` eval m a `ap` eval m b
eval m (a:=>b) = return (=:>) `ap` eval m a `ap` eval m b
eval m (Invert e) = return invert `ap` eval m e
eval m (Var v)
  | Just c <- lookup v m = return c
  | otherwise = fail $ "Unbound variable: " ++ show v
eval _ (Const c) = return c

namedProduct :: [(a, [b])] -> [[(a, b)]]
namedProduct = foldr (\(v, cs) l -> concatMap (\c -> map ((v, c):) l) cs) [[]]

evalAll :: (Eq a, Show a, Ring b) => [b] -> a -> Expr a b -> [[(a, b)]]
evalAll range name e =
    [ vs ++ [(name, either error id $ eval vs e)]
    | vs <- namedProduct $ zip (vars e) (repeat range)
    ]

$ ghci
GHCi,版本6.10.2:http://www.haskell.org/ghc/  :? 获取帮助
加载包ghc-prim...链接...完成。
加载包整数...链接...完成。
加载包基础...链接...完成。
Prelude> :l Expr.hs
[1 of 1] Compiling Expr             ( Expr.hs, interpreted )
好的,模块已加载:Expr。
*Expr> mapM_ print . evalAll [1..3] 'C' $ Var 'A' :* Var 'B'
加载包mtl-1.1.0.2...链接...完成。
[('A',1),('B',1),('C',1)]
[('A',1),('B',2),('C',2)]
[('A',1),('B',3),('C',3)]
[('A',2),('B',1),('C',2)]
[('A',2),('B',2),('C',4)]
[('A',2),('B',3),('C',6)]
[('A',3),('B',1),('C',3)]
[('A',3),('B',2),('C',6)]
[('A',3),('B',3),('C',9)]
*Expr> 让expr = Var 'A' :=> (Var 'B' :+ Var 'C') :* Var 'D'
*Expr> expr
$'A'=>($'B'+$'C')*$'D'
*Expr> mapM_ print $ evalAll [True, False] 'E' expr
[('A',真实的),('B',真实的),('C',真实的),('D',真实的),('E',真实的)]
[('A',真实的),('B',真实的),('C',真实的),('D',假的),('E',假的)]
[('A',真实的),('B',真实的),('C',假的),('D',真实的),('E',真实的)]
[('A',真实的),('B',真实的),('C',假的),('D',假的),('E',假的)]
[('A',真实的),('B',假的),('C',真实的),('D',真实的),('E',真实的)]
[('A',真实的),('B',假的),('C',真实的),('D',假的),('E',假的)]
[('A',真实的),('B',假的),('C',假的),('D',真实的),('E',假的)]
[('A',真实的),('B',假的),('C',假的),('D',假的),('E',假的)]
[('A',假的),('B',真实的),('C',真实的),('D',真实的),('E',真实的)]
[('A',假的),('B',真实的),('C',真实的),('D',假的),('E',真实的)]
[('A',假的),('B',真实的),('C',假的),('D',真实的),('E',真实的)]
[('A',假的),('B',真实的),('C',假的),('D',假的),('E',真实的)]
[('A',假的),('B',假的),('C'

2
基本的evaluate非常直观:
import Data.Maybe (fromJust)
import Data.List (nub)

type Variable = Char
data LogicExpr
   = Var Variable
   | Neg LogicExpr
   | Conj LogicExpr LogicExpr
   | Disj LogicExpr LogicExpr
   | Impl LogicExpr LogicExpr
   deriving (Eq, Ord)

-- evaluates an expression
evaluate :: LogicExpr -> [(Variable, Bool)] -> Bool
evaluate (Var v) bs      = fromJust (lookup v bs)
evaluate (Neg e) bs      = not (evaluate e bs)
evaluate (Conj e1 e2) bs = evaluate e1 bs && evaluate e2 bs
evaluate (Disj e1 e2) bs = evaluate e1 bs || evaluate e2 bs
evaluate (Impl e1 e2) bs = not (evaluate e1 bs) || evaluate e2 bs

为了生成一个真值表,首先必须找到表达式中的所有变量,并为这些变量生成所有可能的赋值。这些赋值的真值可以通过已经实现的evaluate函数轻松确定:
-- get variables in an expression
varsp :: LogicExpr -> [Variable]
varsp (Var v)      = [v]
varsp (Neg e)      = varsp e
varsp (Conj e1 e2) = varsp e1 ++ varsp e2
varsp (Disj e1 e2) = varsp e1 ++ varsp e2
varsp (Impl e1 e2) = varsp e1 ++ varsp e2

-- get variables in an expression without duplicates
vars :: LogicExpr -> [Variable]
vars = nub . varsp

-- possible boolean values
bools = [True, False]

-- all possible combinations of variable assignments
booltable :: [Variable] -> [[(Variable, Bool)]]
booltable [] = [[]]
booltable (a:as) = [(a,b) : r | b <- bools, r <- booltable as]

-- variable assignments and corresponding evaluation of an expression
truthtable :: LogicExpr -> [([(Variable, Bool)], Bool)]
truthtable e = [(bs, evaluate e bs) | bs <- booltable (vars e)]

如果你想探索标准库的深处,你也可以编写一个Read实例,以便轻松输入LogicExpr
-- read a right-associative infix operator
readInfix opprec constr repr prec r
   = readParen (prec > opprec)
     (\r -> [(constr e1 e2, u) |
             (e1,s) <- readsPrec (opprec+1) r,
             (op,t) <- lex s,
             op == repr,
             (e2,u) <- readsPrec (opprec) t]) r

instance Read LogicExpr where
   readsPrec prec r
      =  readInfix 1 Impl "->" prec r
      ++ readInfix 2 Disj "|" prec r
      ++ readInfix 3 Conj "&" prec r
      ++ readParen (prec > 4)
         (\r -> [(Neg e, t) |
                 ("!",s) <- lex r,
                 (e,t)   <- readsPrec 4 s]) r
      ++ readParen (prec > 5)
         (\r -> [(Var v, s) |
                 ([v], s) <- lex r]) r

真值表可以漂亮地打印出来:

showcell :: (Variable, Bool) -> String
showcell (v,b) = v : "=" ++ show b

showrow :: [(Variable, Bool)] -> Bool -> String
showrow []     b = show b
showrow [a]    b = showcell a ++ " => " ++ show b
showrow (a:as) b = showcell a ++ " && " ++ showrow as b

printrow :: ([(Variable, Bool)], Bool) -> IO ()
printrow = putStrLn . uncurry showrow

printtbl :: [([(Variable, Bool)], Bool)] -> IO ()
printtbl = mapM_ printrow

所有的真值表都可以按照以下方式生成:
Prelude Main> printtbl $ truthtable $ read "(a -> b) & (b -> a)"
a=True && b=True => True
a=True && b=False => False
a=False && b=True => False
a=False && b=False => True

Prelude Main> printtbl $ truthtable $ read "(a | b) | (!a & !b)"
a=True && b=True => True
a=True && b=False => True
a=False && b=True => True
a=False && b=False => True

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接