Как работает foldr?
может кто-нибудь объяснить как foldr
работы?
возьмите следующие примеры:
Prelude> foldr (-) 54 [10, 11]
53
Prelude> foldr (x y -> (x+y)/2) 54 [12, 4, 10, 6]
12.0
Я смущен этими казнями. Есть предложения?
9 ответов
foldr
начинается в правом конце списка и объединяет каждую запись списка со значением аккумулятора, используя функцию, которую вы ему даете. Результатом является конечное значение аккумулятора после "складывания" во всех элементах списка. Типа:
foldr :: (a -> b -> b) -> b -> [a] -> b
и из этого вы можете видеть, что элемент списка (типа a
) является первым аргументом данной функции, а аккумулятор (типа b
) - второй.
для первого примера:
Starting accumulator = 54
11 - 54 = -43
10 - (-43) = 53
^ Result from the previous line
^ Next list item
так что ответ у тебя был 53.
второй пример:
Starting accumulator = 54
(6 + 54) / 2 = 30
(10 + 30) / 2 = 20
(4 + 20) / 2 = 12
(12 + 12) / 2 = 12
таким образом, результат 12.
Edit: я хотел добавить, что это для конечных списков. foldr
также может работать с бесконечными списками, но лучше всего сначала обойти конечный случай, я думаю.
самый простой способ понять foldr-переписать список, который вы складываете без сахара.
[1,2,3,4,5] => 1:(2:(3:(4:(5:[]))))
теперь что foldr f x
делает то, что он заменяет каждый :
С f
в инфиксной форме и []
С x
и оценивает результат.
например:
sum [1,2,3] = foldr (+) 0 [1,2,3]
[1,2,3] === 1:(2:(3:[]))
так
sum [1,2,3] === 1+(2+(3+0)) = 6
это помогает понять различие между foldr
и foldl
. Почему это foldr
называется "сложи правильно"?
Первоначально я думал, что это потому, что он потреблял элементы справа налево. Еще как foldr
и foldl
потребляйте список слева направо.
-
foldl
оценивает слева направо (слева-ассоциативный) -
foldr
оценивает справа налево (право-ассоциативной)
мы можем сделать это различие ясным на примере, который использует оператор, для которого ассоциативность имеет значение. Мы могли бы использовать человеческий пример, такой как оператор "ест":
foodChain = (human : (shark : (fish : (algae : []))))
foldl step [] foodChain
where step eater food = eater `eats` food -- note that "eater" is the accumulator and "food" is the element
foldl `eats` [] (human : (shark : (fish : (algae : []))))
== foldl eats (human `eats` shark) (fish : (algae : []))
== foldl eats ((human `eats` shark) `eats` fish) (algae : [])
== foldl eats (((human `eats` shark) `eats` fish) `eats` algae) []
== (((human `eats` shark) `eats` fish) `eats` algae)
семантика этого foldl
is: человек ест акулу, а затем тот же человек, который съел акулу, затем ест рыбу и т. д. Едок-это аккумулятор.
сравните это с:
foldr step [] foodChain
where step food eater = eater `eats` food. -- note that "eater" is the element and "food" is the accumulator
foldr `eats` [] (human : (shark : (fish : (algae : []))))
== foldr eats (human `eats` shark) (fish : (algae : []))))
== foldr eats (human `eats` (shark `eats` (fish)) (algae : [])
== foldr eats (human `eats` (shark `eats` (fish `eats` algae))) []
== (human `eats` (shark `eats` (fish `eats` algae)
семантика этого foldr
is: человек ест акулу, которая уже съела рыбу, которая уже съела некоторые водоросли. Пища-это аккумулятор.
и foldl
и foldr
"слезьте" едоков слева направо, так что это не причина, по которой мы называем foldl как "левая складка". Вместо этого важен порядок оценки.
думал о foldr
очень определение:
-- if the list is empty, the result is the initial value z
foldr f z [] = z
-- if not, apply f to the first element and the result of folding the rest
foldr f z (x:xs) = f x (foldr f z xs)
например foldr (-) 54 [10,11]
должен быть равен (-) 10 (foldr (-) 54 [11])
, т. е. снова расширяется, равной (-) 10 ((-) 11 54)
. Таким образом, внутренняя операция 11 - 54
, то есть, -43; и внешние операции 10 - (-43)
, то есть 10 + 43
, поэтому 53
как вы наблюдаете. Пройдите аналогичные шаги для вашего второго случая, и снова вы увидите, как формируется результат!
foldr
означает сгиб справа, так что foldr (-) 0 [1, 2, 3]
производит (1 - (2 - (3 - 0)))
. В сравнении foldl
производит (((0 - 1) - 2) - 3)
.
когда операторы не коммутативной foldl
и foldr
получите разные результаты.
в вашем случае, первый пример расширяется до (10 - (11 - 54))
что дает 53.
простой способ понять foldr
Это: он заменяет каждый конструктор списка приложением предоставленной функции. Ваш первый пример будет переведен на:
10 - (11 - 54)
от:
10 : (11 : [])
хороший совет, который я получил от Haskell Wikibook, может быть полезен здесь:
как правило, вы должны использовать
foldr
в списках, которые могут быть бесконечными или где складка создает структуру данных, иfoldl'
если известно, что список конечен и сводится к одному значению.foldl
(без галочки) следует использовать редко вообще.
Я всегда думал http://foldr.com чтобы быть забавной иллюстрацией. Вижу лямда-конечная пост.
Я думаю, что реализация map, foldl и foldr простым способом помогает объяснить, как они работают. Проработанные примеры также помогают в нашем понимании.
myMap f [] = []
myMap f (x:xs) = f x : myMap f xs
myFoldL f i [] = i
myFoldL f i (x:xs) = myFoldL f (f i x) xs
> tail [1,2,3,4] ==> [2,3,4]
> last [1,2,3,4] ==> 4
> head [1,2,3,4] ==> 1
> init [1,2,3,4] ==> [1,2,3]
-- where f is a function,
-- acc is an accumulator which is given initially
-- l is a list.
--
myFoldR' f acc [] = acc
myFoldR' f acc l = myFoldR' f (f acc (last l)) (init l)
myFoldR f z [] = z
myFoldR f z (x:xs) = f x (myFoldR f z xs)
> map (\x -> x/2) [12,4,10,6] ==> [6.0,2.0,5.0,3.0]
> myMap (\x -> x/2) [12,4,10,6] ==> [6.0,2.0,5.0,3.0]
> foldl (\x y -> (x+y)/2) 54 [12, 4, 10, 6] ==> 10.125
> myFoldL (\x y -> (x+y)/2) 54 [12, 4, 10, 6] ==> 10.125
foldl from above: Starting accumulator = 54
(12 + 54) / 2 = 33
(4 + 33) / 2 = 18.5
(10 + 18.5) / 2 = 14.25
(6 + 14.25) / 2 = 10.125`
> foldr (++) "5" ["1", "2", "3", "4"] ==> "12345"
> foldl (++) "5" ["1", "2", "3", "4"] ==> “51234"
> foldr (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
> myFoldR' (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
> myFoldR (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
foldr from above: Starting accumulator = 54
(6 + 54) / 2 = 30
(10 + 30) / 2 = 20
(4 + 20) / 2 = 12
(12 + 12) / 2 = 12
Ok, давайте посмотрим на аргументы:
- функция (которая принимает элемент списка и значение (возможный частичный результат) того же типа значения, которое она возвращает);
- спецификация начального результата для специального случая пустого списка
- список;
возвращаемое значение:
- какой-то конечный результат
Он сначала применяет функцию к последнему элементу в списке и пустой список результатов. Затем он повторно применяет функцию с этим результатом и предыдущим элементом и так далее, пока не примет некоторый текущий результат и первый элемент списка для возврата конечного результата.
Fold "складывает" список вокруг начального результата, используя функцию, которая принимает элемент и некоторый предыдущий результат складывания. Он повторяет это для каждого элемента. Итак, foldr делает это, начиная с конца списка или с правой его стороны.
folr f emptyresult [1,2,3,4]
превращается в
f(1, f(2, f(3, f(4, emptyresult) ) ) )
. Теперь просто следуйте скобки в оценке и все.
одна важная вещь, котор нужно заметить что поставленная функция f
должен обрабатывать свое собственное возвращаемое значение в качестве второго аргумента, который подразумевает, что оба должны иметь один и тот же тип.
источник: мой пост где я смотрю на это с императивной неспешной точки зрения javascript, если вы думаете, что это может помочь.