Haskell 哈斯克尔:把一棵树变成地图

Haskell 哈斯克尔:把一棵树变成地图,haskell,map,binary-tree,Haskell,Map,Binary Tree,基本上,我想把一个BST树变成一个映射,其中节点是键,节点的出现次数是值。如果我输入这个: 番茄叶13 我会得到 > [(13,1)] 以下是我到目前为止的情况: data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show) leaf x = Node x Empty Empty toMap' :: Int -> Tree a -> ([(a, Int)], Int) toMap' a Empty = ([],

基本上,我想把一个BST树变成一个映射,其中节点是键,节点的出现次数是值。如果我输入这个:

番茄叶13

我会得到

> [(13,1)]
以下是我到目前为止的情况:

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

toMap' :: Int -> Tree a -> ([(a, Int)], Int)
toMap' a Empty = ([], a)
toMap' a (Node x xl xr) = ((x, a): xl' ++ xr', k)
                      where (xl', i) = toMap' (a+1) xl
                            (xr', k) = toMap' (i) xr

toMap :: Tree a -> [(a, Int)]
toMap = fst. toMap' 1

此程序返回映射,但值不正确。每个值比前一个值大一个,因此如果有3个节点,则第三个节点的值将为3。我想我必须在每把新钥匙上放置一个计数器,但我不确定如何放置。提前感谢

假设您有一个函数foldt,它可以按与当前应用程序无关的顺序在树上折叠,还有一个函数insertIncr,它可以在映射中插入或增加一个键的值a Int,那么您可以将一个应用于另一个

您将处理以下类型签名:

import Data.Map

foldt :: (a -> b -> b) -> b -> Tree a -> b
foldt f acc Empty = acc
foldt f acc (Node x l r) = acc'
    where accl = foldt f acc l
          accr = foldt f accl r
          acc' = f x accr

-- insert 1 if not present, increment if present
insertIncr :: Ord a => a -> Map a Int -> Map a Int
insertIncr = undefined

toMap' :: Ord a => Tree a -> Map a Int
toMap' = foldt insertIncr empty
insertIncr功能可以使用以下方法实现:例如。请注意,为了在Data.Map中插入内容,Ord类型类是必需的。如果您更喜欢纯[a,Int]类型的映射,那么insertIncr可以具有Eq a=>a->[a,Int]->[a,Int]类型


编辑:更改了使用adjustWithKey插入的建议。

假设您有一个函数foldt,它以与当前应用程序无关的顺序在树上折叠,并且有一个函数insertIncr,它在映射中插入或增加一个键的值Int,您可以将一个应用到另一个

您将处理以下类型签名:

import Data.Map

foldt :: (a -> b -> b) -> b -> Tree a -> b
foldt f acc Empty = acc
foldt f acc (Node x l r) = acc'
    where accl = foldt f acc l
          accr = foldt f accl r
          acc' = f x accr

-- insert 1 if not present, increment if present
insertIncr :: Ord a => a -> Map a Int -> Map a Int
insertIncr = undefined

toMap' :: Ord a => Tree a -> Map a Int
toMap' = foldt insertIncr empty
insertIncr功能可以使用以下方法实现:例如。请注意,为了在Data.Map中插入内容,Ord类型类是必需的。如果您更喜欢纯[a,Int]类型的映射,那么insertIncr可以具有Eq a=>a->[a,Int]->[a,Int]类型


编辑:更改了使用adjustWithKey插入的建议。

老实说,这是一个我只需通过成分分解来解决的问题

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

toMap :: Ord a => Tree a -> [(a, Int)]
toMap = countDups . toList
注意,我必须在a上添加一个额外的约束。它至少需要Eq才能解,但Ord允许渐近更好的解

这里的基本思想是将解决方案分解为多个部分,然后计算出每个部分。所以,下一部分是托利斯。我不打算假设顺序很重要,因此我将选择前缀排序,因为它很容易使两者都变得懒惰和简单

toList :: Tree a -> [a]
toList Empty = []
toList (Node a l r) = a : toList l ++ toList r
好的,简单明了。继续数一数重复的数字。让我们也把它分成几部分

countDups :: Ord a => [a] -> [(a, Int)]
countDups = map (\xs -> (head xs, length xs)) . group . sort
好的,我可能利用group和sort from Data.List在这里有点作弊。但另一方面,这正是团队要解决的问题。排序只是一个标准的工具

如果导入Control.Arrow,我会用head&&&length替换lambda。但这只是一个标准的习语,并不能真正简化事情——它只是让它们的输入更加简洁

这种方法的主要思想是将问题分解成若干部分,独立完成一些有意义的事情。然后将这些部分组合成完整的解决方案。有一种将树a转换为[a]的方法非常方便。也可以有一个函数来实现这一点。一旦你这样做了,剩下的部分就是一个有用的逻辑,可以用来处理列表。如果你把它分解,你会发现它是现有比特列表功能的简单组合


这通常是在任何编程语言中解决问题的最佳方法之一——将大任务分解为小任务。在Haskell中这样做的好处在于,将较小的任务组合到整个过程中是一个非常简洁的过程。

老实说,我只需要通过组合分解来解决这个问题

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

toMap :: Ord a => Tree a -> [(a, Int)]
toMap = countDups . toList
注意,我必须在a上添加一个额外的约束。它至少需要Eq才能解,但Ord允许渐近更好的解

这里的基本思想是将解决方案分解为多个部分,然后计算出每个部分。所以,下一部分是托利斯。我不打算假设顺序很重要,因此我将选择前缀排序,因为它很容易使两者都变得懒惰和简单

toList :: Tree a -> [a]
toList Empty = []
toList (Node a l r) = a : toList l ++ toList r
好的,简单明了。继续数一数重复的数字。让我们也把它分成几部分

countDups :: Ord a => [a] -> [(a, Int)]
countDups = map (\xs -> (head xs, length xs)) . group . sort
好的,我可能利用group和sort from Data.List在这里有点作弊。但另一方面,这正是团队要解决的问题。排序只是一个标准的工具

如果导入Control.Arrow,我会用head&&&length替换lambda。但这只是一个标准的习语,并不能真正简化事情——它只是让它们的输入更加简洁

这种方法的主要思想是将问题分解成若干部分,独立完成一些有意义的事情。然后将这些部分组合成完整的解决方案。有一种将树a转换为[a]的方法非常方便。也可以有一个函数来实现这一点。一旦你这样做了,它就会 e剩余的部分是一个有用的逻辑位,可用于处理列表。如果你把它分解,你会发现它是现有比特列表功能的简单组合


这通常是在任何编程语言中解决问题的最佳方法之一——将大任务分解为小任务。在Haskell中这样做的好处在于,将较小的任务组合到整个过程中是一个非常简洁的过程。

非常感谢!不仅如此,我还学到了很多关于如何编写程序的方法。我还学习了一些将来有用的新功能。再次感谢@user2548080:Simons解决方案的效率要高得多——出于好奇,我测试了它,使用一个由数字1..150组成的树反复循环。Simon的解决方案可以在半秒钟内处理200000个元素,而Carl的解决方案可以处理10000个元素。如果你有很多数据,要小心一些简单的事情,比如仅仅对数据进行排序!此外,折叠是在高层次上表达许多问题的适当思维结构。或者,可以定义toList=foldt:[]:对于渐进比较,进行树折叠并插入到数据中。映射在lg n n上表示元素数,lg n表示插入到基于树的映射中,而countDups在lg n+n+n lg n上表示排序,n表示分组,n表示长度。尽管排序在这里是一个渐进的坏蛋,但我还是忍不住想,长度也是罪魁祸首。我不知道内存使用是否是这里真正的坏蛋。@MikeHartl的表现几乎不重要。如果你在简单化之前就关注它,那你就做错了。当我建议它时,我知道这不是最快的。但是在99%的用例中,这并不重要。非常感谢!不仅如此,我还学到了很多关于如何编写程序的方法。我还学习了一些将来有用的新功能。再次感谢@user2548080:Simons解决方案的效率要高得多——出于好奇,我测试了它,使用一个由数字1..150组成的树反复循环。Simon的解决方案可以在半秒钟内处理200000个元素,而Carl的解决方案可以处理10000个元素。如果你有很多数据,要小心一些简单的事情,比如仅仅对数据进行排序!此外,折叠是在高层次上表达许多问题的适当思维结构。或者,可以定义toList=foldt:[]:对于渐进比较,进行树折叠并插入到数据中。映射在lg n n上表示元素数,lg n表示插入到基于树的映射中,而countDups在lg n+n+n lg n上表示排序,n表示分组,n表示长度。尽管排序在这里是一个渐进的坏蛋,但我还是忍不住想,长度也是罪魁祸首。我不知道内存使用是否是这里真正的坏蛋。@MikeHartl的表现几乎不重要。如果你在简单化之前就关注它,那你就做错了。当我建议它时,我知道这不是最快的。但在99%的用例中,这并不重要;如果你把它做成可折叠的,你可以把它折叠成一张地图,这会很整洁;如果将其设置为可折叠的,则可以将其折叠到地图中,这将非常整洁。如果钥匙不在地图中,则adjustWithKey将忽略插入。一个正确的定义是Data.Map.insertWith+k 1adjustWithKey,如果键不在映射中,则忽略插入。一个合适的定义是Data.Map.insertWith+k 1