获取jq中的对象数组索引

xeo*_*eor 9 arrays json indexof jq

我有一个看起来像这样的json对象(由...生成)i3-msg -t get_workspaces.

[
  {
    "name": "1",
    "urgent": false
  },
  {
    "name": "2",
    "urgent": false
  },
  {
    "name": "something",
    "urgent": false
  }
]
Run Code Online (Sandbox Code Playgroud)

我试图用来jq确定列表中的哪个索引号是基于select查询的.jq有东西叫index(),但它接缝只支持字符串?

使用类似的东西i3-msg -t get_workspaces | jq '.[] | select(.name=="something")'给我我想要的对象.但我想要它的索引.在这种情况下2(从0开始计数)

这有可能jq单独使用吗?

Jim*_* D. 9

所以我提供了一个解决OP的策略,OP很快就接受了.随后@peak和@Jeff Mercado提供了更好,更完整的解决方案.所以我把它变成了社区维基.如果可以的话,请改进这个答案.

一个简单的解决方案(由@peak指出)是使用内置函数,index:

map(.name == "something") | index(true)
Run Code Online (Sandbox Code Playgroud)

jq文档令人困惑地建议index对字符串进行操作,但它也在数组上运行.因此index(true)返回true地图生成的布尔数组中第一个的索引.如果没有满足条件的项,则结果为null.

jq表达式以"惰性"方式计算,但map将遍历整个输入数组.我们可以通过重写上面的代码并引入一些调试语句来验证这一点:

[ .[] | debug | .name == "something" ] | index(true)
Run Code Online (Sandbox Code Playgroud)

正如@peak所建议的,做得更好的关键是使用breakjq 1.5中引入的语句:

label $out | 
foreach .[] as $item (
  -1; 
  .+1; 
  if $item.name == "something" then 
    ., 
    break $out 
  else 
    empty
  end
) // null
Run Code Online (Sandbox Code Playgroud)

注意,//没有评论; 它是替代运营商.如果找不到名称,foreach则将返回empty将由替代运算符转换为null的值.

另一种方法是递归处理数组:

def get_index(name): 
  name as $name | 
  if (. == []) then
    null
  elif (.[0].name == $name) then 
    0 
  else 
    (.[1:] | get_index($name)) as $result |
    if ($result == null) then null else $result+1 end      
end;
get_index("something")
Run Code Online (Sandbox Code Playgroud)

然而,这种递归实现将使用与最坏情况下数组长度成比例的堆栈空间,如@Jeff Mercado所指出的那样.在版本1.5中jq引入了尾部调用优化(TCO),这将允许我们使用本地辅助函数来优化它(请注意,这是对@Jeff Mercado提供的解决方案的小修改,以便与上面的示例一致):

def get_index(name): 
  name as $name | 
  def _get_index:
    if (.i >= .len) then
      null
    elif (.array[.i].name == $name) then
      .i
    else
      .i += 1 | _get_index
    end;
  { array: ., i: 0, len: length } | _get_index;
get_index("something")
Run Code Online (Sandbox Code Playgroud)

根据@peak获得数组的长度jq是一个恒定时间操作,显然索引数组也很便宜.我会尽力找到一个引用.

现在让我们尝试实际测量.以下是测量简单解决方案的示例:

#!/bin/bash

jq -n ' 

  def get_index(name): 
    name as $name |
    map(.name == $name) | index(true)
  ;

  def gen_input(n):  
    n as $n |
    if ($n == 0) then 
      []
    else
      gen_input($n-1) + [ { "name": $n, "urgent":false } ]
    end
  ;  

  2000 as $n |
  gen_input($n) as $i |
  [(0 | while (.<$n; [ ($i | get_index(.)), .+1 ][1]))][$n-1]
'
Run Code Online (Sandbox Code Playgroud)

当我在我的机器上运行时,我得到以下内容:

$ time ./simple
1999

real    0m10.024s
user    0m10.023s
sys     0m0.008s
Run Code Online (Sandbox Code Playgroud)

如果我用get_index的"快速"版本替换它:

def get_index(name): 
  name as $name |
  label $out | 
  foreach .[] as $item (
    -1; 
    .+1; 
  if $item.name == $name then 
    ., 
    break $out 
  else 
    empty
  end
) // null;
Run Code Online (Sandbox Code Playgroud)

然后我得到:

$ time ./fast
1999

real    0m13.165s
user    0m13.173s
sys     0m0.000s
Run Code Online (Sandbox Code Playgroud)

如果我用"快速"递归版本替换它:

def get_index(name): 
  name as $name | 
  def _get_index:
    if (.i >= .len) then
      null
    elif (.array[.i].name == $name) then
      .i
    else
      .i += 1 | _get_index
    end;
  { array: ., i: 0, len: length } | _get_index;
Run Code Online (Sandbox Code Playgroud)

我明白了:

$ time ./fast-recursive 
1999

real    0m52.628s
user    0m52.657s
sys     0m0.005s
Run Code Online (Sandbox Code Playgroud)

哎哟! 但我们可以做得更好.@peak提到了一个未记录的开关--debug-dump-disasm,它可以让您了解如何jq编译代码.有了这个,你可以看到修改和传递对象_indexof然后提取数组,长度和索引是昂贵的.重构只是通过索引是一个巨大的改进,并且进一步改进以避免测试索引的长度使其与迭代版本竞争:

def indexof($name):
  (.+[{name: $name}]) as $a | # add a "sentinel"
  length as $l | # note length sees original array
  def _indexof:
    if ($a[.].name == $name) then
      if (. != $l) then . else null end
    else
      .+1 | _indexof
    end
  ;


  0 | _indexof
;
Run Code Online (Sandbox Code Playgroud)

我明白了:

$ time ./fast-recursive2
null

real    0m13.238s
user    0m13.243s
sys     0m0.005s
Run Code Online (Sandbox Code Playgroud)

因此,如果每个元素具有相同的可能性,并且您希望获得平均案例性能,那么您应该坚持使用简单的实现.(C编码函数往往很快!)


pea*_*eak 5

@ Jim-D最初使用foreach提出的解决方案只能按预期用于JSON对象数组,并且最初提出的两种解决方案效率都很低。他们在没有满足条件的物品的情况下的行为也可能令人惊讶。

解决方案使用 index/1

如果只需要快速简便的解决方案,则可以使用内置函数,index如下所示:

map(.name == "something") | index(true)
Run Code Online (Sandbox Code Playgroud)

如果没有满足条件的项目,则结果为null

顺便说一句,如果您希望所有条件都为真的索引,则只需将其更改index为,即可轻松将上述内容转换为超快速解决方案indices

map(.name == "something") | indices(true)
Run Code Online (Sandbox Code Playgroud)

高效的解决方案

这是一个通用且有效的函数,它返回输入数组中(item | f)为真(不为null或false)的项中第一次出现的项的索引(即偏移),null否则返回。(在jq,javascript和许多其他语言中,数组的索引始终基于0。)

# 0-based index of item in input array such that f is truthy, else null
def which(f):
  label $out
  | foreach .[] as $x (-1; .+1; if ($x|f) then ., break $out else empty end)
  // null ;
Run Code Online (Sandbox Code Playgroud)

用法示例:

which(.name == "something")
Run Code Online (Sandbox Code Playgroud)