将数据类型转换为地图

时间:2013-07-18 21:51:30

标签: haskell recursion map algebraic-data-types

我想将我的数据类型Exp转换为一个映射,其中函数名称(Add,Subtract等)是键,值是它们在表达式中出现的次数。这是我的数据声明:

data Exp = Number     Int
         | Add        Exp Exp
         | Subtract   Exp Exp
         | Multiply   Exp Exp
         | Divide     Exp Exp
  deriving Show

我可以用BST解决这个问题,但我似乎无法使用不同的数据类型完成此任务。如果有帮助,这是我的BST解决方案:

import Data.Map 

data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show)
leaf x = Node x Empty Empty

foldt :: (a -> b -> b) -> b -> Tree a -> b
foldt f a Empty = a
foldt f a (Node x xl xr) = f x ar 
                           where al = foldt f a xl
                                 ar = foldt f al xr

insert' :: Ord a => a -> Map a Int -> Map a Int 
insert' a = insertWith (+) a 1 

toMap :: Ord a => Tree a -> Map a Int
toMap = foldt insert' empty

在完成上述程序后似乎应该很简单,但我甚至不知道从哪里开始。注意:我想尽可能多地使用递归。提前谢谢!

2 个答案:

答案 0 :(得分:3)

您的树函数使用包含a的树来生成b类型的值,但您的Exp数据类型除了要合并的表达式之外不包含任何内容(或计数) )。让我们制作第二种数据类型,我们可以计算它的出现次数。它最好是Ord,因此我们需要Eq,而Show会对输出有益:

data Term = NumberTerm | AddTerm | SubtractTerm | MultiplyTerm | DivideTerm
  deriving (Eq, Ord, Show)

每个代表Exp类型的术语。

我已将您的insert'重命名为inc

inc :: Ord a => a -> Map a Int -> Map a Int 
inc a = insertWith (+) a 1 

不,我们已准备好计算:

countExp :: Exp -> Map Term Int

Number只有一个字词(没有子字符),因此我们将以empty开头并增加NumberTerm的数量:

countExp (Number _) = inc NumberTerm empty

Add条款更复杂。每个表达式都有自己的计数,因此我们在每个子项上递归使用countExp,然后我们unionWith (+)对计数求和。之后,我们inc AddTerm将当前Add字词包含在总计中。

countExp (Add e1 e2) = inc AddTerm $ unionWith (+) (countExp e1) (countExp e2) 

Subtract我们几乎可以完全相同:

countExp (Subtract e1 e2) = inc SubtractTerm $ unionWith (+) (countExp e1) (countExp e2) 

你现在明白我的想法,所以你可以完成。

答案 1 :(得分:1)

这是一个选项,这是AndrewC的回答略有不同。您可以将表达式表示为更简单的基本类型上的自由monad,而不是将表示Exp类型的构造函数的单独数据类型创建为数字。例如,如果基类型是

import Control.Monad.Free
import Data.Map

data ExpT a = Number a
            | Add a a
            | Subtract a a
            | Multiply a a
            | Divide a a
            deriving (Eq,Ord,Show)

然后您的表达式可以定义为ExpT上的免费monad,其中Int为根类型

type Exp = Free ExpT Int

现在你在AndrewC的帖子中写inc

inc :: Ord a => a -> Map a Int -> Map a Int
inc a = insertWith (+) a 1

并且countExp功能再次非常相似

countExp :: Exp -> Map (ExpT ()) Int
countExp (Free (Number _)) = inc (Number ()) empty
countExp (Free (Add a b))  = inc (Add () ()) $ unionWith (+) (countExp a) (countExp b)

等等。您可能想要定义一些用于创建表达式的便利函数

number :: Int -> Exp

number n = Free (Number (Pure n))
add a b  = Free (Add a b)
sub a b  = Free (Subtract a b)
mul a b  = Free (Multiply a b)
divide a b = Free (Divide a b)

,最终结果是

>>> countExp (add (number 1) (number 2))
fromList [(Number (),2),(Add () (),1)]