Foldr 和 Foldl 进一步解释和示例

发布于 2024-09-27 17:51:42 字数 363 浏览 13 评论 0原文

我查看了不同的折叠一般折叠 以及其他一些内容,他们解释得相当好。

我仍然不知道 lambda 在这种情况下如何工作。

foldr (\y ys -> ys ++ [y]) [] [1,2,3]

有人可以逐步完成该过程并尝试向我解释吗?

还有 foldl 是如何工作的?

I've looked at different folds and folding in general as well as a few others and they explain it fairly well.

I'm still having trouble on how a lambda would work in this case.

foldr (\y ys -> ys ++ [y]) [] [1,2,3]

Could someone go through that step-by-step and try to explain that to me?

And also how would foldl work?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

绿光 2024-10-04 17:51:42

foldr 是一件简单的事情:

foldr :: (a->b->b) -> b -> [a] -> b

它需要一个类似于 (:) 的函数

(:) :: a -> [a] -> [a]

和一个类似于空列表 [] 的值,

[] :: [a]

并替换某个列表中的每个 : 和 []。

它看起来像这样:

foldr f e (1:2:3:[]) = 1 `f` (2 `f` (3 `f` e))

您也可以将foldr想象为某种状态机评估器:

f是转换,

f :: input -> state -> state

e是开始状态。

e :: state

foldr (foldRIGHT) 在输入列表上运行带有转换 f 和起始状态 e 的状态机,从右端开始。想象一下中缀表示法中的 pacman 来自右方。

Foldl (foldLEFT) 从左开始执行相同的操作,但以中缀表示法编写的转换函数从右获取其输入参数。因此机器从左端开始消耗列表。 Pacman 从左开始使用张开的嘴向右消费列表,因为嘴是 (b->a->b) 而不是 (a->b->b)。

foldl :: (b->a->b) -> b -> [a] -> b

为了清楚地说明这一点,请将函数 (-) 想象为转换:

foldl (-) 100 [1]         = 99 = ((100)-1)
foldl (-) 100 [1,2]       = 97 = (( 99)-2) = (((100)-1)-2)
foldl (-) 100 [1,2,3]     = 94 = (( 97)-3)
foldl (-) 100 [1,2,3,4]   = 90 = (( 94)-4)
foldl (-) 100 [1,2,3,4,5] = 85 = (( 90)-5)

foldr (-) 100 [1]         = -99 = (1-(100))
foldr (-) 100 [2,1]       = 101 = (2-(-99)) = (2-(1-(100)))
foldr (-) 100 [3,2,1]     = -98 = (3-(101))
foldr (-) 100 [4,3,2,1]   = 102 = (4-(-98))
foldr (-) 100 [5,4,3,2,1] = -97 = (5-(102))

您可能希望在列表可以无限且评估应该惰性的情况下使用foldr:

foldr (either (\l ~(ls,rs)->(l:ls,rs))
              (\r ~(ls,rs)->(ls,r:rs))
      ) ([],[]) :: [Either l r]->([l],[r])

并且您可能希望使用当您使用整个列表来生成其输出时,foldl 的严格版本是 Foldl'。它可能会表现得更好,并且可能会防止您因极长的列表与惰性求值相结合而出现堆栈溢出或内存不足异常(取决于编译器):

foldl' (+) 0 [1..100000000] = 5000000050000000
foldl  (+) 0 [1..100000000] = error "stack overflow or out of memory" -- dont try in ghci
foldr  (+) 0 [1..100000000] = error "stack overflow or out of memory" -- dont try in ghci

第一个 - 一步一步 - 创建列表的一个条目,对其进行评估并使用它。

第二个首先创建一个非常长的公式,使用 ((...((0+1)+2)+3)+...) 浪费内存,然后对所有公式进行求值。

第三个与第二个类似,但公式不同。

foldr is an easy thing:

foldr :: (a->b->b) -> b -> [a] -> b

It takes a function which is somehow similar to (:),

(:) :: a -> [a] -> [a]

and a value which is similar to the empty list [],

[] :: [a]

and replaces each : and [] in some list.

It looks like this:

foldr f e (1:2:3:[]) = 1 `f` (2 `f` (3 `f` e))

You can imagine foldr as some state-machine-evaluator, too:

f is the transition,

f :: input -> state -> state

and e is the start state.

e :: state

foldr (foldRIGHT) runs the state-machine with the transition f and the start state e over the list of inputs, starting at the right end. Imagine f in infix notation as the pacman coming from-RIGHT.

foldl (foldLEFT) does the same from-LEFT, but the transition function, written in infix notation, takes its input argument from right. So the machine consumes the list starting at the left end. Pacman consumes the list from-LEFT with an open mouth to the right, because of the mouth (b->a->b) instead of (a->b->b).

foldl :: (b->a->b) -> b -> [a] -> b

To make this clear, imagine the function (-) as transition:

foldl (-) 100 [1]         = 99 = ((100)-1)
foldl (-) 100 [1,2]       = 97 = (( 99)-2) = (((100)-1)-2)
foldl (-) 100 [1,2,3]     = 94 = (( 97)-3)
foldl (-) 100 [1,2,3,4]   = 90 = (( 94)-4)
foldl (-) 100 [1,2,3,4,5] = 85 = (( 90)-5)

foldr (-) 100 [1]         = -99 = (1-(100))
foldr (-) 100 [2,1]       = 101 = (2-(-99)) = (2-(1-(100)))
foldr (-) 100 [3,2,1]     = -98 = (3-(101))
foldr (-) 100 [4,3,2,1]   = 102 = (4-(-98))
foldr (-) 100 [5,4,3,2,1] = -97 = (5-(102))

You probably want to use foldr in situations where the list can be infinite, and where the evaluation should be lazy:

foldr (either (\l ~(ls,rs)->(l:ls,rs))
              (\r ~(ls,rs)->(ls,r:rs))
      ) ([],[]) :: [Either l r]->([l],[r])

And you probably want to use the strict version of foldl, which is foldl', when you consume the whole list to produce its output. It might perform better and might prevent you from having stack-overflow or out-of-memory exceptions (depending on compiler) due to extreme long lists in combination with lazy evaluation:

foldl' (+) 0 [1..100000000] = 5000000050000000
foldl  (+) 0 [1..100000000] = error "stack overflow or out of memory" -- dont try in ghci
foldr  (+) 0 [1..100000000] = error "stack overflow or out of memory" -- dont try in ghci

The first one –step by step– creates one entry of the list, evaluates it, and consumes it.

The second one creates a very long formula first, wasting memory with ((...((0+1)+2)+3)+...), and evaluates all of it afterwards.

The third one is like the second, but with the other formula.

烛影斜 2024-10-04 17:51:42

使用

foldr f z []     = z
foldr f z (x:xs) = x `f` foldr f z xs

k y ys = ys ++ [y]

解压:

foldr k [] [1,2,3]
= k 1 (foldr k [] [2,3]
= k 1 (k 2 (foldr k [] [3]))
= k 1 (k 2 (k 3 (foldr k [] [])))
= (k 2 (k 3 (foldr k [] []))) ++ [1]
= ((k 3 (foldr k [] [])) ++ [2]) ++ [1]
= (((foldr k [] []) ++ [3]) ++ [2]) ++ [1]
= ((([]) ++ [3]) ++ [2]) ++ [1]
= (([3]) ++ [2]) ++ [1]
= ([3,2]) ++ [1]
= [3,2,1]

Using

foldr f z []     = z
foldr f z (x:xs) = x `f` foldr f z xs

And

k y ys = ys ++ [y]

Let's unpack:

foldr k [] [1,2,3]
= k 1 (foldr k [] [2,3]
= k 1 (k 2 (foldr k [] [3]))
= k 1 (k 2 (k 3 (foldr k [] [])))
= (k 2 (k 3 (foldr k [] []))) ++ [1]
= ((k 3 (foldr k [] [])) ++ [2]) ++ [1]
= (((foldr k [] []) ++ [3]) ++ [2]) ++ [1]
= ((([]) ++ [3]) ++ [2]) ++ [1]
= (([3]) ++ [2]) ++ [1]
= ([3,2]) ++ [1]
= [3,2,1]
聚集的泪 2024-10-04 17:51:42

foldr 的定义是:

foldr f z []     = z
foldr f z (x:xs) = f x (foldr f z xs)

因此,这是示例的逐步简化:

  foldr (\y ys -> ys ++ [y]) [] [1,2,3]
= (\y ys -> ys ++ [y]) 1 (foldr (\y ys -> ys ++ [y]) [] [2,3])
= (foldr (\y ys -> ys ++ [y]) [] [2,3]) ++ [1]
= (\y ys -> ys ++ [y]) 2 (foldr (\y ys -> ys ++ [y]) [] [3]) ++ [1]
= (foldr (\y ys -> ys ++ [y]) [] [3]) ++ [2] ++ [1]
= (\y ys -> ys ++ [y]) 3 (foldr (\y ys -> ys ++ [y]) [] []) ++ [2] ++ [1]
= (foldr (\y ys -> ys ++ [y]) [] []) ++ [3] ++ [2] ++ [1]
= [] ++ [3] ++ [2] ++ [1]
= [3,2,1]

The definition of foldr is:

foldr f z []     = z
foldr f z (x:xs) = f x (foldr f z xs)

So here's a step by step reduction of your example:

  foldr (\y ys -> ys ++ [y]) [] [1,2,3]
= (\y ys -> ys ++ [y]) 1 (foldr (\y ys -> ys ++ [y]) [] [2,3])
= (foldr (\y ys -> ys ++ [y]) [] [2,3]) ++ [1]
= (\y ys -> ys ++ [y]) 2 (foldr (\y ys -> ys ++ [y]) [] [3]) ++ [1]
= (foldr (\y ys -> ys ++ [y]) [] [3]) ++ [2] ++ [1]
= (\y ys -> ys ++ [y]) 3 (foldr (\y ys -> ys ++ [y]) [] []) ++ [2] ++ [1]
= (foldr (\y ys -> ys ++ [y]) [] []) ++ [3] ++ [2] ++ [1]
= [] ++ [3] ++ [2] ++ [1]
= [3,2,1]
情定在深秋 2024-10-04 17:51:42

中缀表示法在这里可能会更清楚。

让我们从定义开始:

foldr f z []     = z
foldr f z (x:xs) = x `f` (foldr f z xs)

为了简洁起见,让我们写成 g 而不是 (\y ys -> ys ++ [y])。以下几行是等效的:

foldr g [] [1,2,3]
1 `g` (foldr g [] [2,3])
1 `g` (2 `g` (foldr g [] [3]))
1 `g` (2 `g` (3 `g` (foldr g [] [])))
1 `g` (2 `g` (3 `g` []))
(2 `g` (3 `g` [])) ++ [1]
(3 `g` []) ++ [2] ++ [1]
[3] ++ [2] ++ [1]
[3,2,1]

Infix notation will probably be clearer here.

Let's start with the definition:

foldr f z []     = z
foldr f z (x:xs) = x `f` (foldr f z xs)

For the sake of brevity, let's write g instead of (\y ys -> ys ++ [y]). The following lines are equivalent:

foldr g [] [1,2,3]
1 `g` (foldr g [] [2,3])
1 `g` (2 `g` (foldr g [] [3]))
1 `g` (2 `g` (3 `g` (foldr g [] [])))
1 `g` (2 `g` (3 `g` []))
(2 `g` (3 `g` [])) ++ [1]
(3 `g` []) ++ [2] ++ [1]
[3] ++ [2] ++ [1]
[3,2,1]
挖鼻大婶 2024-10-04 17:51:42

我首先记住这一点的方法是通过使用关联敏感减法操作:

foldl (\a b -> a - b) 1 [2] = -1
foldr (\a b -> a - b) 1 [2] = 1

然后,foldl从最左边或第一个元素开始列表的元素,而 foldr 从列表的最右边或最后一个元素开始。上面这一点并不明显,因为列表只有一个元素。

我的助记符是这样的:leftright 描述了两件事:

  • 减号 (-) 符号的位置
  • 列表的起始元素

My way of remembering this firstly, is through the use of an associative sensitive subtraction operation:

foldl (\a b -> a - b) 1 [2] = -1
foldr (\a b -> a - b) 1 [2] = 1

Then secondly , foldl starts at the leftmost or first element of the list whereas foldr starts at the rightmost or last element of the list. It is not obvious above since the list has only one element.

My mnemonic is this: The left or right describes two things:

  • the placement of the minus (-) symbol
  • the starting element of the list
寻找一个思念的角度 2024-10-04 17:51:42

我倾向于通过运动来记住事物,所以我想象并想象飞来飞去的价值观。这是我对foldl 和foldr 的内部表示。

下图做了一些事情:

  1. 以直观的方式命名折叠函数的参数(对我来说),
  2. 显示每个特定折叠从哪一端开始工作(foldl 从左侧,foldr 从右侧),
  3. 颜色编码累加器和当前值,
  4. 通过 lambda 函数跟踪这些值,将它们映射到折叠的下一次迭代。

便于记忆的是,我记得foldl的参数按字母顺序排列(\ac ->),foldr的参数按相反的字母顺序排列(\ca ->)代码>)。 l 表示从左侧获取,r 表示从右侧获取。

I tend to remember things with movement, so I imagine and visualize values flying around. This is my internal representation of foldl and foldr.

The diagram below does a few things:

  1. names the arguments of the fold functions in a way that is intuitive (for me),
  2. shows which end each particular fold works from, (foldl from the left, foldr from the right),
  3. color codes the accumulator and current values,
  4. traces the values through the lambda function, mapping them onto the next iteration of the fold.

Mnemonically, I remember the arguments of foldl as being in alphabetical order (\a c ->), and the arguments of foldr to be in reverse alphabetical order (\c a ->). The l means take from the left, the r means take from the right.

A Story of Two Folds

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文