Elixir: Свертка
Функции map и filter обрабатывают списки, сохраняя саму структуру. Иногда нужно избавиться от этой структуры, вычислив какое-то итоговое значение. Простейший пример — сумма всех чисел в списке, либо текст, собранный из списка строк.
В процедурных языках для получения итоговых значений по списку проходят с использованием цикла и промежуточный результат хранят в отдельной переменной — аккумуляторе.
Декларативным же аналогом такого цикла будет операция сворачивания (folding) или, как ещё говорят, получение свёртки (fold). Суть сворачивания списка заключается в последовательном применении некоторой операции к очередному элементу списка и текущему значению аккумулятора acc с целью получить новое значение аккумулятора. Рассмотрим процесс сворачивания списка [1, 2, 3, 4] в сумму чисел. Начальным значением аккумулятора будет 0, а операцией — +. Сложить числа можно как минимум двумя способами:
- двигаясь от первого элемента к последнему, слева-направо:
(((0 + 1) + 2) + 3) + 4- двигаясь от последнего элемента к первому, справа-налево:
1 + (2 + (3 + (4 + 0)))Для операции сложения не имеет значения то, какой из вариантов мы выберем. Потому что операция сложения ассоциативна. Но далеко не все операции таковы: например, при конкатенации строк важно, последнюю мы будем с первой складывать или наоборот.
Enum.reduce([1, 2, 3], 0, &(&1 + &2)) # => 6
Enum.reduce([1, 2, 3], 0, fn x, acc -> acc - x end) # => -6
Enum.reduce(["a", "b", "c"], "", fn x, acc -> "#{acc}#{x}" end) # => "abc"
Enum.reverse(["a", "b", "c"]) |> Enum.reduce("", fn x, acc -> "#{acc}#{x}" end) # => "cba"Часто используют левую свёртку reduce из модуля Enum потому, что она более интуитивна — двигается от первого элемента к последнему. Однако, иногда полезна правая.
Если появилась необходимость использовать правую свертку, то можно обратиться к модулю List и функции foldr, однако стоит понимать, что в общем случае лучше использовать Enum, так как модуль работает со всеми структурами, имплементирующими протокол Enumerable, в то время как List работает только со списками. Про протоколы будет рассказано чуть позже.
List.foldr([1, 2, 3], 0, &(&1 + &2)) # => 6
List.foldr([1, 2, 3], 0, fn x, acc -> acc - x end) # => -6
List.foldl(["a", "b", "c"], "", fn x, acc -> "#{acc}#{x}" end) # => "abc"
List.foldr(["a", "b", "c"], "", fn x, acc -> "#{acc}#{x}" end) # => "cba"Задание
Реализуйте функцию max_delta, которая должна принимать два списка чисел и вычислять максимальную разницу (абсолютное значение разницы) между соответствующими парами элементов. Примеры:
Solution.max_delta([], [])
# => 0
Solution.max_delta([10, -15, 35], [2, -12, 42])
# => 8
Solution.max_delta([-5], [-15])
# => 10Вам пригодятся функции abs и max:
abs(42) # => 42
abs(-13) # => 13
max(1, 5) # => 5Полезное
Elixir: Свертка
Функции map и filter обрабатывают списки, сохраняя саму структуру. Иногда нужно избавиться от этой структуры, вычислив какое-то итоговое значение. Простейший пример — сумма всех чисел в списке, либо текст, собранный из списка строк.
В процедурных языках для получения итоговых значений по списку проходят с использованием цикла и промежуточный результат хранят в отдельной переменной — аккумуляторе.
Декларативным же аналогом такого цикла будет операция сворачивания (folding) или, как ещё говорят, получение свёртки (fold). Суть сворачивания списка заключается в последовательном применении некоторой операции к очередному элементу списка и текущему значению аккумулятора acc с целью получить новое значение аккумулятора. Рассмотрим процесс сворачивания списка [1, 2, 3, 4] в сумму чисел. Начальным значением аккумулятора будет 0, а операцией — +. Сложить числа можно как минимум двумя способами:
- двигаясь от первого элемента к последнему, слева-направо:
(((0 + 1) + 2) + 3) + 4- двигаясь от последнего элемента к первому, справа-налево:
1 + (2 + (3 + (4 + 0)))Для операции сложения не имеет значения то, какой из вариантов мы выберем. Потому что операция сложения ассоциативна. Но далеко не все операции таковы: например, при конкатенации строк важно, последнюю мы будем с первой складывать или наоборот.
Enum.reduce([1, 2, 3], 0, &(&1 + &2)) # => 6
Enum.reduce([1, 2, 3], 0, fn x, acc -> acc - x end) # => -6
Enum.reduce(["a", "b", "c"], "", fn x, acc -> "#{acc}#{x}" end) # => "abc"
Enum.reverse(["a", "b", "c"]) |> Enum.reduce("", fn x, acc -> "#{acc}#{x}" end) # => "cba"Часто используют левую свёртку reduce из модуля Enum потому, что она более интуитивна — двигается от первого элемента к последнему. Однако, иногда полезна правая.
Если появилась необходимость использовать правую свертку, то можно обратиться к модулю List и функции foldr, однако стоит понимать, что в общем случае лучше использовать Enum, так как модуль работает со всеми структурами, имплементирующими протокол Enumerable, в то время как List работает только со списками. Про протоколы будет рассказано чуть позже.
List.foldr([1, 2, 3], 0, &(&1 + &2)) # => 6
List.foldr([1, 2, 3], 0, fn x, acc -> acc - x end) # => -6
List.foldl(["a", "b", "c"], "", fn x, acc -> "#{acc}#{x}" end) # => "abc"
List.foldr(["a", "b", "c"], "", fn x, acc -> "#{acc}#{x}" end) # => "cba"Задание
Реализуйте функцию max_delta, которая должна принимать два списка чисел и вычислять максимальную разницу (абсолютное значение разницы) между соответствующими парами элементов. Примеры:
Solution.max_delta([], [])
# => 0
Solution.max_delta([10, -15, 35], [2, -12, 42])
# => 8
Solution.max_delta([-5], [-15])
# => 10Вам пригодятся функции abs и max:
abs(42) # => 42
abs(-13) # => 13
max(1, 5) # => 5Полезное
Ваше упражнение проверяется по этим тестам
defmodule Test do
use ExUnit.Case
test "max_delta work" do
assert Solution.max_delta([], []) == 0
assert Solution.max_delta([-5], [-15]) == 10
assert Solution.max_delta([0], [42]) == 42
assert Solution.max_delta([10, -15, 35], [2, -12, 42]) == 8
end
endРешение учителя откроется через:
20:00
