Свернуть (функция высшего порядка)

редактировать

В функциональном программировании, свернуть (также называется уменьшить, накопить, агрегировать, сжать или ввести ) относится к семейству более высокого порядка функции, которые анализируют рекурсивную структуру данных и посредством использования заданной операции комбинирования рекомбинируют результаты рекурсивно обработки его составных частей, создавая возвращаемое значение. Как правило, свертка представлена ​​объединяющей функцией , верхним узлом структуры данных и, возможно, некоторыми значениями по умолчанию, которые будут использоваться при определенных условиях. Затем свертка переходит к объединению элементов иерархии структуры данных, используя функцию систематическим образом.

Сворачивания в некотором смысле двойственны по отношению к развёртыванию, которые принимают начальное значение и применяют функцию corecursively, чтобы решить, как постепенно построить структуру данных corecursive, тогда как fold рекурсивно разрушает эту структуру, заменяя ее результатами применения объединяющей функции в каждом узле на его терминальных значениях и рекурсивными результатами (катаморфизм, по сравнению с анаморфизмом разворачивается).

Содержание
  • 1 Как структурные преобразования
  • 2 В списках
    • 2.1 Линейные и древовидные складки
    • 2.2 Специальные складки для непустых списков
    • 2.3 Реализация
      • 2.3.1 Линейные складки
      • 2.3.2 Древовидные складки
      • 2.3.3 Сгибы для непустых списков
    • 2.4 Рекомендации по порядку оценки
    • 2.5 Примеры
  • 3 На разных языках
  • 4 Универсальность
  • 5 См. Также
  • 6 Ссылки
  • 7 Внешние ссылки
Как структурные преобразования

Складки можно рассматривать как последовательную замену структурных компонентов структуры данных функциями и значениями. Списки, например, построены на многих функциональных языках из двух примитивов: любой список является либо пустым списком, обычно называемым nil (), либо создается с помощью префикса элемента перед другим. list, создавая то, что называется cons node (Cons (X1, Cons (X2, Cons (... (Cons (Xn, nil)))))), полученный в результате применения функции cons(записанной как двоеточие (:)в Haskell ). Можно рассматривать свертку списков как замену нуля в конце списка определенным значением и замену каждого минуса определенной функцией. Эти замены можно рассматривать как диаграмму:

Right-fold-transformation.png

Есть другой способ выполнить структурное преобразование согласованным образом, с изменением порядка двух связей каждого узла при подаче в функцию объединения:

Left-fold-transformation.png

Эти изображения иллюстрируют правый и левый сгиб списка визуально. Они также подчеркивают тот факт, что foldr (:)является функцией идентификации в списках (неглубокая копия на языке Lisp ), поскольку вместо cons на consи nil с nilне изменит результат. Диаграмма левого сгиба предлагает простой способ перевернуть список, foldl (flip (:)). Обратите внимание, что параметры cons должны быть перевернуты, потому что добавляемый элемент теперь является правым параметром функции объединения. Другой простой результат, который можно увидеть с этой точки зрения, - это написать функцию карты более высокого порядка в терминах foldr, составив функцию для воздействия на элементы с cons, как:

map f = foldr ((:). F)

где точка (.) - это оператор, обозначающий композицию функции.

Такой взгляд на вещи обеспечивает простой путь к разработке функций типа свертки для других алгебраических типов данных и структур, таких как различные виды деревьев. Один пишет функцию, которая рекурсивно заменяет конструкторы типа данных предоставленными функциями, а любые постоянные значения типа предоставленными значениями. Такая функция обычно называется катаморфизмом.

в списках

Сворачивание списка [1,2,3,4,5]с помощью оператора сложения приведет к 15, сумме элементов списка [1,2,3,4,5]. В грубом приближении можно представить себе эту складку как замену запятых в списке операцией +, что дает 1 + 2 + 3 + 4 + 5.

В приведенном выше примере + - это ассоциативная операция, поэтому конечный результат будет одинаковым независимо от скобок, хотя конкретный способ его вычисления будет другим. В общем случае неассоциативных бинарных функций порядок, в котором объединяются элементы, может влиять на значение окончательного результата. В списках есть два очевидных способа сделать это: либо комбинируя первый элемент с результатом рекурсивного комбинирования остальных (так называемый правый сгиб ), либо комбинируя результат рекурсивного комбинирования всех элементов. но последний, с последним элементом (называемым левой складкой ). В терминологии Haskell или Prolog это соответствует тому, что бинарный оператор является либо правоассоциативным, либо левоассоциативным. При правой складке сумма будет заключена в круглые скобки как 1 + (2 + (3 + (4 + 5))), тогда как при левой складке она будет заключена в скобки как (((1 + 2) + 3) + 4) + 5.

На практике удобно и естественно иметь начальное значение, которое в случае правого сгиба используется при достижении конца списка, а в случае левой складки - это то, что изначально совмещено с первым элементом списка. В приведенном выше примере значение 0 (аддитивный идентификатор ) будет выбрано в качестве начального значения, что даст 1 + (2 + (3 + (4 + (5 + 0))))для правого сгиба и ((((0 + 1) + 2) + 3) + 4) + 5для левого сгиба. Для умножения начальный выбор 0 не сработает: 0 * 1 * 2 * 3 * 4 * 5 = 0. элемент идентичности для умножения равен 1. Это даст нам результат 1 * 1 * 2 * 3 * 4 * 5 = 120 = 5!.

Линейные складки против древовидных

Использование начального значения необходимо, когда функция комбинирования f асимметрична по своим типам (например, a → b → b), т.е. когда тип ее результата отличается от типа элементов списка. Затем необходимо использовать начальное значение того же типа, что и результат f, чтобы была возможна линейная цепочка приложений. Будет ли он ориентирован влево или вправо, будет определяться типами, ожидаемыми от его аргументов функцией комбинирования. Если это второй аргумент, который должен быть того же типа, что и результат, тогда f можно рассматривать как двоичную операцию, которая связывает справа, и наоборот.

Когда функция - это магма, т.е. симметричная по своим типам (a → a → a), и тип результата такой же, как у элементов списка ' типа, круглые скобки могут быть размещены произвольно, создавая таким образом дерево вложенных подвыражений, например, ((1 + 2) + (3 + 4)) + 5. Если двоичная операция f ассоциативна, это значение будет четко определено, то есть одинаковым для любых скобок, хотя рабочие детали того, как она вычисляется, будут другими. Это может существенно повлиять на эффективность, если f нестрогий.

, тогда как линейные складки ориентированы на узлы и работают согласованно для каждого узла из list, древовидные свертки ориентированы на весь список и работают согласованно для всех групп узлов.

Специальные свертки для непустых списков

Часто требуется выбрать элемент идентичности операции f в качестве начального значения z. Когда исходное значение не кажется подходящим, например, когда кто-то хочет свернуть функцию, которая вычисляет максимум из двух ее параметров по непустому списку, чтобы получить максимальный элемент списка, существуют варианты foldrи foldl, которые используют последний и первый элементы списка соответственно в качестве начального значения. В Haskell и некоторых других языках они называются foldr1и foldl1, где 1 ссылается на автоматическое предоставление начального элемента и тот факт, что списки, к которым они применяются, должны иметь хотя бы один элемент.

В этих свертках используется симметричная по типу двоичная операция: типы ее аргументов и ее результат должны быть одинаковыми. Ричард Берд в своей книге 2010 года предлагает "общую функцию сворачивания непустых списков" foldrn, которая преобразует свой последний элемент, применяя к нему дополнительную функцию аргумента, в значение типа результата перед запуском сворачивание самого себя и, таким образом, может использовать двоичную операцию с асимметричным типом, такую ​​как обычный foldr, для получения результата, тип которого отличается от типа элементов списка.

Реализация

Линейные свертки

Используя Haskell в качестве примера, foldlи foldrможно сформулировать в виде нескольких уравнений.

foldl :: (b ->a ->b) ->b ->[a] ->b foldl fz = z foldl fz (x: xs) = foldl f (fzx) xs

Если список пусто, результатом будет начальное значение. Если нет, сверните конец списка, используя в качестве нового начального значения результат применения f к старому начальному значению и первому элементу.

foldr :: (a ->b ->b) ->b ->[a] ->b foldr fz = z foldr fz (x: xs) = fx (foldr fz xs)

Если список пусто, результатом будет начальное значение z. Если нет, примените f к первому элементу и результату свертывания остальных.

Древовидные складки

Списки можно складывать древовидным способом, как для конечных, так и для неопределенно определенных списков:

foldt fz = z foldt fz [x] = fxz foldt fz xs = foldt fz (пары f xs) foldi fz = z foldi fz (x: xs) = fx (foldi fz (пары f xs)) пары f (x: y: t) = fxy: пары ft пары _ t = t

В случае функции foldi, чтобы избежать неконтролируемого вычисления в неопределенно определенных списках, функция fне всегда должна требовать значение своего второго аргумента, по крайней мере, не все этого или не сразу (см. пример ниже).

Сворачивание непустых списков

foldl1 f [x] = x foldl1 f (x: y: xs) = foldl1 f (fxy: xs) foldr1 f [x] = x foldr1 f (x : xs) = fx (foldr1 f xs) foldt1 f [x] = x foldt1 f (x: y: xs) = foldt1 f (fxy: пары f xs) foldi1 f [x] = x foldi1 f (x: xs) = fx (foldi1 f (pair f xs))

Рекомендации по порядку оценки

При ленивом или нестрогом вычислении, foldrнемедленно вернет применение f в начало списка и рекурсивный случай сворачивания по остальной части списка. Таким образом, если f может произвести некоторую часть своего результата без ссылки на рекурсивный случай в его «правом», то есть во втором аргументе, а остальная часть результата никогда не требуется, то рекурсия остановится (например, head == foldr (\ a b->a) (ошибка «пустой список»)). Это позволяет правым сверткам работать с бесконечными списками. Напротив, foldlнемедленно вызовет себя с новыми параметрами, пока не достигнет конца списка. Эту хвостовую рекурсию можно эффективно скомпилировать как цикл, но она вообще не может иметь дело с бесконечными списками - она ​​будет рекурсивно повторяться бесконечно в бесконечном цикле.

. выражение фактически построено foldlвложенными углубляющимися влево f-приложениями, которые затем представляются вызывающей стороне для оценки. Если бы функция fсначала ссылалась здесь на свой второй аргумент и могла бы произвести некоторую часть своего результата без ссылки на рекурсивный случай (здесь, слева, т.е. в своем первом аргументе), тогда рекурсия остановится. Это означает, что, хотя foldrвыполняет рекурсию справа, это позволяет функции ленивого комбинирования проверять элементы списка слева; и наоборот, в то время как foldlрекурсирует слева, это позволяет функции ленивого комбинирования проверять элементы списка справа, если она того пожелает (например, last == foldl (\ a b->б) (ошибка «пустой список»)).

Обращение списка также является хвостовой рекурсией (это можно реализовать с помощью rev = foldl (\ ys x ->x: ys)). В конечных списках это означает, что левый и обратный складки могут быть составлены для выполнения правого сворачивания хвостовой рекурсией (см. 1 +>(2 +>(3+>0)) == (( 0 <+3)<+2)<+1) с модификацией функции f, так что она меняет порядок своих аргументов (т. Е. foldr fz == foldl (flip f) z. Foldl (flip (:))), рекурсивно построение представления выражения, которое будет строиться при свертывании вправо. Посторонняя структура промежуточного списка может быть устранена с помощью метода передачи стиля, foldr fz xs = = foldl (\ k x->k. fx) id xs z; аналогично, foldl fz xs == foldr (\ x k->k. flip fx) id xs z(flipтребуется только в таких языках, как Haskell, с его перевернутым порядком аргументов для функции объединения foldl, в отличие, например, от Scheme, где один и тот же порядок аргументов используется для объединения функций для обоих foldlи foldr).

Другой технический момент заключается в том, что в случае левых складок с помощью ленивого вычисления n, новый начальный параметр не оценивается до выполнения рекурсивного вызова. Это может привести к переполнению стека, когда кто-то достигает конца списка и пытается оценить результирующее потенциально гигантское выражение. По этой причине такие языки часто предоставляют более строгий вариант сворачивания влево, который заставляет вычислять начальный параметр перед выполнением рекурсивного вызова. В Haskell это функция foldl '(обратите внимание на апостроф, произносится как «prime») в библиотеке Data.List(нужно помнить о том, что принуждение значения построенный с помощью ленивого конструктора данных, сам по себе не заставит его составляющие автоматически). В сочетании с хвостовой рекурсией такие складки приближаются к эффективности циклов, обеспечивая постоянную пространственную операцию, когда ленивая оценка конечного результата невозможна или нежелательна.

Примеры

Используя интерпретатор Haskell, структурные преобразования, выполняемые функциями свертки, можно проиллюстрировать построением строки:

λ>foldr (\ xy ->concat ["(", x, "+", y, ")"]) "0" (отображение карты [1..13]) "(1+ (2+ (3+ (4+ (5+ (6 + (7+ (8+ (9+ (10+ (11+ (12+ (13 + 0)))))))))))) "λ>foldl (\ xy ->concat [" (", x, "+", y, ")"]) "0" (показать карту [1..13]) "(((((((((((((0 + 1) +2) +3)) +4) +5) +6) +7) +8) +9) +10) +11) +12) +13) "λ>foldt (\ xy ->concat [" (", x," + ", y,") "])" 0 "(показать карту [1..13])" (((((1 + 2) + (3 + 4)) + ((5 + 6) + (7+ 8))) + (((9 + 10) + (11 + 12)) + 13)) + 0) "λ>foldi (\ xy ->concat [" (", x," + ", y,") "])" 0 "(показать карту [1..13])" (1 + ((2 + 3) + (((4 + 5) + (6 + 7)) + ((((8 + 9)) + (10 + 11)) + (12 + 13)) + 0)))) «

Демонстрируется бесконечное древовидное сворачивание, например, в рекурсивном производстве простых чисел неограниченным решетом Эратосфена в Haskell :

primes = 2: _Y ((3 :). minus [5,7..]. foldi (\ (x: xs) ys ->x: union xs ys). map (\ p ->[p * p, p * p + 2 * p..])) _Y g = g (_Y g) - = g. грамм. грамм. грамм....

, где функция union работает с упорядоченными списками локально, чтобы эффективно создавать их set union и минус их разница между наборами.

Для конечных списков, например, сортировка слиянием (и ее разновидность удаления дубликатов, nubsort) может быть легко определена с использованием древовидной свертки как

mergesort xs = foldt merge [[x] | x <- xs] nubsort xs = foldt union [[x] | x <- xs]

с функцией merge вариант с сохранением дубликатов union.

Функции headи lastмогли были определены путем сворачивания как

head = foldr (\ xr ->x) (ошибка "head: Empty list") last = foldl (\ ax ->x) (error "last: Empty list")
На разных языках
ЯзыкСгиб слеваСгиб справаСгиб слева без начального значенияСгиб справа без начального значенияРазвернутьПримечания
APL func⍨ / initval, vectorfunc / vector, initvalfunc⍨ / ⌽vectorfunc / vector
C# 3.0 ienum.Aggregate (initval, func)ienum.Reverse ().Aggregate (initval, func)ienum.Aggregate (func)ienum.Reverse ().Aggregate (func)Aggregate - это метод расширения . ienum - это IEnumerable . Аналогично во всех языках.NET
C ++ std :: accumulate (begin, end, initval, func)std :: accumulate (rbegin, rend, initval, func)в заголовке . begin, end, rbegin, rend - итераторы. func может быть указателем на функцию или объектом функции
C ++ 17 (initval op... op pack)( pack op... op initval)(... op pack)(pack op...)Выражение свертывания (только для шаблонов функций с переменным числом аргументов): op - бинарный оператор (оба ops должны быть таким же, например, (std :: cout <<... << args)), pack - это нерасширенный пакет параметров.
CFML obj.reduce (func, initial)obj.reduce (func)Где funcполучает в качестве аргументов результат предыдущей операции (или начальное значениена первой итерации); текущий элемент; индекс или ключ текущего элемента; и ссылку на obj
Clojure (уменьшить список функций initval)(уменьшить initval функции (обратный список '))(уменьшить список функций)(уменьшить func » (обратный список))См. также clojure.core.reducers / fold
Common Lisp (сокращение списка функций: начальное значение initval)(сокращение списка функций: from-end t : начальное значение initval)(уменьшить список функций)(уменьшить список функций: from-end t)
Curl {{TreeNode.default treeNode...}.to-Iterator}{{TreeNode.default treeNode...}.reverse}.to-Iterator}{для {treeNode.to-Iterator} do}{для {{treeNode.reverse }.to-Iterator} do}также DefaultListModel и HashTable реализуют to-Iterator
D reduce! Func (initval, list)reduce! Func (initval, list. reverse)reduce! func (list)reduce! func (list.reverse)в модуле std.algorithm
Elm List.foldl (Fun, Accumulator, List)List.foldr (Fun, Accumulator, List)См. Также List API [1]
Erlang списки: foldl (Fun, Accumu lator, List)списки: foldr (Fun, Accumulator, List)
F# Seq / List.fold func initval listList.foldBack func list initvalSeq / List.reduce func listList.reduceBack func listSeq.unfold func initval
Gosu Iterable.fold (f (agg, e)) Iterable.reduce (init, f (agg, e)) Iterable.partition (f (e))

Все это методы расширения в интерфейсе Iterable Java, массивы также поддерживаются
Groovy list.inject (initval, func)list.reverse ().inject (initval, func)list.inject (func)list.reverse ().inject (func)
Haskell foldl func initval listfoldr func initval listfoldl1 func listfoldr1 func listdeployr func initvalДля foldl функция сворачивания принимает аргументы в порядке, обратном таковому для foldr.
Haxe Lambda.fold (iterable, func, initval)
J глагол ~ / |. initval, массивкоманда / массив, initvalкоманда ~ / |. arrayverb / arrayu / y применяет диаду u между элементами y. «J Dictionary: Insert»
Java 8+stream.reduce (initval, func)stream.reduce (func)
JavaScript 1.8. ECMAScript 5array.reduce (func, initval)array.reduceRight (func, initval)array.reduce (func)array.reduceRight (func)
Джулия foldl (op, itr; [init])foldr (op, itr; [init])foldl (op, itr)foldr (op, itr)
Kotlin Iterable.fold (initval, func)Iterable.foldRight (initval, func)Iterable.reduce (func)Iterable.reduceRight (func)Другие коллекции также поддерживают foldи reduce. Также существует Result.fold (onSuccess, onFailure), который сокращает Result(успех или неудача) до типа возврата onSuccessи onFailure.
LFE (списки: список накоплений foldl func)(списки: список накоплений foldr func)
Logtalk fold_left (Закрытие, Начальное, Список, Результат)fold_right (Closure, Initial, List, Result)Мета-предикаты, предоставляемые мета-объектом стандартной библиотеки. Также могут использоваться сокращения foldl и foldr.
Maple foldl (func, initval, sequence)foldr (func, initval, sequence)
Mathematica Fold [func, initval, list]Fold [func, initval, Обратный [список]]Fold [func, list]Fold [func, Reverse [list]]NestWhileList [func, initval, predicate]Foldбез начального значения поддерживается в версиях 10.0 и выше.
MATLAB fold (@func, list, defaultVal)fold (@func, flip (list), defaultVal)fold (@func, list)fold (@func, flip (список))Требуется Symbolic Math Toolbox, поддерживаемый с R2016b.
Maxima lreduce (func, list, initval)rreduce (func, list, initval)lreduce (func, list)rreduce (func, list)
fold_left func initval list. vector :: fold_left func initval vectorfold_right func initval list. vector :: fold_right func initval vectorПредоставляемая функция принимает свои аргументы в виде кортежа.
OCaml List.fold_left func initval list. Array.fold_left func initval arrayList.fold_right func list initval. Array.fold_right func array initvalBase.Sequence.unfold ~ init ~ f
Oz {FoldL List Func InitVal}{FoldR List Func InitVal}
PARI / GP fold (f, A)
Perl уменьшить инициализацию блока, списокуменьшить список блоковв List :: Utilmodule
PHP array_reduce (array, func, initval)array_reduce (array_reverse (массив), func, initval)array_reduce (array, func)array_reduce (array_reverse (array), func)Когда initval не указан, используется NULL, поэтому это не истинный foldl1. До PHP 5.3 initval может быть только целым числом. "func" - это обратный вызов . Попробуйте array_reduce в сети.
Python 2.xreduce (func, list, initval)reduce (lambda x, y: func (y, x), reverse (list), initval)reduce ( func, list)reduce (lambda x, y: func (y, x), reversed (list))
Python 3.xfunctools.reduce (func, list, initval)functools.reduce (lambda x, y: func (y, x), reversed (list), initval)functools.reduce (func, list)functools.reduce (lambda x, y: func ( y, x), обратный (список))В модуле functools.
R Reduce (func, list, initval)Reduce (func, list, initval, right = TRUE)Reduce ( func, list)Reduce (func, list, right = TRUE)R поддерживает свертывание вправо и влево или вправо с или без начального значения через аргументы right и init функции Reduce.
Ruby enum.inject (initval, block). enum.reduce (initval, block)enum.reverse_each.inject (initval, block). enum.reverse_each.reduce (initval, block)enum.inject (block). enum.reduce (block)enum.reverse_each.inject (block). enum.reverse_each.reduce (block)В Ruby 1.8.7+ также можно передавать символ, представляющий функцию, вместо блока.. enum - это перечисление. Обратите внимание, что эти реализации правильных складок неверны для некоммутативного блока (также начальное значение помещается не на той стороне).
Rust iterator.fold (initval, func)iterator.rev (). Fold (initval, func)
Scala list.foldLeft (initval) (func). ( initval /: list) (func)list.foldRight (initval) (func). (list: \ initval) (func)list.reduceLeft (func)list.reduceRight (func)Синтаксис символической складки в Scala должен был напоминать дерево с наклоном влево или вправо, обычно используемое для объяснения операции складывания, но с тех пор был переинтерпретирован как иллюстрация опрокидывающегося домино. Двоеточие происходит от общего синтаксического механизма Scala, посредством которого очевидный инфиксный оператор вызывается как метод для левого операнда с правым операндом, переданным в качестве аргумента, или наоборот, если последний символ оператора является двоеточием, здесь применяется симметрично.

Scala также имеет древовидные складки с использованием метода list.fold (z) (op).

Scheme RRS(fold-left func initval list). (vector -fold func initval vector)(fold-right func initval list). (vector-fold-right func initval vector)(reduce-left func defaultval list)(reduce-right func defaultval list)srfi / 1 srfi / 43
Smalltalk aCollection inject: aValue into: aBlockaCollection reduce: aBlockANSI Smalltalk не определяет #reduce: но многие реализации это делают.
Стандартный ML foldl func initval list. Array.foldl func initval arrayfoldr func initval list. Array.foldr func initval arrayПредоставляемая функция принимает свои аргументы в виде кортежа. Для foldl функция сворачивания принимает аргументы в том же порядке, что и для foldr.
Swift array.reduce (initval, func). reduce (sequence, initval, func)array.reverse ().reduce (initval, func)
XPath 3.1 array: fold-left (

$ array как array (*),

$ zero as item () *,

$ f как функция (

item () *,

item () *

) как item () *

) как item () *

. fold-left (

$ seq as item () *,

$ zero как item () *,

$ f как функция (

item () *,

item ()

) как item () *

) как item () *

.

.

.

.

.

массив: fold-right (

$ массив как массив (*),

$ ноль как элемент () *,

$ f какфункция (

item () *,

item () *

) как item () *

) как item () *

. fold-right (

$ seq as item () *,

$ zero as item () *,

$ f как функция (

item (),

item () *

) как item () *

) как item () *

.

.

.

.

.

В XPath 3.1 по историческим причинам массив и последовательностьнесовместимы, поэтому необходимы отдельные функции foldдля массиваи для последовательности

.. Разница в сигнатурах из-за того, что значение массива элемент может быть последовательностью, в то время как XPath не имеет последовательностьиз последовательностьs

.

.

.

.

.

.

Xtend iterable.fold (initval, [func])iterable.reduce [func]
Универсальность

Fold - это полиморфная функция. Для любого g, имеющего определение

g = vg (x: xs) = fx (g xs)

, тогда g можно выразить как

g = foldr fv

Кроме того, фиксированная точка комбинатор может быть реализован через свертку, доказывая, что итерации можно свести к сверткам:

yf = foldr (\ _ ->f) undefined (repeat undefined)
См. также
Ссылки
Внешние ссылки
Последняя правка сделана 2021-05-20 09:59:03
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте