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单独使用吗?
所以我提供了一个解决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编码函数往往很快!)
@ 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)