例如,我有一个类似 ['a','b','c','d','e'] 的列表。
我想做这样的事情:
首先对前两个元素做一些事情, f 'a' 'b'
然后对 f 的返回值和列表中的下一个元素做同样的事情,结果 = f 'a' 'b ',让我们说像 f 结果'c'。然后 f resultof(result 'c') 'd' 等等。
我怎么能做这样的事情?
3 回答
首先让我们考虑一下f
您拥有的功能。它采用某种累积值,一个普通值,并将它们组合成一个结果。所以,在类型签名中,我们会说a
累加值v
的类型、值r
的类型和结果的类型。
f :: a -> v -> r
现在我们要创建一个折叠函数,它使用f
和一个值列表。
someFold :: (a -> v -> r) -> [v] -> ?
它应该返回什么?它应该产生一些结果类型r
,对吧?现在请注意,a
andr
实际上应该是相同的类型,因为我们f
再次将结果输入到它的第一个参数中。
someFold :: (a -> v -> a) -> [v] -> a
现在缺少一件事。你如何获得第一名a
?有两种看待它的方法。要么只选择第一个值,在这种情况下a
与 的类型相同v
,要么指定一个基值,因此a
实际上可能与 不同v
。让我们选择后者,因为这更有趣。让我们也决定在这个列表中从左到右移动。(这就是你需要的,对吧?)
someFold :: (a -> v -> a) -> a -> [v] -> a
那么......我们如何实现它?这将是递归的,所以让我们从基本情况开始。
someFold f acc [] = acc
如果我们到达列表的末尾,那么我们已经积累了足够的,对吧?那很简单。那么递归情况呢?根据您所说,在每一步中,我们应该将f
“迄今为止的累计值”作为第一个参数,将“列表的第一个值”作为第二个参数。f acc x
. 然后我们继续折叠,将其用作我们新的“累积”值。
someFold f acc (x:xs) = someFold f (f acc x) xs
容易,对吧?但是......如果我们想像你说的那样做并通过获取列表的前两个值来启动函数怎么办?也很容易。只需取第一个元素,并将其称为原始“基础”累加器!
someFold1 :: (v -> v -> v) -> [v] -> v
someFold1 f (x:xs) = someFold f x xs
请注意,由于与这种特殊情况的a
类型相同,因此该函数具有非常有趣的类型签名。如果你理解了这个解释,那么恭喜你。我们刚刚实现了and。v
someFold1
foldl
foldl1
Prelude> foldl1 min "abcde" -- "abcde" is sugar for ['a','b','c','d','e']
'a'
在真正的代码中,你应该实际使用foldl'
和朋友。
听起来像家庭作业。看看褶皱。
在这种情况下,折叠的问题在于,它通常一次处理元素。您可以尝试手动滚动折叠。
假设你有你的函数f
,它一次获取两个元素并馈送累加器(最后一次迭代的结果)。然后你的函数看起来像这样:
fold2 :: (a -> a -> b -> b) -> [a] -> b -> b
fold2 f accum (x:y:zs) = fold2 f (f x y) zs
fold2 _ accum [] = accum
fold2 _ _ _ = error "odd number of elements"
试着理解这一点。fold2
刮掉列表的前两个元素并将其输入f
. 然后将结果 this 作为新的累加器传递给递归调用。这样做直到列表为空。