如何使用jq获取两个JSON数组的交集

时间:2016-07-14 02:14:43

标签: arrays json intersection 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]

4 个答案:

答案 0 :(得分:4)

简单说明

所有这些答案的复杂性掩盖了对原理的理解。这很不幸,因为原理很简单:

<块引用>
  • array1 减去 array2 返回:
  • array1 中剩下的所有内容
  • 删除 array2 中的所有内容后
  • (并丢弃 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

的行为 <块引用>

没有选项,生成三列输出。第一栏 包含 FILE1 独有的行,第二列包含独有的行 到 FILE2,第三列包含两个文件共有的行。

$ 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"
]

答案 1 :(得分:3)

一个简单且非常快速(但有些幼稚)的过滤器可能基本上可以完成你想要的过程,可以定义如下: # 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) ;

如果在STDIN上提供x作为输入,并且以某种其他方式提供y(例如def y:...),则可以将其用作:intersection(。; y)

提供两个不同数组作为输入的其他方法包括:

 * using the --slurp option
 * using "--arg a v" (or "--argjson a v" if available in your jq)

这是一个更简单但速度更慢的def,但实际上速度相当快: 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被分类和/或唯一,则更有效的解决方案是可能的。特别参见https://rosettacode.org/wiki/Set#Finite_Sets_of_JSON_Entities

答案 2 :(得分:0)

$ echo '[1,2,3,4] [2,4,6,8,10]' | jq --slurp '[.[0][] as $x | .[1][] | select($x == .)]'
[
  2,
  4
]

答案 3 :(得分:-1)

这是一个解决方案,它通过使用 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]

假设初始数组中没有重复项。如果情况并非如此,则很容易用唯一进行补偿。 E.g。

[
  foreach (($X|unique)[], ($Y|unique)[]) as $r (
    {}
  ; .[$r|tostring] += 1
  ; if .[$r|tostring] == 2 then $r else empty end
  )
]