考虑以下树的定义:
data Tree a = Leaf a | Node [Tree a]
示例树:
input :: Tree String
input = Node [Leaf "a", Leaf "b", Node [Leaf "c", Leaf "d"]]
我正在尝试在这样的树上“映射”列表,而应该丢弃树的值。在[0..]
作为列表的情况下,结果应如下所示:
output :: Tree Int
output = Node [Leaf 0, Leaf 1, Node [Leaf 2, Leaf 3]]
所以我正在寻找一个功能..
seqTree :: [b] -> Tree a -> Tree b
seqTree = undefined
..以下内容:
seqTree [0..] input == output
我得出的结论是,像这样的函数必须包装另一个函数,以便跟踪尚未被“采用”的列表项:
seqTree' :: [b] -> Tree a -> Tree ([b], b)
seqTree' xxs@(x:xs) t = case t of
Leaf _ -> Leaf (xs, x)
--Node ts = the tricky part... maybe something with foldr?
seqTree' [] t = error "empty list."
有了这个我希望实现seqTree
,这需要对整个树进行最终映射,我想有更好的方法来做到这一点,这里是一个冗长的版本:
finish :: Tree (a,b) -> Tree b
finish t = case t of
Leaf v -> Leaf $ snd v
Node ts -> Node (map finish ts)
最后:
seqTree xs t = finish $ seqTree' xs t
这会编译,但是在注释中标记时,函数seqTree'
是部分的。有没有人知道如何解决这个问题,而且,解决这个问题的更合适,更低级别的方法是什么?
答案 0 :(得分:4)
我认为有一种方法可以将此视为更通用的特定情况:通过在原始树叶上组合许多较小的有状态计算来生成树作为输出的有状态计算。 Lee提供了一种使用State和标签列表手动实现这一功能的好方法,但是我们可以通过将作业分为两个步骤来简化事物并重用一些来自Applicative和State的内置机制:首先,fmap over你的树,用State s
monad中的相同值替换每个节点,然后使用sequenceA :: Tree (State s a) -> State s (Tree a)
按顺序通过每个节点运行有状态计算。
当然,这意味着你必须为你的树类型实现Foldable和Traversable,但无论如何这些都是为树类型编写的好例子。假设已经编写了这些,你可以实现这样的功能:
seqTree :: [b] -> Tree a -> Tree b
seqTree labels = evalState labels . sequenceA . (nextLabel <$)
where nextLabel = do
(x:xs) <- get
put xs
pure x
或者,正如在对此答案的先前版本的评论中所指出的,而不是sequenceA . (nextLabel <$)
,写traverse (const nextLabel)
可能更清晰。
答案 1 :(得分:3)
您可以使用State
,其中州包含剩余的值列表。然后,您可以提供一个函数,该函数根据当前值和输入流中的下一个值转换树中的值,例如
data Tree a = Leaf a | Node [Tree a] deriving (Show)
input :: Tree String
input = Node [Leaf "a", Leaf "b", Node [Leaf "c", Leaf "d"]]
labelWithState :: (a -> l -> b) -> Tree a -> State [l] (Tree b)
labelWithState f (Leaf v) = do
(l : ls) <- get
put ls
pure $ Leaf (f v l)
labelWithState f (Node ts) = do
lts <- traverse (labelWithState f) ts
pure $ Node lts
labelWith :: (a -> l -> b) -> [l] -> Tree a -> Tree b
labelWith f ls t = evalState (labelWithState f t) ls
然后您可以将seqTree
定义为:
seqTree :: [b] -> Tree a -> Tree b
seqTree = labelWith (\_ l -> l)
答案 2 :(得分:3)
我真的不明白为什么你需要一个“完成”可以这么说。您可以定义一个函数:
seqTree' :: [b] -> Tree a -> ([b],Tree b)
映射给定子树上的序列的一部分,并返回结果树以及 not 尚未消耗的元素。所以你通过函数调用传递元素列表,可以这么说,每个函数“吃掉”它的一些元素并返回尾部,以便其他函数可以“吃掉”下一个元素。
现在和大多数递归函数一样,有Tree a
为Leaf x
的基本情况:
seqTree' (x:xs) (Leaf _) = (xs,Leaf x)
这里你返回一个带有序列给定元素的Leaf x
,然后返回序列的其余部分。
接下来还有seqTree'
的{{1}}案例,在这种情况下,您将序列提供给Node
的调用,并且该调用会占用树的一部分,其余部分用于呼叫第二个孩子,依此类推。因此,对于有三个孩子的树,它看起来像:
seqTree'
好的是,已经存在这样的功能:mapAccumL
。所以你可以写:
--Example
seqTree' xsa (Tree [na,nb,nc]) = (xsd,Tree [oa,ob,oc])
where (xsb,oa) = seqTree' xsa na
(xsc,ob) = seqTree' xsb nb
(xsd,oc) = seqTree' xsc nc
或完整功能:
seqTree' xsa (Node nodes) = (xsz,Node newnodes)
where (xsz,newnodes) = mapAccumL seqTree' xsa nodes
现在我们只需构建一个从seqTree' (x:xs) (Leaf _) = (xs,Leaf x)
seqTree' xsa (Node nodes) = (xsz,Node newnodes)
where (xsz,newnodes) = mapAccumL seqTree' xsa nodes
到seqTree
的来电,这只是丢弃剩余的Feed:
seqTree'
或者更短一些:
seqTree xs tree = snd $ seqTree' xs tree
如果我将seqTree xs = snd . seqTree' xs
添加到您的deriving Show
定义并运行该程序,我得到了:
Tree a