Why is the sea salty? Simple question but stay with me because I think the water cycle is a good analogy for the way reduce
method really works.
In short, water (H2O) evaporates from the surface of the oceans to form clouds. The clouds pass over land and condense until they start to precipitate as rain/snow fall. Eventually the falling water runs into rivers and starts on it way to the sea. En route the water picks up minerals including salts and carries them to the sea. As the cycle starts again the minerals are left behind and over time concentration levels build.
To see how the water cycle can help us understand the way reduce
works we have to break it down into three element:
- The minerals equate to the items in the array on which we are performing the
reduce
. - The water is the accumulator parameter or the reducer callback function.
- The ocean is the accumulator in its argument form, both in the initial and final values.
So let's align this to code
Please excuse the concentration measurements, they are probably way off, I am not a chemist.
First we will prepare some test data. The following code generates simulated samples, two per month for a years.
const mineralsPerCycle = concentrationSamplesOverYear(24);
console.table(mineralsPerCycle);
function concentrationSamplesOverYear(samples) {
const interval = (2 * Math.PI) / samples;
const captureSample = i =>
((Math.random() + 7) / 8) * ((Math.cos(i * interval) + 2) / 3);
return [...new Array(samples)].map((_, i) => captureSample(i));
}
The console.table
will render the values before we use them. Below is an example but yours will have different values.
┌─────────┬─────────────────────┐
│ (index) │ Values │
├─────────┼─────────────────────┤
│ 0 │ 0.89801916280756 │
│ 1 │ 0.9567662790947499 │
│ 2 │ 0.9325939089002321 │
│ 3 │ 0.8992754278881672 │
│ 4 │ 0.7532231143389726 │
│ 5 │ 0.6765845269058688 │
│ 6 │ 0.6187743088061717 │
│ 7 │ 0.5157538308846997 │
│ 8 │ 0.46555646525988514 │
│ 9 │ 0.38054565223528175 │
│ 10 │ 0.33107496732400704 │
│ 11 │ 0.3348125096349211 │
│ 12 │ 0.30271050596599436 │
│ 13 │ 0.30352471441053985 │
│ 14 │ 0.3696661578004031 │
│ 15 │ 0.4156042590776569 │
│ 16 │ 0.4608111994637522 │
│ 17 │ 0.53172225574472 │
│ 18 │ 0.6594949154650602 │
│ 19 │ 0.6714790771824638 │
│ 20 │ 0.7728233018044018 │
│ 21 │ 0.8208884212567936 │
│ 22 │ 0.924437922104001 │
│ 23 │ 0.9497900622814304 │
└─────────┴─────────────────────┘
Next we will simulate the accumulation of minerals as implied by each fortnightly sample.
let oceanConcentration = 0;
console.log(`
Initial concentration = ${oceanConcentration} mgs/ltr
`);
oceanConcentration = mineralsPerCycle.reduce(
waterCycle,
oceanConcentration);
console.log(`
Final concentration = ${oceanConcentration} mgs/ltr
`);
function waterCycle(currentConcentration, cycleConcentration) {
return currentConcentration + cycleConcentration;
}
/* Output
Initial concentration = 0 mgs/ltr
Final concentration = 14.945932946637733 mgs/ltr
*/
Note in the above code how we have simplified the calling of the reduce method by firstly referencing the callback function and secondly referencing a variable for the initial value of the accumulator.
function waterCycle(currentConcentration, cycleConcentration) {
const newConcentration = currentConcentration +
cycleConcentration;
console.log(`${cycleConcentration} + ${
currentConcentration} = ${
newConcentration}`);
return newConcentration;
}
If we replace the waterCycle reducer for the above version we can see the concentration 'accumulate' with each sample.
0.89801916280756 + 0 = 0.89801916280756
0.9567662790947499 + 0.89801916280756 = 1.85478544190231
0.9325939089002321 + 1.85478544190231 = 2.787379350802542
0.8992754278881672 + 2.787379350802542 = 3.686654778690709
0.7532231143389726 + 3.686654778690709 = 4.439877893029681
0.6765845269058688 + 4.439877893029681 = 5.11646241993555
0.6187743088061717 + 5.11646241993555 = 5.735236728741722
0.5157538308846997 + 5.735236728741722 = 6.2509905596264215
0.46555646525988514 + 6.2509905596264215 = 6.716547024886307
0.38054565223528175 + 6.716547024886307 = 7.097092677121588
0.33107496732400704 + 7.097092677121588 = 7.428167644445595
0.3348125096349211 + 7.428167644445595 = 7.762980154080516
0.30271050596599436 + 7.762980154080516 = 8.06569066004651
0.30352471441053985 + 8.06569066004651 = 8.369215374457049
0.3696661578004031 + 8.369215374457049 = 8.738881532257452
0.4156042590776569 + 8.738881532257452 = 9.154485791335109
0.4608111994637522 + 9.154485791335109 = 9.61529699079886
0.53172225574472 + 9.61529699079886 = 10.14701924654358
0.6594949154650602 + 10.14701924654358 = 10.806514162008641
0.6714790771824638 + 10.806514162008641 = 11.477993239191106
0.7728233018044018 + 11.477993239191106 = 12.250816540995508
0.8208884212567936 + 12.250816540995508 = 13.071704962252301
0.924437922104001 + 13.071704962252301 = 13.996142884356303
0.9497900622814304 + 13.996142884356303 = 14.945932946637733
Unsurprisingly the callback function of the reduce method (parameter one) is called a reducer. However, one thing that confuses matters is that the callback is not called a reducer because it 'reduces' an array of (potentially) many items into a single value (it might not). It is called a reducer because (for each element of the array) it takes two arguments (primarily, we will expand on this point later) the accumulator and the element. It then reduces them to a single value to form the new accumulator.
On the point of how many parameters the Array.reduce
method expects, it actually expects up to four:
- The accumulator - the in-bound reduced value
- The item - the element from the array to be reduced
- The index of the element of the array (not often used)
- The array being processed (not reduced), very rarely used.
We will explore the fact the output might not be a single value in the next section.
Reduce, the root of many methods
The reduce
method is capable of many operations (we will explore this later) and once mastered it is easy to find opportunities to use it but there are usually better options.
The map
method
Like reduce
the map
method takes a callback but in this case it is a mapping function that takes a value from the array and produces a new value, one for one. The new array that is created will be the same size as the input array.
If we use map
in the following fashion,
function celsiusToFahrenheit(degCelsius) {
return (degCelsius * 9) / 5 + 32;
}
console.table([-40, 0, 16, 100].map(celsiusToFahrenheit));
a table of temperatures in Fahrenheit will be presented on the console for each of the Celsius temperatures in the input array.
This can also be written using the reduce
method as follows using the same mapping function.
console.table([-40, 0, 16, 100].reduce((acc, celsius) =>
[...acc, celsiusToFahrenheit(celsius)], []));
The filter
method
We can do something similar to reproduce the filter
method using a predicate function such as:
const greaterThanFifty = (value) => value > 50;
console.table([20, 40, 60, 80, 100].filter(greaterThanFifty));
// 60, 80, 100
Now with the reduce
method.
console.table([20, 40, 60, 80, 100].reduce((acc, val) =>
greaterThanFifty(val) ? [...acc, val] : acc, []));
In both examples using reduce
make for a longer and slightly more complicated solution. However, reduce
can combine both operations in a single pass.
console.table(
[-40, 0, 16, 100].reduce((acc, celsius) => {
const fahrenheit = celsiusToFahrenheit(celsius);
return greaterThanFifty(fahrenheit) ?
[...acc, fahrenheit] : acc;
}, [])
); // [60.8, 212]
In fact the output of a reduce
does not even have to be an array.
console.table(
[-40, 0, 16, 100].reduce(
(acc, celsius) => ({ ...acc, [celsius]:
celsiusToFahrenheit(celsius) }),
{}
)
); // {'16': 60.8, '100': 212}
The above example will produce an object containing a mapping of the Celsius to Fahrenheit temperatures but only for those Fahrenheit temperatures greater than 50 degrees.
Do's and Don'ts
In this final section I would like to offer some advice for using the reduce
method.
Do's
- Use
reduce
when converting from an array to another data structure. - Consider using the
reduce
method when the operation is a combination ofmap
ping andfilter
ing.
Don'ts
- Do not use
reduce
when there are better alternative methods. They well usually perform better as they are implemented within the JavaScript engine. - Don't be scared to at least explore using the
reduce
method when appropriate.
Reduce's evil twin
The reduce
method is not likely to be a method you use every day but knowing it exists and what is capable of adds another tool to your toolbox.
An even less used array method in reduce's (not so) evil twin reduceRight
, which I think is fairly obvious what it does. Reduce
processes the items in the array from left to right (in index order), reduceRight
processes the array from right to left (in inverse index order). But reduceRight
is not equivalent to Array.reverse().reduce()
because the third parameter of the reducer function will decrease not increase as the method traverses the array.
Top comments (0)