如何使用 jq 获取两个 JSON 数组的交集
how to get the intersection of two JSON arrays using jq
给定数组 X 和 Y(最好都作为输入,否则,一个作为输入,另一个硬编码),如何使用 jq 输出包含两者共有的所有元素的数组?例如f 的值是多少
echo '[1,2,3,4]' | jq 'f([2,4,6,8,10])'
会输出
[2,4]
?
我试过以下方法:
map(select(in([2,4,6,8,10]))) --> outputs [1,2,3,4]
select(map(in([2,4,6,8,10]))) --> outputs [1,2,3,4,5]
一个简单且相当快速(但有点天真)的过滤器,基本上可以做你想要的,可以定义如下:
# x and y are arrays
def intersection(x;y):
( (x|unique) + (y|unique) | sort) as $sorted
| reduce range(1; $sorted|length) as $i
([]; if $sorted[$i] == $sorted[$i-1] then . + [$sorted[$i]] else . end) ;
如果 x 作为 STDIN 的输入提供,而 y 以其他方式提供(例如 def y: ...
),那么您可以将其用作:intersection(.;y)
提供两个不同数组作为输入的其他方法包括:
- 使用
--slurp
选项
- 使用
--arg a v
(或 --argjson a v
,如果在您的 jq 中可用)
这是一个更简单但速度较慢的定义,但在实践中速度相当快:
def i(x;y):
if (y|length) == 0 then []
else (x|unique) as $x
| $x - ($x - y)
end ;
这是一个独立的过滤器,用于查找任意多个数组的交集:
# Input: an array of arrays
def intersection:
def i(y): ((unique + (y|unique)) | sort) as $sorted
| reduce range(1; $sorted|length) as $i
([]; if $sorted[$i] == $sorted[$i-1] then . + [$sorted[$i]] else . end) ;
reduce .[1:][] as $a (.[0]; i($a)) ;
示例:
[ [1,2,4], [2,4,5], [4,5,6]] #=> [4]
[[]] #=> []
[] #=> null
当然,如果已知 x
和 y
排序 and/or 唯一,则可能有更有效的解决方案。具体参见 Finite Sets of JSON Entities
这是一个解决方案,它使用 foreach
计算数组中元素的出现次数
[
foreach ($X[], $Y[]) as $r (
{}
; .[$r|tostring] += 1
; if .[$r|tostring] == 2 then $r else empty end
)
]
如果此过滤器在 filter.jq
中,则
jq -M -n -c --argjson X '[1,2,3,4]' --argjson Y '[2,4,6,8,10]' -f filter.jq
会产生
[2,4]
假设初始数组中没有重复项。如果不是这种情况,那么很容易用 unique 进行补偿。例如
[
foreach (($X|unique)[], ($Y|unique)[]) as $r (
{}
; .[$r|tostring] += 1
; if .[$r|tostring] == 2 then $r else empty end
)
]
$ echo '[1,2,3,4] [2,4,6,8,10]' | jq --slurp '[.[0][] as $x | .[1][] | select($x == .)]'
[
2,
4
]
简单说明
所有这些答案的这些复杂性掩盖了对原理的理解。这很不幸,因为原理很简单:
- array1 minus array2 returns:
- everything that's left in array1
- after removing everything that is in array2
- (and discarding the rest of array2)
简单演示
# From array1, subtract array2, leaving the remainder
$ jq --null-input '[1,2,3,4] - [2,4,6,8]'
[
1,
3
]
# Subtract the remainder from the original
$ jq --null-input '[1,2,3,4] - [1,3]'
[
2,
4
]
# Put it all together
$ jq --null-input '[1,2,3,4] - ([1,2,3,4] - [2,4,6,8])'
[
2,
4
]
comm
演示
def comm:
(.[0] - (.[0] - .[1])) as $d |
[.[0]-$d, .[1]-$d, $d]
;
有了这种理解,我就能够模仿 the *nix comm
command
的行为
With no options, produce three-column output. Column one
contains lines unique to FILE1, column two contains lines unique
to FILE2, and column three contains lines common to both files.
$ echo 'def comm: (.[0]-(.[0]-.[1])) as $d | [.[0]-$d,.[1]-$d, $d];' > comm.jq
$ echo '{"a":101, "b":102, "c":103, "d":104}' > 1.json
$ echo '{ "b":202, "d":204, "f":206, "h":208}' > 2.json
$ jq --slurp '.' 1.json 2.json
[
{
"a": 101,
"b": 102,
"c": 103,
"d": 104
},
{
"b": 202,
"d": 204,
"f": 206,
"h": 208
}
]
$ jq --slurp '[.[] | keys | sort]' 1.json 2.json
[
[
"a",
"b",
"c",
"d"
],
[
"b",
"d",
"f",
"h"
]
]
$ jq --slurp 'include "comm"; [.[] | keys | sort] | comm' 1.json 2.json
[
[
"a",
"c"
],
[
"f",
"h"
],
[
"b",
"d"
]
]
$ jq --slurp 'include "comm"; [.[] | keys | sort] | comm[2]' 1.json 2.json
[
"b",
"d"
]
给定数组 X 和 Y(最好都作为输入,否则,一个作为输入,另一个硬编码),如何使用 jq 输出包含两者共有的所有元素的数组?例如f 的值是多少
echo '[1,2,3,4]' | jq 'f([2,4,6,8,10])'
会输出
[2,4]
?
我试过以下方法:
map(select(in([2,4,6,8,10]))) --> outputs [1,2,3,4]
select(map(in([2,4,6,8,10]))) --> outputs [1,2,3,4,5]
一个简单且相当快速(但有点天真)的过滤器,基本上可以做你想要的,可以定义如下:
# x and y are arrays
def intersection(x;y):
( (x|unique) + (y|unique) | sort) as $sorted
| reduce range(1; $sorted|length) as $i
([]; if $sorted[$i] == $sorted[$i-1] then . + [$sorted[$i]] else . end) ;
如果 x 作为 STDIN 的输入提供,而 y 以其他方式提供(例如 def y: ...
),那么您可以将其用作:intersection(.;y)
提供两个不同数组作为输入的其他方法包括:
- 使用
--slurp
选项 - 使用
--arg a v
(或--argjson a v
,如果在您的 jq 中可用)
这是一个更简单但速度较慢的定义,但在实践中速度相当快:
def i(x;y):
if (y|length) == 0 then []
else (x|unique) as $x
| $x - ($x - y)
end ;
这是一个独立的过滤器,用于查找任意多个数组的交集:
# Input: an array of arrays
def intersection:
def i(y): ((unique + (y|unique)) | sort) as $sorted
| reduce range(1; $sorted|length) as $i
([]; if $sorted[$i] == $sorted[$i-1] then . + [$sorted[$i]] else . end) ;
reduce .[1:][] as $a (.[0]; i($a)) ;
示例:
[ [1,2,4], [2,4,5], [4,5,6]] #=> [4]
[[]] #=> []
[] #=> null
当然,如果已知 x
和 y
排序 and/or 唯一,则可能有更有效的解决方案。具体参见 Finite Sets of JSON Entities
这是一个解决方案,它使用 foreach
计算数组中元素的出现次数[
foreach ($X[], $Y[]) as $r (
{}
; .[$r|tostring] += 1
; if .[$r|tostring] == 2 then $r else empty end
)
]
如果此过滤器在 filter.jq
中,则
jq -M -n -c --argjson X '[1,2,3,4]' --argjson Y '[2,4,6,8,10]' -f filter.jq
会产生
[2,4]
假设初始数组中没有重复项。如果不是这种情况,那么很容易用 unique 进行补偿。例如
[
foreach (($X|unique)[], ($Y|unique)[]) as $r (
{}
; .[$r|tostring] += 1
; if .[$r|tostring] == 2 then $r else empty end
)
]
$ echo '[1,2,3,4] [2,4,6,8,10]' | jq --slurp '[.[0][] as $x | .[1][] | select($x == .)]'
[
2,
4
]
简单说明
所有这些答案的这些复杂性掩盖了对原理的理解。这很不幸,因为原理很简单:
- array1 minus array2 returns:
- everything that's left in array1
- after removing everything that is in array2
- (and discarding the rest of array2)
简单演示
# From array1, subtract array2, leaving the remainder
$ jq --null-input '[1,2,3,4] - [2,4,6,8]'
[
1,
3
]
# Subtract the remainder from the original
$ jq --null-input '[1,2,3,4] - [1,3]'
[
2,
4
]
# Put it all together
$ jq --null-input '[1,2,3,4] - ([1,2,3,4] - [2,4,6,8])'
[
2,
4
]
comm
演示
def comm:
(.[0] - (.[0] - .[1])) as $d |
[.[0]-$d, .[1]-$d, $d]
;
有了这种理解,我就能够模仿 the *nix comm
command
With no options, produce three-column output. Column one contains lines unique to FILE1, column two contains lines unique to FILE2, and column three contains lines common to both files.
$ echo 'def comm: (.[0]-(.[0]-.[1])) as $d | [.[0]-$d,.[1]-$d, $d];' > comm.jq
$ echo '{"a":101, "b":102, "c":103, "d":104}' > 1.json
$ echo '{ "b":202, "d":204, "f":206, "h":208}' > 2.json
$ jq --slurp '.' 1.json 2.json
[
{
"a": 101,
"b": 102,
"c": 103,
"d": 104
},
{
"b": 202,
"d": 204,
"f": 206,
"h": 208
}
]
$ jq --slurp '[.[] | keys | sort]' 1.json 2.json
[
[
"a",
"b",
"c",
"d"
],
[
"b",
"d",
"f",
"h"
]
]
$ jq --slurp 'include "comm"; [.[] | keys | sort] | comm' 1.json 2.json
[
[
"a",
"c"
],
[
"f",
"h"
],
[
"b",
"d"
]
]
$ jq --slurp 'include "comm"; [.[] | keys | sort] | comm[2]' 1.json 2.json
[
"b",
"d"
]