Припустімо, ми хочемо підсумувати масив чисел. Існує багато способів досягти цієї мети.
У багатьох мовах цю задачу виражають як цикл зі змінною-акумулятором.
Цю задачу можна записати як рекурсивну функцію. У псевдокоді це могло б мати такий вигляд.
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);
У виразі згортки . - це акумулятор.
Якщо вхідні дані - це якийсь обʼєкт, до якого потрібно звернутися всередині функції згортки, його треба зберегти у змінній.
{"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"]
Ми працюємо вчителями. Наприкінці року ми згенерували числову оцінку для кожного нашого учня. Тепер нам потрібно перетворити це на літерну оцінку та порахувати, скільки учнів отримали кожну літерну оцінку
Функція letter_grade прийматиме числову оцінку як вхідні дані та повертатиме літеру.
Використовуйте ці діапазони:
| Літера | Оцінка |
|---|---|
| A | 90% - 100% |
| B | 80% - 89% |
| C | 70% - 79% |
| D | 60% - 69% |
| F | 0% - 59% |
Приклад:
75 | letter_grade # => "C"
Функція count_letter_grades прийматиме обʼєкт, який зіставляє імена учнів з їхніми оцінками.
Вихідні дані будуть обʼєктом, який зіставляє кожну літерну оцінку з кількістю учнів, які мають цю оцінку.
Приклад:
{"Joe": 78, "Jane": 93, "Richard": 72} | count_letter_grades
# => {"A": 1, "B": 0, "C": 2, "D": 0, "F": 0}
Існує кілька різних способів розвʼязати це.
Використовуйте фільтр reduce для практики.
Зареєструйтеся на Exercism, щоб вивчати й опановувати jq, а також 12 концепцій75 вправ та справжнє наставництво від людей, і все це безкоштовно.