【问题标题】:how to get the intersection of two JSON arrays using jq如何使用 jq 获取两个 JSON 数组的交集
【发布时间】:2016-07-14 02:14:43
【问题描述】:

给定数组 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]

【问题讨论】:

    标签: arrays json intersection jq


    【解决方案1】:

    简单说明

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

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

    【讨论】:

      【解决方案2】:

      一个简单且相当快速(但有点幼稚)的过滤器可能基本上可以满足您的需求,可以定义如下:

         # 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,但在实践中却相当快:

          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
      

      当然,如果已知xy 是已排序和/或唯一的,则可以使用更有效的解决方案。具体见Finite Sets of JSON Entities

      【讨论】:

      • 对于 jq 来说似乎异常冗长,但对于我尝试过的任何其他解决方案,它确实有效。谢谢
      • 如何在 .jq 文件中使用此功能?我不明白为什么这不起作用:jq -f plugin-group.jq plugins.jsongist.github.com/MagentaRuby/0ec5a4b7023b49d62e998c3925821863
      • intersection 在这里定义为 arity 0。也许你的意思是:.intersection = ([ .servers[].installed] | intersection)
      【解决方案3】:

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

      【讨论】:

      • 在某些情况下这些是很好的解决方案,但如果存在“冲突”,则会给出错误的答案,因为(例如)数字 1 和字符串“1”都映射到后一个值。
      【解决方案4】:
      $ echo '[1,2,3,4] [2,4,6,8,10]' | jq --slurp '[.[0][] as $x | .[1][] | select($x == .)]'
      [
        2,
        4
      ]
      

      【讨论】:

      • 请在您的答案中添加更多细节。为什么这行得通?你解决了什么问题?这让 OP 和未来的每个人都更容易理解他们的问题。
      • 此解决方案要求将两个数组分别传递给jqjq 在其输出中包含第二个数组的每个元素(如果它存在于第一个数组中)。我猜jq 将线性地(?)搜索第二个数组以查找第一个数组的每个元素。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-23
      • 1970-01-01
      相关资源
      最近更新 更多