jq中使用滑动窗口处理数组是否有比索引法更优的实现方式?
问题说明
需要将形如["a", 1, "b", 2, "c", 3]的交替键值数组,转换为{"a": 1, "b": 2, "c": 3}格式的对象。这类需求的通用处理逻辑是滑动窗口(本场景窗口大小为2、步长为2)切分数组。
原有基于数组索引的实现写法繁琐,且需要将全量数组加载到内存,对流式处理支持不佳,原有代码如下:
# 生成测试用输入,本示例中为从'a'到'z'的所有字母 function input () { printf '"%s" ' {a..z} | jq --slurp --compact-output '.' } input | jq '. as $i | $i | keys | map(select (. % 2 == 0)) | map({key:($i[.]|tostring), value:$i[. + 1]}) | from_entries'
预期的理想写法是实现类Scala集合的sliding(size: Int, step: Int)方法,直接调用滑动窗口逻辑简化代码:
input | jq 'sliding(2;2) | map({key: (.[0]|tostring), value: .[1]) | from_entries'
实现方案
jq本身没有内置sliding方法,但可以通过内置的while迭代语法快速实现等价效果,写法和预期的理想形式几乎一致。
场景专属快速实现
针对当前窗口大小2、步长2的键值转换场景,可以直接写出如下简洁代码:
# 沿用前述测试输入生成函数 input | jq ' [while(. != []; .[2:]) | .[:2]] | map({key: .[0]|tostring, value: .[1]}) | from_entries '
核心逻辑说明:
while(. != []; .[2:])会迭代生成每次向后滑动2个位置后的剩余数组,直到数组为空时停止迭代- 对每次迭代得到的剩余数组取前2位,即为单个大小为2的滑动窗口
- 外层用
[]将所有迭代生成的窗口收集为二维数组,后续直接按键值对转换逻辑生成对象即可
通用sliding函数封装
如果需要复用到其他窗口大小、步长的场景,可以将滑动逻辑封装为自定义jq函数,实现和预期完全一致的调用形式:
# 定义通用滑动窗口函数:sliding(窗口大小; 滑动步长) def sliding(size; step): [while(. != []; .[step:]) | .[:size]]; # 调用示例,和理想写法完全匹配 input | jq ' sliding(2;2) | map({key: .[0]|tostring, value: .[1]}) | from_entries '
该实现的优势:
- 逻辑可读性高,传入的
size参数会直接控制每个窗口的截取长度,中间输出结果清晰易读 - 不需要提前遍历全量数组生成索引列表,处理逻辑更直观
- 配合jq的
--stream流式参数,可以支持超大数组的逐段处理,不需要将全量数据加载到内存
如果需要验证不同窗口大小的效果,比如窗口大小3、步长2,直接调用sliding(3;2)即可得到对应切分结果,和测试的while(. != []; .[2:])[:3]输出完全一致。
内容的提问来源于stack exchange,提问作者Morgen
相关产品推荐
相关产品推荐

