【问题标题】:How to split an array into chunks with jq?如何使用 jq 将数组拆分为块?
【发布时间】:2018-12-27 00:08:05
【问题描述】:

我有一个非常大的 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。

【问题讨论】:

  • jq 'group_by(. % 3)' <<< '[1,2,3,4,5,6,7,8,9,10]' 分为三组。现在,如果我能得到完整输入数组的长度……

标签: arrays json shell jq memory-efficient


【解决方案1】:

如果数组太大而无法舒适地放入内存中,那么我会采用@CharlesDuffy 建议的策略——即,使用@987654323 的面向流的版本将数组元素流式传输到 jq 的第二次调用中@,如:

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

这种方法非常高效。对于使用nwise/2 将包含 300 万个项目的流分组为 3 个组,

/usr/bin/time -lp

对于 jq 的第二次调用给出:

user         5.63
sys          0.04
   1261568  maximum resident set size

警告:此定义使用nan 作为流结束标记。由于nan 不是 JSON 值,因此处理 JSON 流不会有问题。

【讨论】:

  • 啊——如果你认为它增加了一些有用的东西,我会把它拉回来。
  • 既然你对hackery很了解,我建议你保留它——它展示了如何使用whiletryinput:-)
  • 当我进行实验测试时,当. == [nan](如果项目总数均分到$n)时,我们似乎可以得到一个尾随的空列表。也许检查流结束的行应该是if .[-1] | isnan then if (. | length) > 1 then .[:-1] else empty end?测试程序见gist.github.com/charles-dyfis-net/…
  • @CharlesDuffy - 感谢您发现问题,我已经以稍微不同的方式解决了这个问题,以防它不慢。
  • 要将输出分成单独的文件,请将第二个 jq 调用更改为例如jq -cn -f nwise.jq | awk '{print > "doc00" NR ".json"}'stackoverflow.com/a/48801628/482758
【解决方案2】:

下面是骇客,可以肯定 - 但内存效率骇客,即使是任意长的列表:

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

输入 JSON 文件中的第一段管道流,每个元素发出一行,假设数组由原子值组成(其中 [] 和 {} 在这里包含为原子值)。因为它在流模式下运行,所以它不需要将整个内容存储在内存中,尽管它是单个文档。

管道的第二部分重复读取最多三个项目并将它们组合成一个列表。

这应该避免一次在内存中需要三个以上的数据。

【讨论】:

  • 在此处使用 while 会导致错误。最好写成: jq -nc 'foreach inputs as $i (null; null; [$i,try input,try input])'
  • 我在处理结束时忽略了错误,但这是一个明显的改进。感谢您的改进。
【解决方案3】:

window/3 的以下面向流的定义,由于 Cédric Connes (github:connesc),概括_nwise, 并说明了一个 “拳击技术”,避免使用 流结束标记,因此可以使用 如果流包含非 JSON 值 nan。定义 _nwise/1window/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

【讨论】:

  • 读者:您可以通过将最后一行替换为def streamsplit($n): window(inputs | .[1]; $n $n) 并传递--stream 来将其用于流式传输。它将在 2844k 最大内存中运行。
  • @EchoNolan:请注意,window/2 比本页其他地方给出的面向流的 nwise/2 慢一点。可以使用使用 inputs 的相同技术,但无论哪种情况,请记住使用 -n 命令行选项。
【解决方案4】:

有一个(未记录的)内置函数_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;

对于大小为 300 万的数组,这是非常高效的: 在旧 Mac 上为 3.91 秒,最大驻留大小为 162746368。

请注意,此版本(使用尾调用优化递归)实际上比使用 foreachnwise/2 版本更快。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-06
    • 2013-06-27
    • 1970-01-01
    • 1970-01-01
    • 2014-12-22
    相关资源
    最近更新 更多