.map()
and .filter()
take just one callback argument and allow you to solve simple problems. But it .reduce()
that the .reduce()
method causes certain difficulties for many. To understand it is a little more difficult..reduce()
creates a lot of problems. In part, this is due to the fact that many manuals demonstrate the use of .reduce()
only when processing numbers. That's why I wrote about how many tasks that do not imply the execution of arithmetic operations can be solved using .reduce()
. But what if you absolutely need to work with numbers?.reduce()
looks like the calculation of the arithmetic average of the elements of an array. At first glance it seems that there is nothing special in this task. But it is not so simple. The fact is that before you calculate the average, you need to find the following indicators: function average(nums) { return nums.reduce((a, b) => (a + b)) / nums.length; }
.reduce()
in real projects.found
property of the corresponding objects is false
) and find the average rating of the popularity of expressions. Here is what similar data might look like (it is taken from here ): const victorianSlang = [ term: 'doing the bear', found: true, popularity: 108, }, term: 'katterzem', found: false, popularity: null, }, term: 'bone shaker', found: true, popularity: 609, }, term: 'smothering a parrot', found: false, popularity: null, }, term: 'damfino', found: true, popularity: 232, }, term: 'rain napper', found: false, popularity: null, }, term: 'donkey's breakfast', found: true, popularity: 787, }, term: 'rational costume', found: true, popularity: 513, }, term: 'mind the grease', found: true, popularity: 154, }, ];
.reduce()
method will not be used. If you have not come across methods for iterating arrays before, then I hope the analysis of this example will clarify the situation for you a bit. let popularitySum = 0; let itemsFound = 0; const len = victorianSlang.length; let item = null; for (let i = 0; i < len; i++) { item = victorianSlang[i]; if (item.found) { popularitySum = item.popularity + popularitySum; itemsFound = itemsFound + 1; } const averagePopularity = popularitySum / itemsFound; console.log("Average popularity:", averagePopularity);
itemsFound
and itemsFound
. The first variable, popularitySum
, stores the overall popularity of expressions. And the second variable, itemsFound
, (that's a surprise) stores the number of expressions found.len
constant and the item
variable, which will be useful to us when traversing the array.for
loop, counter i
incremented until its value reaches the index value of the last element of the array.victorianSlang[i]
construction.popularitySum
.itemsFound
.popularitySum
by itemsFound
..filter()
method..map()
method is suitable..reduce()
. // // ---------------------------------------------------------------------------- function isFound(item) { return item.found; }; function getPopularity(item) { return item.popularity; } function addScores(runningTotal, popularity) { return runningTotal + popularity; } // // ---------------------------------------------------------------------------- // , . const foundSlangTerms = victorianSlang.filter(isFound); // , . const popularityScores = foundSlangTerms.map(getPopularity); // . , // , reduce , 0. const scoresTotal = popularityScores.reduce(addScores, 0); // . const averagePopularity = scoresTotal / popularityScores.length; console.log("Average popularity:", averagePopularity);
addScore
function, and the line where .reduce()
called. Notice that the addScore
takes two parameters. The first, runningTotal
, is known as a battery. It stores the sum of the values. Its value changes every time when we iterate over the array and execute the return
. The second parameter, popularity
, is a separate element of the array that we process. At the very beginning of the array addScore
, the return
in addScore
has never been executed yet. This means that the runningTotal
value runningTotal
not yet been set automatically. Therefore, by calling .reduce()
, we pass this value to the method that needs to be written to runningTotal
at the very beginning. This is the second parameter passed to .reduce()
..filter()
, .map()
and .reduce()
. This approach to solving such problems is more expressive. These array methods are much more complete than a cycle can do; they inform us of the intent contained in the code.foundSlangTerms
and popularityScores
. In our case, this solution is quite acceptable. But what if we set a more complex goal for the device code? It would be nice if we could use the fluent interface design pattern in the program. With this approach, we could chain the calls of all functions and be able to do without intermediate variables. However, there is one problem waiting for us. Pay attention to the fact that we need to get the value of popularityScores.length
. If we are going to combine everything into a chain, then we need some other way to find the number of elements in the array. The number of elements in the array plays the role of the divisor in calculating the average value. Let's see if we can change the approach to solving the problem so that everything can be done by combining the method calls into a chain. We will do this by tracking two values when sorting the elements of an array, that is, using a “double battery”. // // --------------------------------------------------------------------------------- function isFound(item) { return item.found; }; function getPopularity(item) { return item.popularity; } // , return, . function addScores({totalPopularity, itemCount}, popularity) { return { totalPopularity: totalPopularity + popularity, itemCount: itemCount + 1, }; } // // --------------------------------------------------------------------------------- const initialInfo = {totalPopularity: 0, itemCount: 0}; const popularityInfo = victorianSlang.filter(isFound) .map(getPopularity) .reduce(addScores, initialInfo); // . const {totalPopularity, itemCount} = popularityInfo; const averagePopularity = totalPopularity / itemCount; console.log("Average popularity:", averagePopularity);
addScrores
, we update the total value of the popularity rating and the number of elements. It is important to note that these two values are represented as a single object. With this approach, we can "deceive" the system and store two entities within the same return value.addScrores
function was a bit more complex than the function with the same name of the previous example. But now it turns out that we can use a single chain of method calls to perform all operations with an array. As a result of array processing, a popularityInfo
object is obtained, which stores everything needed to find the average. This makes the call chain neat and simple.curry()
and compose()
. If you want to delve into this topic - take a look at this material on functional programming in JavaScript, and, in particular, on the third part of the series, in which it is included.compose()
function, without using variables. We call it “programming without using fine notation” or “implicit programming”. In order to write such programs will need a lot of auxiliary functions. // // ---------------------------------------------------------------------------- const filter = p => a => a.filter(p); const map = f => a => a.map(f); const prop = k => x => x[k]; const reduce = r => i => a => a.reduce(r, i); const compose = (...fns) => (arg) => fns.reduceRight((arg, fn) => fn(arg), arg); // - "blackbird combinator". // : https://jrsinclair.com/articles/2019/compose-js-functions-multiple-parameters/ const B1 = f => g => h => x => f(g(x))(h(x)); // // ---------------------------------------------------------------------------- // sum, . const sum = reduce((a, i) => a + i)(0); // . const length = a => a.length; // . const div = a => b => a / b; // compose() . // compose() . const calcPopularity = compose( B1(div)(sum)(length), map(prop('popularity')), filter(prop('found')), ); const averagePopularity = calcPopularity(victorianSlang); console.log("Average popularity:", averagePopularity);
compose()
function. If you read its contents from the bottom up, it turns out that the calculations begin with filtering the array by the property of its found
elements. Then we retrieve the property of the popularity
elements with map()
. After that we use the so-called “ blackbird combinator ”. This entity is represented as a function B1
, which is used to perform two computation passes on one set of input data. To better understand this, take a look at these examples: // , , : const avg1 = B1(div)(sum)(length); const avg2 = arr => div(sum(arr))(length(arr)); const avg3 = arr => ( sum(arr) / length(arr) ); const avg4 = arr => arr.reduce((a, x) => a + x, 0) / arr.length;
.reduce()
method have something in common. They are based on breaking the problem into small fragments. These fragments are then assembled in various ways. Analyzing these solutions, you may have noticed that in them we go around the array three times. There is a feeling that it is ineffective. It would be nice if there was a way to process the array and return the result in one pass. Such a method exists, but its application will require resort to mathematics.n
numbers can be found using the following formula:n + 1
numbers, the same formula is suitable, but in another record: // // ---------------------------------------------------------------------------- function averageScores({avg, n}, slangTermInfo) { if (!slangTermInfo.found) { return {avg, n}; return { avg: (slangTermInfo.popularity + n * avg) / (n + 1), n: n + 1, }; } // // ---------------------------------------------------------------------------- // . const initialVals = {avg: 0, n: 0}; const averagePopularity = victorianSlang.reduce(averageScores, initialVals).avg; console.log("Average popularity:", averagePopularity);
.reduce()
..filter()
.map()
, — .reduce()
.Source: https://habr.com/ru/post/458030/
All Articles