# Макрос FOLD<N>
Макрос FOLD<N>
позволяет реализовать операции над списком значений, такие как суммирование (sum
), фильтрация (filter
), преобразование (map
), агрегация элементов (zip
), проверка наличия (exists
) и т. п. Он работает аналогично функции свертки fold
или reduce
в других языках программирования.
FOLD<N>(list, start, foldFunc)
Параметр | Описание |
---|---|
N | Максимальное количество итераций, не более 1000 |
list | Список значений |
start | Начальное значение |
foldFunc | Cворачиваемая функция |
Сворачиваемая функция получает на вход два параметра: промежуточный результат и очередной элемент списка. Макрос FOLD<N>(list, start, foldFunc)
означает:
- выполнить не более
N
итераций; - на каждой итерации: взять результат предыдущей итерации (на первой итерации взять значение
start
) и следующий элемент спискаlist
, применить к этой паре функциюfoldFunc
; - вернуть итоговый результат.
Величина N
должна быть известна заранее. Если в списке оказалось больше элементов, чем указано в FOLD, выполнение скрипта завершается ошибкой.
Сложность FOLD<N>
соответствует сложности сворачиваемой функции, умноженной на N
, плюс накладные расходы.
Макрос FOLD<N>
— синтаксический сахар, он «раскрывается» на этапе компиляции кода. Поэтому, в частности, размер скрипта растет линейно с ростом N
.
# Суммирование
func sum(accum: Int, next: Int) = accum + next
let arr = [1,2,3,4,5]
FOLD<5>(arr, 0, sum) # Результат: 15
Выражение
FOLD<5>(arr, 0, sum)
после компиляции и декомпиляции будет выглядеть так:
let $l = arr let $s = size($l) let $acc0 = 0 func 1 ($a,$i) = if (($i >= $s)) then $a else sum($a, $l[$i]) func 2 ($a,$i) = if (($i >= $s)) then $a else throw("List size exceeds 5") 2(1(1(1(1(1($acc0, 0), 1), 2), 3), 4), 5)
Такой код вы можете увидеть в Waves Explorer. Пример
# Произведение
func mult(accum: Int, next: Int) = accum * next
let arr = [1,2,3,4,5]
FOLD<5>(arr, 1, mult) # Результат: 120
# Фильтрация
Следующий код формирует список только из четных чисел исходного списка.
func filterEven(accum: List[Int], next: Int) =
if (next % 2 == 0) then accum :+ next else accum
let arr = [1,2,3,4,5]
FOLD<5>(arr, [], filterEven) # Результат: [2, 4]
# Преобразование
Следующий код инвертирует список, уменьшая каждый элемент на единицу:
func map(accum: List[Int], next: Int) = (next - 1) :: accum
let arr = [1, 2, 3, 4, 5]
FOLD<5>(arr, [], map) # Результат: [4, 3, 2, 1, 0]
# Zip
Следующий код сводит два списка в один: создает структуру из каждых двух элементов с одинаковым индексом и формирует список, состоящий из таких структур. Аналогичным образом можно соединять элементы в кортежи.
let keys = ["key1", "key2", "key3"]
let values = ["value1", "value2", "value3"]
func addStringEntry(accum: (List[StringEntry], Int), nextValue: String) =
{
let (result, j) = accum
(result :+ StringEntry(keys[j], nextValue), j + 1)
}
let r = FOLD<10>(values, ([], 0), addStringEntry)
r._1
В качестве аккумулятора здесь используется кортеж, который содержит два элемента:
- формируемый список структур StringEntry,
- текущий индекс во всех списках.
Сворачиваемая функция addStringEntry
добавляет в список структур очередную структуру StringEntry
, которая содержит ключ из списка keys
и значение из списка values
. Кроме того, сворачиваемая функция увеличивает индекс (второй элемент кортежа-аккумулятора) на единицу.
Результат:
[
StringEntry(
key = "key1"
value = "value1"
),
StringEntry(
key = "key2"
value = "value2"
),
StringEntry(
key = "key3"
value = "value3"
)
]