计算表达式中的出现次数

时间:2013-02-26 22:25:46

标签: haskell functional-programming

我对Haskell很新,我有一个评估,涉及布尔表达式的操纵器和评估器。

Thee表达式是:

type Variable = String
data Expr = T | Var Variable | And Expr Expr | Not Expr 

我已经解决了很多问题,但我仍然坚持如何处理以下功能。我需要计算表达式

中所有变量的出现次数
addCounter :: Expr -> Expr
addCounter = undefined

prop_addCounter1 = addCounter (And (Var "y") (And (Var "x") (Var "y"))) == 
                   And (Var "y1") (And (Var "x2") (Var "y1"))
prop_addCounter2 = addCounter (Not (And (Var "y") T)) == 
                   Not (And (Var "y1") T)

我不是在寻找一个关于如何做到这一点的答案,因为这是一个评估问题,但我想要一些关于如何接近这个的提示?

在我的脑海中,我想到递增一个计数器,这样我就可以获得y1x2部分,但这并不是Haskell中可能实现的(或者不建议做任何事情!)我会通过递归来解决这个问题,如果是这样,我怎么知道要添加到变量的数字?

2 个答案:

答案 0 :(得分:2)

正如你所说,在这种情况下你不能保留一个非常自然的共享计数器。您可以做的是在递归访问所有Expr时将当前计数器值传递到树中,并从被调用的函数接收递增的计数器值。 必须是双向沟通。您传递当前值并接收更新的Expr和新计数器值。

如果希望每个唯一变量名具有相同的计数器值,则需要将变量名称映射到指定的计数器值。您需要像当前计数器值一样传递那个。

希望有所帮助。

答案 1 :(得分:1)

Atomize您的状态更新

所以,这绝对是使用State monad的好时机。特别是,您正在寻找的原子变换是一种通过每个字符串的唯一ID枚举字符串String -> String的方法。我们称之为enumerate

import Control.Monad.State

-- | This is the only function which is going to touch our 'Variable's
enumerate :: Variable -> State OurState Variable

为此,我们需要跟踪将String映射到计数(Int s)的状态

import qualified Data.Map as M
type OurState = Map String Int

runOurState :: State OurState a -> a
runOurState = flip evalState M.empty

runOurState $ mapM enumerate ["x", "y", "z", "x" ,"x", "x", "y"]
-- ["x1", "y1", "z1", "x2", "x3", "x4", "y2"]

所以我们可以直接实现枚举作为有状态的动作。

enumerate :: Variable -> State OurState Variable
enumerate var = do m <- get
                 let n = 1 + M.findWithDefault 0 var m
                 put $ M.insert var n m
                 return $ var ++ show n

酷!


一般在表达式树上折叠

现在我们真的应该编写一个精心设计的折叠设备,通过在每个Expr -> State OurState Expr类型的叶子上应用枚举来映射Var

enumerateExpr :: Expr -> State OurState Expr
enumerateExpr T = return T
enumerateExpr (Var s) = fmap Var (enumerate s)
enumerateExpr (And e1 e2) = do em1 <- addCounter e1
                            em2 <- addCounter e2
                            return (Add em1 em2)
enumerateExpr (Not expr) = fmap Not (addCounter expr)

但这非常繁琐,所以我们会使用Uniplate库保持干燥。

{-# LANGUAGE DeriveDataTypeable #-}
import Data.Data
import Data.Generics.Uniplate.Data

data Expr = T | Var Variable | And Expr Expr | Not Expr 
  deriving (Show,Eq,Ord,Data)

onVarStringM :: (Variable -> State OurState Variable) -> Expr -> State OurState Expr
onVarStringM action = transformM go
  where go :: Expr -> State OurState Expr
        go (Var s) = fmap Var (action s)
        go x       = return x

transformM运算符正是我们想要的 - 在通用树的所有部分(我们的Expr)上应用monadic变换。

现在,我们只需解压State个动作以制作addCounter

addCounter :: Expr -> Expr
addCounter = runOurState . onVarStringM enumerate

哦,等等!

注意,这实际上没有正确的行为 - 它没有很好地枚举你的变量(prop_addCounter1失败但prop_addCounter2通过)。不幸的是,我不确定应该怎么做......但鉴于这里存在的问题分离,只需编写适当的enumerate State个完整行动就可以了。将它应用于相同的通用Expr - 转换机制。