Уявімо, що ми хочемо знайти суму масиву чисел. Є багато способів досягти цієї мети.
У багатьох мовах цю задачу записують як цикл зі змінною-акумулятором.
Цю задачу можна записати як рекурсивну функцію. У псевдокоді це могло б мати такий вигляд.
function Add(X, Sum=0):
if X is empty then
return Sum
else
return Add(rest(X), Sum + first(X))
end
end
Цей метод поділу задачі на менші частини можна також описати як «зведення до базового випадку».
Reduce - це спосіб поєднати всі елементи структури даних в одне значення. Цей процес перебирає структуру даних, застосовуючи функцію до кожного елемента, щоб оновити накопичений результат.
У jq цей процес реалізовано у фільтрі reduce.
В інших мовах його можуть називати «fold», «fold-left», «inject» або «aggregate».
Вираз reduce у jq має такий вигляд.
reduce STREAM_EXPRESSION as $var (INITIAL_VALUE; UPDATE_EXPRESSION)
STREAM_EXPRESSION - це потік елементів, кожен з яких по черзі зберігається у змінній $var.
.[]: $myArray | .[].INITIAL_VALUE - це початкове значення накопиченого результату (який називають «акумулятором»).UPDATE_EXPRESSION поєднує («згортає») поточне значення ($var) в акумулятор.
. - це значення акумулятора.reduce.Розгляньмо приклад: додавання чисел у масиві.
Фільтр add робить саме це, але ми побачимо, як його реалізувати.
Якщо взяти [10, 20, 30, 40] як вхідні дані та нуль як початковий стан, ось що відбувається на кожному кроці.
| # | стан | елемент | редюсер | результат |
|---|---|---|---|---|
| 1 | 0 | 10 | 0 + 10 | 10 |
| 2 | 10 | 20 | 10 + 20 | 30 |
| 3 | 30 | 30 | 30 + 30 | 60 |
| 4 | 60 | 40 | 60 + 40 | 100 |
У синтаксисі jq це має такий вигляд.
0 + 10 | . + 20 | . + 30 | . + 40
Це можна виразити за допомогою фільтра reduce.
[10, 20, 30, 40] | reduce .[] as $n (0; . + $n) # => 100
Вбудований add насправді реалізовано через reduce, але він використовує «null» як початковий стан (до null можна додати будь-який тип даних).
def add: reduce .[] as $x (null; . + $x);
У виразі reduce . - це акумулятор.
Якщо вхідні дані - це обʼєкт, до якого потрібно звернутися всередині функції-редюсера, його слід зберегти у змінній.
{"apple": 10, "banana": 16, "carrot": 4}
| . as $obj
| reduce (keys | .[]) as $key (0; . + $obj[$key]) # => 30
Акумулятор може мати будь-який тип даних. Наприклад, нам може знадобитися обернути масив у зворотному порядку.
["A", "B", "C", "D"]
| reduce .[] as $elem ([]; [$elem] + .) # => ["D", "C", "B", "A"]