Мне нужно подсчитать количество объектов в каждой группе с помощью JQ, но только для N самых последних объектов.

Пример ввода для N = 3:

{"modified":"Mon Sep 25 14:20:00 +0000 2018","object_id":1,"group_id":"C"}
{"modified":"Mon Sep 25 14:23:00 +0000 2018","object_id":2,"group_id":"A"}
{"modified":"Mon Sep 25 14:21:00 +0000 2018","object_id":3,"group_id":"B"}
{"modified":"Mon Sep 25 14:22:00 +0000 2018","object_id":4,"group_id":"A"}

Ожидаемый выход:

{"A",2}
{"B",1}

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

 [
   .modified |= strptime("%a %b %d %H:%M:%S %z %Y") |
   .modified |= mktime |
   .modified |= strftime("%Y-%m-%d %H:%M:%S")
 ]  |
 sort_by(.modified) |
 .[] |
 {modified, object_id, group_id}

По какой-то причине результаты до сих пор не отсортированы.

Мне также не удается преобразовать такой список в массив, чтобы выбрать только N последних записей.

И после этого мне нужно будет как-то подсчитать количество объектов в группе.


В целом, похоже, мне нужно чрезвычайно интуитивно понятное объяснение того, как массивы и списки объектов конвертируются друг в друга и как изменять некоторые из их полей, а затем извлекать только необходимые поля. Учебники, которые я нашел до сих пор, к сожалению, не помогли.

3
wass rubleff 25 Сен 2018 в 14:52

2 ответа

Лучший ответ

Предполагая, что ваш входной файл:

cat file
{"modified":"Mon Sep 25 14:20:00 +0000 2018","object_id":1,"class_id":"C"}
{"modified":"Mon Sep 25 14:23:00 +0000 2018","object_id":2,"class_id":"A"}
{"modified":"Mon Sep 25 14:21:00 +0000 2018","object_id":3,"class_id":"B"}
{"modified":"Mon Sep 25 14:22:00 +0000 2018","object_id":4,"class_id":"A"}

Вы можете попробовать следующее:

<file jq -s '
   [ .[] | 
     (.modified |= (strptime("%a %b %d %H:%M:%S +0000 %Y") | mktime)) 
   ] | 
   sort_by(.modified) |              # sort using converted time
   .[-3:] |                          # take the last 3
   group_by(.class_id) |             # group ids together
   .[] |                             
   {(.[0].class_id): length}'        # create the object using the id name and table length
{
   "A": 2
}
{
  "B": 1
}

Обратите внимание, что в моей системе опция %z из strptime не работает. Поэтому я заменил его на +0000 (который в любом случае не используется при преобразовании времени).

3
oliv 25 Сен 2018 в 13:30

В принятом ответе используется параметр командной строки -s, который требует, чтобы все входные данные умещались в памяти. Для очень больших наборов данных это может быть невозможно.

С момента выхода jq 1.5 (в 2015 году) доступна альтернатива. Поэтому здесь представлено решение с эффективным использованием памяти с использованием inputs.

Основные функции заключены в следующем фильтре jq:

# Return an array of n items as if by 
# [stream] | sort_by(filter) | .[-n:]
def maxn(stream; filter; n):
  def maxn:
    sort_by(filter) | .[-n :];
  reduce stream as $x ([]; . + [$x] | maxn);

Решение поставленной задачи (с N == 3) теперь можно получить всего за три дополнительных строки:

maxn(inputs; .modified | strptime("%a %b %d %H:%M:%S +0000 %Y") | mktime; 3)
| group_by(.class_id)[]
| {(.[0].class_id): length}

Обратите внимание, что здесь предполагается использование параметра командной строки -n. Если он не указан, первая строка ввода будет проигнорирована.

Большой N

Для больших наборов данных, если значение N также велико, вероятно, стоило бы потрудиться настроить вышеупомянутое, чтобы использовать поддержку jq для двоичного поиска (bsearch) вместо sort_by. Точно так же может быть целесообразно кешировать значения mktime.

2
wass rubleff 1 Окт 2018 в 12:41