Как суммировать значения в массиве карт в jq?

Дан поток JSON следующего вида:

{ "a": 10, "b": 11 } { "a": 20, "b": 21 } { "a": 30, "b": 31 }

Я хотел бы суммировать значения в каждом из объектов и вывести один объект, а именно:

{ "a": 60, "b": 63 }

Я предполагаю, что это, вероятно, потребует выравнивания приведенного выше списка объектов в массив пар [name, value], а затем суммирования значений с использованием reduce, но документация синтаксиса для использования reduce ужасна.


person Alan Burlison    schedule 12.02.2015    source источник


Ответы (3)


Если у вашего jq нет inputs, вам придется глотать объекты, используя флаг -s. Тогда вам придется проделать изрядное количество манипуляций:

  1. Каждый из объектов должен быть сопоставлен с парами ключ/значение.
  2. Свести пары в один массив
  3. Сгруппируйте пары по ключу
  4. Сопоставьте каждую группу, накапливая значения в одной паре ключ/значение.
  5. Сопоставьте пары обратно с объектом
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 {$key,$value} ({}; .[$key] += $value)
' input.json

Поскольку мы просто накапливаем все значения в каждом из объектов, будет проще просто просмотреть пары ключ/значение всех входных данных и сложить их все.

person Jeff Mercado    schedule 12.02.2015
comment
Ах, group_by — это то, чего мне не хватало, я проглядел его в документации. Кроме того, у меня есть инструмент, создающий исходные данные для вывода в виде одного потока JSON, поэтому «-s» не нужен. Единственная другая настройка, которую я сделал, заключалась в том, чтобы использовать «сглаживание» вместо «добавить». Спасибо за ответ, он был в точку :-) - person Alan Burlison; 13.02.2015
comment
Уменьшить это круто! Я борюсь с тем, как он может поддерживать 2 метода (сумма и среднее значение для отсутствия). Ему не нравятся запятые с запятой, запятые, похоже, выполняют последнюю манипуляцию, только уменьшают .usage.os:linux[] as $item ({credits:0 ,минуты:0}; .credits += $item.credits; .minutes += $item.amount /1000/60 ) - person Eddie; 21.11.2018
comment
@Eddie: точки с запятой используются для разделения параметров на вызовы функций. Как вы его там используете, это должны быть каналы, и он будет работать так, как вы ожидаете. - person Jeff Mercado; 22.11.2018
comment
Разве jq -s 'reduce (.[] | to_entries[]) ... не должно работать так же хорошо, как jq -n 'reduce (inputs | to_entries[]) ...? Мне не хватает отображения, group_by и т. д. Или отсутствие inputs означает, что в версии jq отсутствуют другие конструкции в примере -n? - person Watson; 16.09.2020

Я столкнулся с тем же вопросом при перечислении всех артефактов из GitHub (см. здесь для подробностей) и хотите суммировать их размер.

curl https://api.github.com/repos/:owner/:repo/actions/artifacts \
     -H "Accept: application/vnd.github.v3+json" \
     -H "Authorization:  token <your_pat_here>" \
     | jq '.artifacts | map(.size_in_bytes) | add'

Вход:

{
  "total_count": 3,
  "artifacts": [
    {
      "id": 0000001,
      "node_id": "MDg6QXJ0aWZhY3QyNzUxNjI1",
      "name": "artifact-1",
      "size_in_bytes": 1,
      "url": "https://api.github.com/repos/:owner/:repo/actions/artifacts/2751625",
      "archive_download_url": "https://api.github.com/repos/:owner/:repo/actions/artifacts/2751625/zip",
      "expired": false,
      "created_at": "2020-03-10T18:21:23Z",
      "updated_at": "2020-03-10T18:21:24Z"
    },
    {
      "id": 0000002,
      "node_id": "MDg6QXJ0aWZhY3QyNzUxNjI0",
      "name": "artifact-2",
      "size_in_bytes": 2,
      "url": "https://api.github.com/repos/:owner/:repo/actions/artifacts/2751624",
      "archive_download_url": "https://api.github.com/repos/:owner/:repo/actions/artifacts/2751624/zip",
      "expired": false,
      "created_at": "2020-03-10T18:21:23Z",
      "updated_at": "2020-03-10T18:21:24Z"
    },
    {
      "id": 0000003,
      "node_id": "MDg6QXJ0aWZhY3QyNzI3NTk1",
      "name": "artifact-3",
      "size_in_bytes": 3,
      "url": "https://api.github.com/repos/docker/mercury-ui/actions/artifacts/2727595",
      "archive_download_url": "https://api.github.com/repos/:owner/:repo/actions/artifacts/2727595/zip",
      "expired": false,
      "created_at": "2020-03-10T08:46:08Z",
      "updated_at": "2020-03-10T08:46:09Z"
    }
  ]
}

Выход:

6
person Oleg Burov    schedule 11.03.2020
comment
Используйте флаг -s, чтобы заставить его работать: jq -s '.artifacts | map(.size_in_bytes) | add'. - person Gorka; 20.04.2021

Другой подход, который довольно хорошо иллюстрирует мощь jq, заключается в использовании фильтра с именем «сумма», определяемого следующим образом:

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]))})
;
person peak    schedule 16.02.2015