Как разбить массив на куски с помощью jq?

у меня очень большой файл JSON, содержащий массив. Можно ли использовать jq разделить этот массив на несколько меньших массивов фиксированного размера? Предположим, мой вклад был таков:[1,2,3,4,5,6,7,8,9,10] и я хочу разделить его на 3 элемента длинными кусками. Желаемый выход из jq будет:

[1,2,3]
[4,5,6]
[7,8,9]
[10]

на самом деле, мой входной массив имеет почти три миллиона элементов, все UUID.

4 ответов


следующее потоковое определение window/3, из-за Седрика Конна (github:connesc), обобщает _nwise, и иллюстрирует "техника бокса", которая обходит необходимость использования маркер конца потока, и поэтому может быть использован если поток содержит значение, отличное от JSON nan. Определение из _nwise/1 С точки зрения window/3 также включен.

первый аргумент window/3 интерпретируется как поток. $size-это размер окна, а $step указывает номер значения, которые нужно пропустить. Например,

window(1,2,3; 2; 1)

выходы:

[1,2]
[2,3]

window/3 и _nsize / 1

def window(values; $size; $step):
  def checkparam(name; value): if (value | isnormal) and value > 0 and (value | floor) == value then . else error("window \(name) must be a positive integer") end;
  checkparam("size"; $size)
| checkparam("step"; $step)
  # We need to detect the end of the loop in order to produce the terminal partial group (if any).
  # For that purpose, we introduce an artificial null sentinel, and wrap the input values into singleton arrays in order to distinguish them.
| foreach ((values | [.]), null) as $item (
    {index: -1, items: [], ready: false};
    (.index + 1) as $index
    # Extract items that must be reused from the previous iteration
    | if (.ready | not) then .items
      elif $step >= $size or $item == null then []
      else .items[-($size - $step):]
      end
    # Append the current item unless it must be skipped
    | if ($index % $step) < $size then . + $item
      else .
      end
    | {$index, items: ., ready: (length == $size or ($item == null and length > 0))};
    if .ready then .items else empty end
  );

def _nwise($n): window(.[]; $n; $n);

источник:

https://gist.github.com/connesc/d6b87cbacae13d4fd58763724049da58


есть (недокументированный) строение, _nwise, что соответствует функциональным требованиям:

$ jq -nc '[1,2,3,4,5,6,7,8,9,10] | _nwise(3)'

[1,2,3]
[4,5,6]
[7,8,9]
[10]

также:

$ jq -nc '_nwise([1,2,3,4,5,6,7,8,9,10];3)' 
[1,2,3]
[4,5,6]
[7,8,9]
[10]

кстати, _nwise может использоваться как для массивов, так и для строк.

(Я считаю, что это недокументировано, потому что были некоторые сомнения относительно подходящего имени.)

TCO-версия

к сожалению, встроенная версия небрежно определена и не будет хорошо работать для больших массивов. Вот оптимизированный версия (она должна быть примерно такой же эффективной, как и нерекурсивная версия):

def nwise($n):
 def _nwise:
   if length <= $n then . else .[0:$n] , (.[$n:]|_nwise) end;
 _nwise;

для массива размером 3 миллиона это довольно эффективно: 3.91 s на старом Mac, 162746368 максимальный размер резидента.

обратите внимание, что эта версия (с использованием оптимизированной рекурсии tail-call) на самом деле быстрее, чем версия nwise/2 используя foreach показано в другом месте на этой странице.


ниже, конечно, хакеры , но памяти-эффективным!--3--> hackery, даже с произвольно длинным списком:

jq -c --stream 'select(length==2)|.[1]' <huge.json \
| jq -nc 'foreach inputs as $i (null; null; [$i,try input,try input])'

первая часть потоков конвейера во входном файле JSON, испуская по одной строке на элемент, предполагая, что массив состоит из атомарных значений (где [] и {} здесь включены как атомарные значения). Поскольку он работает в потоковом режиме, ему не нужно хранить весь контент в памяти, несмотря на то, что он является одним документом.

в вторая часть трубопровода многократно считывает до трех элементов и собирает их в список.

Это должно не более трех блоков данных в памяти одновременно.


если массив слишком велик, чтобы удобно поместиться в памяти, то я бы принял стратегию, предложенную @CharlesDuffy - то есть, поток элементов массива во второй вызов jq, используя потоковую версию nwise, например:

def nwise(stream; $n):
  foreach (stream, nan) as $x ([];
    if length == $n then [$x] else . + [$x] end;
    if (.[-1] | isnan) and length>1 then .[:-1]
    elif length == $n then .
    else empty
    end);

"Водитель" для вышеуказанного будет:

nwise(inputs; 3)

но, пожалуйста, не забудьте использовать опцию-N командной строки.

создать поток из произвольного массива:

$ jq -cn --stream '
    fromstream( inputs | (.[0] |= .[1:])
                | select(. != [[]]) )' huge.json 

так производство оболочки может выглядеть так:

$ jq -cn --stream '
    fromstream( inputs | (.[0] |= .[1:])
                | select(. != [[]]) )' huge.json |
  jq -n -f nwise.jq

этот подход довольно эффективен. Для группировки потока из 3 миллионов элементов в группы по 3 с помощью nwise/2,

/usr/bin/time -lp

для второго вызова jq дает:

user         5.63
sys          0.04
   1261568  maximum resident set size

предостережение: это определение использует nan как маркер конца потока. С nan не является значением JSON, это не может быть проблемой для обработки потоков JSON.