文件夹和文件夹之间的区别对地图和集合重要吗?

时间:2018-12-07 23:35:22

标签: haskell fold unordered

(这个问题比Haskell更为普遍,但这是我要用的语言。)

foldlfoldr之间的区别似乎取决于列表是有序的。也就是说,foldlfoldl通过将函数应用于起始值和第一个或最后一个元素,然后应用于第一个应用程序的结果和第二个或倒数第二个元素来折叠列表,然后将第二次应用的结果应用于第三个或倒数第二个元素,等等。

但是Haskell的Data.Set和Data.Map库定义了它们自己的foldlfoldr版本。例如,对于地图,它们是这些:

foldr :: (a -> b -> b) -> b -> Map k a -> b
foldl :: (b -> a -> b) -> b -> Map k a -> b -- I've swapped `a` and `b`
  -- in the second type signature to clarify the correspondence.

地图和集合未排序。我应该期望为集合和映射定义的foldlfoldr版本之间的性能差异,还是foldr f做与foldl (flip f)完全相同的事情?

2 个答案:

答案 0 :(得分:5)

实际上,集合和映射是 。这就是为什么所有set和map函数都对键类型设置Ord约束的原因。它们是根据元素类型的自然顺序自动排序的。因此,如果您的集合中包含{3, 5, 2, 1, 4},则Haskell会按{1, 2, 3, 4, 5}的顺序查看(出于折叠目的)。

但是让我们暂时忘记它。假设我们处于一个完美的世界,其中的数据确实是无序的。即使这样,foldlfoldr之间的差异仍然很明显。假设我的集合如前所述:{3, 5, 2, 1, 4},并且我想对其执行一些操作.*

foldl (.*) 0 mySet = ((((0 .* 3) .* 5) .* 2) .* 1) .* 4
foldr (.*) 0 mySet = 3 .* (5 .* (2 .* (1 .* (4 .* 0))))

因此,即使操作恰好是关联的,初始元素也以foldlfoldr相对放置。实际上,如果使用错误的折叠执行某些操作,则甚至不起作用。考虑toList,它在Data.Foldable中定义为可在任何Foldable对象(包括列表,地图和集合)上工作。一种可能的实现方式是

toList :: Foldable t => t a -> [a]
toList = foldr (:) []

如果我们要尝试进行foldl

definitelyWrong :: Foldable t => t a -> [a]
definitelyWrong = foldl (:) []

然后它甚至不编译。

wrongfold.hs:5:25: error:
    • Occurs check: cannot construct the infinite type: a ~ [a]
      Expected type: [a] -> [a] -> [a]
        Actual type: a -> [a] -> [a]

这是因为折叠的关联方式不同,甚至可以使用带有两个不同类型参数的累加运算,这从两个类型的签名中也很明显

foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b

请注意第一个参数不是a -> a -> a,而是实际上具有两种不同的类型,这表明排序确实很重要。

答案 1 :(得分:2)

  

地图和集合未排序。我是否应该期望为集合和地图定义的foldl和foldr版本之间的性能差异

如果引用Data.SetData.Map的源代码,您会发现它们的元素以二进制树的形式组织:

data Map k a  = Bin !Size !k a !(Map k a) !(Map k a)
              | Tip 

data Set a    = Bin !Size !a !(Set a) !(Set a)
              | Tip

和Set的foldr

foldr f z = go z
  where
    go z' Tip           = z'
    go z' (Bin _ x l r) = go (f x (go z' r)) l

使用深度优先搜索按顺序右,当前,左遍历树,因此当foldr (+) 0适用于跟随树时:

                   1
                  / \
                 4   2
                      \
                       3

给予

4 + (1 + (2 + (3 + 0)))

foldl

foldl f z = go z
  where
    go z' Tip           = z'
    go z' (Bin _ x l r) = go (f (go z' l) x) r

按顺序左,当前,右,将foldl (+) 0应用于上面的树,给出:

((((0 + 4) + 1) + 2) + 3)

它表明Set的foldrfoldl等同于以下列表:

foldr (+) 0 [4, 1, 2, 3] = 4 + (1 + (2 + (3 + 0)))
foldl (+) 0 [4, 1, 2, 3] = ((((0 + 4) + 1) + 2) + 3)

Data.Map类似的情况,在此不再赘述。

此外,我们知道,foldr可以应用于无限列表(但foldl不能),例如:

take 10 $ foldr ((:) . sum) [] $ chunksOf 3 [1..] = [6,15,24,33,42,51,60,69,78,87]

(这里chunksOf将列表分组为[[1,2,3], [4,5,6]...]

但是树何时有路径怎么办呢?

                   1
                  / \
                 4   2
                      \
                       3
                        \
                         ...  <- infinite path

Set的foldr是否像上面提到的那样充当列表? (我想答案是肯定的,您可以自己检查一下)

  

文件夹f是否与文件夹(翻转f)完全相同?

,如上图所示,作为源代码:

foldr          = ... go (f x (go z' r)) l 

foldl (flip f) = ... go (f x (go z' l)) r

树的遍历顺序不同,但是foldrfoldl之间的一般关系可以在这篇文章中找到:Defining foldl in terms of foldr