Как суммировать значения в массиве карт в 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]))})
;