Как суммировать значения в массиве карт в jq?
Для потока JSON следующего вида:
{ "a": 10, "b": 11 } { "a": 20, "b": 21 } { "a": 30, "b": 31 }
Я хотел бы суммировать значения в каждом из объектов и выводить один объект, а именно:
{ "a": 60, "b": 63 }
Я предполагаю, что это, вероятно, потребует сглаживания вышеуказанного списка объектов в массив пар [name, value]
, а затем суммирование значений с помощью reduce
, но документация синтаксиса для использования reduce
горька.
Ответы
Ответ 1
Поскольку это отдельные входы, вам придется их развернуть (флаг -s
). Затем с этим вам нужно будет сделать массу манипуляций.
- Каждый из объектов должен быть сопоставлен с парами ключ/значение.
- Сгладить пары в один массив
- Группируйте пары по клавише
- Отметьте каждую группу, скопив значения одной паре ключ/значение.
- Сопоставить пары обратно объекту
map(to_entries)
| add
| group_by(.key)
| map({
key: .[0].key,
value: map(.value) | add
})
| from_entries
С jq 1.5 это может быть упрощено далее. Вы можете покончить с разрывом и просто прочитать inputs
напрямую.
$ jq -n 'reduce (inputs | to_entries[]) as $i ({}; .[$i.key] += $i.value)' input.json
Поскольку мы просто накапливаем все значения в каждом из объектов, будет проще просто пропустить пары ключ/значение всех входов и добавить их все.
Ответ 2
Другим подходом, который достаточно хорошо иллюстрирует мощность jq, является использование фильтра с именем "sum", определяемого следующим образом:
def sum(f): reduce .[] as $row (0; . + ($row|f) );
Чтобы решить конкретную проблему, можно использовать опцию -s
(--slurp), как указано выше, вместе с выражением:
{"a": sum(.a), "b": sum(.b) } # (2)
Выражение, помеченное (2), вычисляет только две указанные суммы, но их легко обобщить, например. следующим образом:
# Produce an object with the same keys as the first object in the
# input array, but with values equal to the sum of the corresponding
# values in all the objects.
def sumByKey:
. as $in
| reduce (.[0] | keys)[] as $key
( {}; . + {($key): ($in | sum(.[$key]))})
;