如何使用 jq 将数组拆分为块?
How to split an array into chunks with 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。
下面是 hackery,可以肯定 - 但 memory-efficient hackery,即使有任意长的列表:
jq -c --stream 'select(length==2)|.[1]' <huge.json \
| jq -nc 'foreach inputs as $i (null; null; [$i,try input,try input])'
输入 JSON 文件中的第一段管道流,每个元素发出一行,假设数组由原子值组成(其中 [] 和 {} 在这里包含为原子值)。因为它以流模式运行,所以不需要将整个内容存储在内存中,尽管它是单个文档。
管道的第二部分重复读取最多三个项目并将它们组装成一个列表。
这应该避免内存中同时需要三个以上的数据。
有一个(未记录的)内置函数,_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。
请注意,此版本(使用尾调用优化递归)实际上比本页其他地方显示的使用 foreach
的 nwise/2
版本更快。
如果数组太大而无法轻松放入内存,那么我会采用@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);
上面的 "driver" 是:
nwise(inputs; 3)
但是请记住使用-n命令行选项。
从任意数组创建流:
$ jq -cn --stream '
fromstream( inputs | (.[0] |= .[1:])
| select(. != [[]]) )' huge.json
因此 shell 管道可能如下所示:
$ 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 流来说不是问题。
window/3
的以下面向流的定义,归功于 Cédric Connes
(github:connesc),概括_nwise
,
并说明了
"boxing technique" 绕过了使用
流结束标记,因此可以使用
如果流包含非 JSON 值 nan
。一个定义
_nwise/1
的 window/3
也包括在内。
window/3
的第一个参数被解释为一个流。 $size 是 window 大小,$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
我有一个非常大的 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。
下面是 hackery,可以肯定 - 但 memory-efficient hackery,即使有任意长的列表:
jq -c --stream 'select(length==2)|.[1]' <huge.json \
| jq -nc 'foreach inputs as $i (null; null; [$i,try input,try input])'
输入 JSON 文件中的第一段管道流,每个元素发出一行,假设数组由原子值组成(其中 [] 和 {} 在这里包含为原子值)。因为它以流模式运行,所以不需要将整个内容存储在内存中,尽管它是单个文档。
管道的第二部分重复读取最多三个项目并将它们组装成一个列表。
这应该避免内存中同时需要三个以上的数据。
有一个(未记录的)内置函数,_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。
请注意,此版本(使用尾调用优化递归)实际上比本页其他地方显示的使用 foreach
的 nwise/2
版本更快。
如果数组太大而无法轻松放入内存,那么我会采用@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);
上面的 "driver" 是:
nwise(inputs; 3)
但是请记住使用-n命令行选项。
从任意数组创建流:
$ jq -cn --stream '
fromstream( inputs | (.[0] |= .[1:])
| select(. != [[]]) )' huge.json
因此 shell 管道可能如下所示:
$ jq -cn --stream '
fromstream( inputs | (.[0] |= .[1:])
| select(. != [[]]) )' huge.json |
jq -n -f nwise.jq
这种方法非常高效。使用 nwise/2
、
/usr/bin/time -lp
对于 jq 的第二次调用给出:
user 5.63
sys 0.04
1261568 maximum resident set size
警告:此定义使用 nan
作为流结束标记。由于 nan
不是 JSON 值,因此这对于处理 JSON 流来说不是问题。
window/3
的以下面向流的定义,归功于 Cédric Connes
(github:connesc),概括_nwise
,
并说明了
"boxing technique" 绕过了使用
流结束标记,因此可以使用
如果流包含非 JSON 值 nan
。一个定义
_nwise/1
的 window/3
也包括在内。
window/3
的第一个参数被解释为一个流。 $size 是 window 大小,$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