Из Википедии, бесплатной энциклопедии
  (Перенаправлено из Reduce (функция высшего порядка) )
Перейти к навигации Перейти к поиску

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

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

Как структурные преобразования [ править ]

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

Right-fold-transformation.png

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

Left-fold-transformation.png

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

 карта  е  =  foldr  (( : )  .  е )  []

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

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

В списках [ править ]

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

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

На практике удобно и естественно иметь начальное значение, которое в случае правой складки используется, когда кто-то достигает конца списка, а в случае левой складки - это то, что изначально объединяется с первым элементом списка. список. В приведенном выше примере значение 0 ( аддитивная идентичность ) будет выбрано в качестве начального значения 1 + (2 + (3 + (4 + (5 + 0))))для правой и ((((0 + 1) + 2) + 3) + 4) + 5левой складок. Для умножения, первоначальный выбор-не будет работать: 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 не   является строгим .

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

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

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

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

Реализация [ править ]

Линейные складки [ править ]

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

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

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

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

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

Древовидные складки [ править ]

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

foldt  f  z  []  =  z foldt  f  z  [ x ]  =  f  x  z foldt  f  z  xs  =  foldt  f  z  ( пары  f  xs ) foldi  f  z  []  =  z foldi  f  z  ( x : xs )  =  f  x  ( foldi  f  z  ( пары  f  xs )) пары  f  ( x : y : t )  =  f  x  y  :  пары  f  t пары  _  t  =  t

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

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

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

Рекомендации по порядку оценки [ править ]

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

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

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

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

Примеры [ править ]

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

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

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

простые числа  =  2  :  _Y  (( 3  : )  .  минус  [ 5 , 7 .. ]  .  Фелди  ( \ ( х : XS )  YS  ->  х  :  объединение  хз  YS )  []  .  карта  ( \ р ->  [ р * р ,  p * p + 2 * p .. ])) _Y  g  =  g ( _Y  g )  - = g. грамм . грамм . грамм . ...

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

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

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

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

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

head  =  foldr  ( \ x  r  ->  x )  ( ошибка  «head: пустой список» ) last  =  foldl  ( \ a  x  ->  x )  ( ошибка  «last: пустой список» )

На разных языках [ править ]

Универсальность [ править ]

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

 g  []  =  v  g  ( x : xs )  =  f  x  ( g  xs )

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

 g  =  foldr  f  v

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

 y  f  =  foldr  ( \ _  ->  f )  undefined  ( повторение  не определено )

См. Также [ править ]

  • Агрегатная функция
  • Итерированная бинарная операция
  • Катаморфизм , обобщение складки
  • Гомоморфизм
  • Карта (функция высшего порядка)
  • Сумма префикса
  • Рекурсивный тип данных
  • Оператор редукции
  • Структурная рекурсия

Ссылки [ править ]

  1. ^ Ричард Берд, «Жемчужины проектирования функциональных алгоритмов», Cambridge University Press 2010, ISBN  978-0-521-51338-8 , стр. 42
  2. ^ "fold - язык программирования Kotlin" . Котлин . Jetbrains . Проверено 29 марта 2019 .
  3. ^ "reduce - язык программирования Kotlin" . Котлин . Jetbrains . Проверено 29 марта 2019 .
  4. ^ "Результат - язык программирования Котлин" . Котлин . Jetbrains . Проверено 29 марта 2019 .
  5. ^ "База" . Джейн Стрит Кэпитал . Проверено 26 февраля 2019 года .
  6. ^ Для справкиfunctools.reduce:import functools
    Для справкиreduce:from functools import reduce
  7. ^ Одерски, Martin (2008-01-05). «Re: Blog: Мой вердикт языку Scala» . Группа новостейcomp.scala.lang . Проверено 14 октября 2013 года .
  8. ^ Стерлинг, Николас. «Интуитивно понятный оператор Scala /: (foldLeft)» . Проверено 24 июня +2016 .
  9. ^ «Fold API - Стандартная библиотека Scala» . www.scala-lang.org . Проверено 10 апреля 2018 .
  10. ^ Хаттон, Грэм. «Учебник по универсальности и выразительности складки» (PDF) . Журнал функционального программирования (9 (4)): 355–372 . Проверено 26 марта 2009 года .
  11. Папа, Берни. «Получение исправления из правильной складки» (PDF) . Монада. Чтение (6): 5–16 . Проверено 1 мая 2011 года .

Внешние ссылки [ править ]

  • «Функции высшего порядка - отображение, сворачивание и фильтрация»
  • «Блок 6: Функции складывания высшего порядка»
  • "Сложить в Tcl"
  • «Построение гомоморфизма списков из левых и правых складок»
  • "Волшебный фолдр"