Data.Sequence中的inits和tails如何工作?

时间:2015-03-06 20:05:24

标签: haskell data-structures sequence

Louis Wasserman在inits中编写了tailsData.Sequence的当前实现。他表示他们非常有效率,而且只是看着我能看到的代码,无论他们做什么,他们都是以干净,自上而下的方式做到这一点,这往往会导致懒树的良好表现。不幸的是,我实际上无法正视他们正在做的事情。任何人都可以帮我一把吗?代码有点长,但可以在Hackage上找到。

1 个答案:

答案 0 :(得分:7)

是我!

我认为最好的方法可能是通过一个例子。让我们一起去......

Deep (Two 1 2)                                                    (Two 7 8))
                (Deep (One (Node2 3 4))        (One (Node2 5 6))
                                         Empty

这是一个有点简化的序列(例如省略了Elem包装器)。

让我们来做这件事;尾巴基本上是对称的。我们的递归算法将省略空的init并且只包括非空的东西。


<强>前缀

因此,inits的前缀数字将基本上由fmap digitToTree (initsDigit (Two 1 2))生成。

initsDigit (Two 1 2) = Two (One 1) (Two 1 2)
fmap digitToTree (Two (One 1) (Two 1 2)) = 
    Two (Single 1) (Deep (One 1) Empty (One 2))

所以这些是整个事物的前两个部分,这个数字将成为inits结果的前缀数字。 (除非我们在完成所有操作后才会在前面添加空序列,但让我们暂时忽略它。)


内树

现在让我们来看看内树的内容,将其视为FingerTree (Node a) - 我们不会将节点分开,它只是两个 - 元素FingerTree包含两个节点。我不打算详细说明这一点,它只是通过相同的算法递归,我只是神奇地达到了结果

Deep 
    (One (Single (Node2 3 4))) 
    Empty 
    (One (Deep (One (Node2 3 4)) Empty (One (Node2 5 6))))
  :: FingerTree (FingerTree (Node a))

所以这些是内树的内容。这些如何对应外树的内部?内树的每个init对应一个包含

的树
  • 原始树的前缀数字Two 1 2
  • 除了内树的init Node之外的所有内容
  • 内树的init Node的最后一个前缀

因此,通过获取内树的init获取的每个FingerTree (Node a)将映射到Node (FingerTree a),其中包含FingerTree,用于FingerTree (Node a)中最后一个节点的每个init 。

例如,提取最后一个节点的Single (Node2 3 4)将被分解为EmptyNode2 3 4,结果Node (FingerTree a)

Node2 
   (Deep (Two 1 2 {- prefix of original tree -}) 
         Empty 
         (One 3 {- first prefix of Node2 3 4 -}))
   (Deep (Two 1 2) 
         Empty 
         (Two 3 4 {- second prefix of Node2 3 4 -}))

对于内树的另一个前缀Deep (One (Node2 3 4)) Empty (One (Node2 5 6)),提取最后一个Node会为我们提供余数Single (Node2 3 4)和提取的节点Node2 5 6,因此得到的结果为Node2 (Deep (Two 1 2 {- prefix of original tree -}) (Single (Node2 3 4) {- init of the inner tree minus the last Node -}) (One 5 {- first prefix of Node2 5 6 -}) (Deep (Two 1 2 {- prefix of original tree -}) (Single (Node2 3 4) {- init of the inner tree minus the last Node -}) (Two 5 6 {- second prefix of Node2 5 6 -})) 。节点(FingerTree a)

FingerTree (Node a)

因此,这是一个将Node (FingerTree a)(内树的单个init)转换为FingerTree (FingerTree (Node a))的操作。因此,以递归方式获取内树的内容为FingerTree (Node (FingerTree a)),我们将此函数映射到它们上以获得initsDigit (Two 7 8),这正是我们想要的;它是整个事物内在的内在树。


<强>后缀

最后,还有由

组成的原始树的内容
  • 原始前缀
  • 原始内树
  • 原始树后缀的每个init

这些成为了inits树的后缀数字。 Two (One 7) (Two 7 8)返回\sf -> Deep pr m sf,我们基本上只是将Two (Deep (Two 1 2 {- original -}) (Deep (One (Node2 3 4)) Empty (One (Node2 5 6)) {- original -}) (One 7 {- first init of original suffix digit -})) (Deep (Two 1 2 {- original -}) (Deep (One (Node2 3 4)) Empty (One (Node2 5 6)) {- original -}) (Two 7 8 {- second init of original suffix digit -})) 映射到此,以获取

FingerTree a

所以,这并不是代码的组织方式。我们已经描述了从FingerTree (FingerTree a)fmap的函数,但实际的实现基本上是加{{1}},因为我们最终总是需要以某种方式映射元素 - 甚至如果它只是包装新类型。但这基本上就是我们正在做的事情。