假设我们要把数组中的数字加起来。 实现这个目标的方法有很多。
在很多语言里,这个问题会写成带累加器变量的循环。
这个问题也可以写成一个递归函数。 用伪代码表示,可能像这样。
function Add(X, Sum=0):
if X is empty then
return Sum
else
return Add(rest(X), Sum + first(X))
end
end
这种把问题拆解成更小片段的方法,也可以称为“向基本情况归约”。
Reduce是一种把数据结构中所有元素合并成单个值的方法。 这个过程会迭代遍历数据结构,对每个元素应用一个函数,以更新累积的结果。
在jq中,这个过程由reduce过滤器实现。
在其他语言里,它可能叫“fold”、“fold-left”、“inject”或“aggregate”。
jq的reduce表达式看起来像这样。
reduce STREAM_EXPRESSION as $var (INITIAL_VALUE; UPDATE_EXPRESSION)
STREAM_EXPRESSION是元素的_流_,每一项会依次存入$var变量。
.[]:$myArray | .[]。INITIAL_VALUE是累积结果的起始值(也就是“累加器”)。UPDATE_EXPRESSION把当前值($var)“折叠”进累加器。
.就是累加器的值。reduce的输出。来看一个例子:把数组中的数字加起来。
add过滤器做的正是这件事,不过我们会看看它该如何实现。
如果以[10, 20, 30, 40]作为输入,并以 0 作为初始状态,每一步是这样的。
| # | 状态 | 元素 | 归约函数 | 结果 |
|---|---|---|---|---|
| 1 | 0 | 10 | 0 + 10 | 10 |
| 2 | 10 | 20 | 10 + 20 | 30 |
| 3 | 30 | 30 | 30 + 30 | 60 |
| 4 | 60 | 40 | 60 + 40 | 100 |
用jq语法写出来,就是下面这段代码。
0 + 10 | . + 20 | . + 30 | . + 40
我们可以用reduce过滤器来表达。
[10, 20, 30, 40] | reduce .[] as $n (0; . + $n) # => 100
add内置过滤器实际上也是借助reduce实现的,只不过它把 "null" 作为初始状态(任何数据类型都可以加到 null 上)。
def add: reduce .[] as $x (null; . + $x);
在归约表达式中,.就是累加器。
如果输入是一个对象,而你需要在归约函数内部引用它,就得把它存到一个变量里。
{"apple": 10, "banana": 16, "carrot": 4}
| . as $obj
| reduce (keys | .[]) as $key (0; . + $obj[$key]) # => 30
累加器可以是任何数据类型。 例如,你可能想反转一个数组。
["A", "B", "C", "D"]
| reduce .[] as $elem ([]; [$elem] + .) # => ["D", "C", "B", "A"]