Suppose we want to sum an array of numbers. There are many ways to accomplish this goal.
In many languages, this problem is expressed as a loop with an accumulator variable.
This problem can be written as a recursive function. In pseudo-code, we might have this.
function Add(X, Sum=0):
if X is empty then
return Sum
else
return Add(rest(X), Sum + first(X))
end
end
This method of dividing the problem into smaller pieces can also be described as "reducing towards the base case."
Reduce is a way to combine all the elements of a data structure into a single value. The process iterates over the data structure, applying a function to each element to update the accumulated result.
In jq
, this process is implemented in the reduce
filter.
In other languages, it might be called "fold", "fold-left", "inject", or "aggregate".
The jq
reduce
expression looks like this.
reduce STREAM_EXPRESSION as $var (INITIAL_VALUE; UPDATE_EXPRESSION)
STREAM_EXPRESSION
is a stream of items, each stored in the $var
variable in turn.
.[]
: $myArray | .[]
.INITIAL_VALUE
is the starting value of the accumulated result (known as the "accumulator").UPDATE_EXPRESSION
combines ("folds") the current value ($var) into the accumulator.
.
is the value of the accumulator.reduce
.Let's look at an example: adding up the numbers in an array.
The add
filter does just this, but we'll see how to implement it.
If we use [10, 20, 30, 40]
as the input, and taking zero as the initial state, this is what each step looks like.
# | state | element | reducer | result |
---|---|---|---|---|
1 | 0 | 10 | 0 + 10 | 10 |
2 | 10 | 20 | 10 + 20 | 30 |
3 | 30 | 30 | 30 + 30 | 60 |
4 | 60 | 40 | 60 + 40 | 100 |
In jq
syntax, this looks like this code.
0 + 10 | . + 20 | . + 30 | . + 40
We can express that with the reduce
filter.
[10, 20, 30, 40] | reduce .[] as $n (0; . + $n) # => 100
The add
builtin is actually implemented with reduce
, but uses "null" as the initial state (any data type can be added to null).
def add: reduce .[] as $x (null; . + $x);
In the reducing expression, .
is the accumulator.
If the input is some object that you need to reference inside the reducing function, you need to store it in a variable.
{"apple": 10, "banana": 16, "carrot": 4}
| . as $obj
| reduce (keys | .[]) as $key (0; . + $obj[$key]) # => 30
The accumulator can be of any type of data. For example you may want to reverse an array.
["A", "B", "C", "D"]
| reduce .[] as $elem ([]; [$elem] + .) # => ["D", "C", "B", "A"]
You are a teacher. At the end of the year, you have generated a numeric grade for each of your students. Now you need to translate that to a letter grade and count how many students have achieved each letter grade
The letter_grade
function will take a numeric grade as input, and it will output the letter.
Use these ranges:
Letter | Grade |
---|---|
A | 90% - 100% |
B | 80% - 89% |
C | 70% - 79% |
D | 60% - 69% |
F | 0% - 59% |
Example:
75 | letter_grade # => "C"
The function count_letter_grades
will take an object mapping student names to their grades.
The output will be an object mapping each letter grade to the number of students with that grade.
Example:
{"Joe": 78, "Jane": 93, "Richard": 72} | count_letter_grades
# => {"A": 1, "B": 0, "C": 2, "D": 0, "F": 0}
There are a few different ways to solve this.
Use the reduce
filter for practice.
Sign up to Exercism to learn and master jq with 12 concepts, 55 exercises, and real human mentoring, all for free.