while
. Before going into details, let's get a little ready. Namely, we will create a function and an array with which we will work.
// oodlify :: String -> String function oodlify(s) { return s.replace(/[aeiou]/g, 'oodle'); } const input = [ 'John', 'Paul', 'George', 'Ringo', ];
oodlify
function. If you use the while
to solve this problem, you get the following:
let i = 0; const len = input.length; let output = []; while (i < len) { let item = input[i]; let newItem = oodlify(item); output.push(newItem); i = i + 1; }
i
to track the current processed element of the array. It is necessary to initialize it with zero and increment by one in each iteration of the loop. In addition, you need to compare it with the length of the array, with len
, in order to know when to stop working.
for
loop. Such a cycle will solve the same problem as follows:
const len = input.length; let output = []; for (let i = 0; i < len; i = i + 1) { let item = input[i]; let newItem = oodlify(item); output.push(newItem); }
for
loop is a useful design, since thanks to it all standard auxiliary operations with the counter are moved to the top of the block. Using while
, it is easy to forget about the need to increment counter i
, which will start an infinite loop. Definitely, the for
loop is much more convenient than the while
. But let's slow down and take a look at what our code is trying to achieve. We want to process, using the oodlify()
function, each element of the array and put what we have got into the new array. By itself, the counter used to access the elements of the array does not interest us.
for…of
loop of for…of
In each iteration of such a loop, the next element of the array is provided. It looks like this:
let output = []; for (let item of input) { let newItem = oodlify(item); output.push(newItem); }
for…of
loop takes all the auxiliary operations.
for…of
loops everywhere instead of for
loops, this will be a good step forward by simplifying the code. But ... we can go further.
for…of
loop looks much cleaner than the for
loop, but with it there are quite a few auxiliary elements in the code. So, you need to initialize the output
array and call the push()
method at each iteration of the loop. The code can be made even more compact and expressive, but before doing this, let's expand the demo task a bit. What if you need to process two arrays using the oodlify()
function?
const fellowship = [ 'frodo', 'sam', 'gandalf', 'aragorn', 'boromir', 'legolas', 'gimli', ]; const band = [ 'John', 'Paul', 'George', 'Ringo', ];
let bandoodle = []; for (let item of band) { let newItem = oodlify(item); bandoodle.push(newItem); } let floodleship = []; for (let item of fellowship) { let newItem = oodlify(item); floodleship.push(newItem); }
function oodlifyArray(input) { let output = []; for (let item of input) { let newItem = oodlify(item); output.push(newItem); } return output; } let bandoodle = oodlifyArray(band); let floodleship = oodlifyArray(fellowship);
function izzlify(s) { return s.replace(/[aeiou]+/g, 'izzle'); }
oodlifyArray()
function does not help. However, if we create another similar function, this time izzlufyArray()
, we will repeat it again. Nevertheless, let's create such a function and compare it with oodlifyArray()
:
function oodlifyArray(input) { let output = []; for (let item of input) { let newItem = oodlify(item); output.push(newItem); } return output; } function izzlifyArray(input) { let output = []; for (let item of input) { let newItem = izzlify(item); output.push(newItem); } return output; }
function map(f, a) { let output = []; for (let item of a) { output.push(f(item)); } return output; }
function map(f, a) { if (a.length === 0) { return []; } return [f(a[0])].concat(map(f, a.slice(1))); }
map
function does is a task so common that JavaScript has a built-in map()
method. If you use this method, the code will be like this:
let bandoodle = band.map(oodlify); let floodleship = fellowship.map(oodlify); let bandizzle = band.map(izzlify); let fellowshizzle = fellowship.map(izzlify);
oodlify
and izzlify
. These functions should not know anything about arrays or loops. There is another function - map
, which works with arrays. At the same time, she does not care at all what type of data is in the array, or what we want to do with this data. It simply performs any function passed to it, passing it the elements of the array. Instead of mixing everything, we separated the processing of strings and the processing of arrays. That is why the final code was simpler.
map
function is very useful, but it does not overlap all the options for processing arrays that use loops. It is good in cases when, on the basis of a certain array, you need to create a new one having the same length. But what if we need, for example, to stack all the elements of a numeric array? Or if you need to find the shortest line in the list? Sometimes it is required to process an array and, in fact, form a single value based on it.
const heroes = [ {name: 'Hulk', strength: 90000}, {name: 'Spider-Man', strength: 25000}, {name: 'Hawk Eye', strength: 136}, {name: 'Thor', strength: 100000}, {name: 'Black Widow', strength: 136}, {name: 'Vision', strength: 5000}, {name: 'Scarlet Witch', strength: 60}, {name: 'Mystique', strength: 120}, {name: 'Namora', strength: 75000}, ];
for…of
loop:
let strongest = {strength: 0}; for (let hero of heroes) { if (hero.strength > strongest.strength) { strongest = hero; } }
strongest
variable. In order to see more clearly the pattern of working with an array, let's imagine that we still need to figure out the total strength of all the characters.
let combinedStrength = 0; for (let hero of heroes) { combinedStrength += hero.strength; }
function greaterStrength(champion, contender) { return (contender.strength > champion.strength) ? contender : champion; } function addStrength(tally, hero) { return tally + hero.strength; } const initialStrongest = {strength: 0}; let working = initialStrongest; for (hero of heroes) { working = greaterStrength(working, hero); } const strongest = working; const initialCombinedStrength = 0; working = initialCombinedStrength; for (hero of heroes) { working = addStrength(working, hero); } const combinedStrength = working;
reduce
function that implements the detected pattern.
function reduce(f, initialVal, a) { let working = initialVal; for (let item of a) { working = f(working, item); } return working; }
map
function template, the reduce
function template is so widely distributed that JavaScript provides it as an embedded method of arrays. Therefore, your method, if there is no special reason for this, is not necessary to write. Using the standard method, the code will look like this:
const strongestHero = heroes.reduce(greaterStrength, {strength: 0}); const combinedStrength = heroes.reduce(addStrength, 0);
reduce
function, written by ourselves, then, in general, the code would have turned out more. However, our goal is not to write short code, but to reduce its complexity. So, have we reduced the complexity of the program? I can say that reduced. We have separated the loop code from the code that processes the elements of the array. As a result, individual sections of the program have become more independent. The code is easier.
reduce
function may seem rather primitive. Most examples of using this function demonstrate simple things, like the addition of all the elements of numeric arrays. However, it is not said anywhere that the value that the reduce
returns should be of a primitive type. This can be an object or even another array. When I first realized it, it struck me. You can, for example, write an implementation of a mapping or filtering an array using reduce
. I suggest you try it yourself.
map
function to perform operations on each element of the array. There is a reduce
function, which allows you to compress the array to a single value. But what if you need to extract only some of its elements from an array? To explore this idea, expand the list of superheroes, add some additional data there:
const heroes = [ {name: 'Hulk', strength: 90000, sex: 'm'}, {name: 'Spider-Man', strength: 25000, sex: 'm'}, {name: 'Hawk Eye', strength: 136, sex: 'm'}, {name: 'Thor', strength: 100000, sex: 'm'}, {name: 'Black Widow', strength: 136, sex: 'f'}, {name: 'Vision', strength: 5000, sex: 'm'}, {name: 'Scarlet Witch', strength: 60, sex: 'f'}, {name: 'Mystique', strength: 120, sex: 'f'}, {name: 'Namora', strength: 75000, sex: 'f'}, ];
for…of
:
let femaleHeroes = []; for (let hero of heroes) { if (hero.sex === 'f') { femaleHeroes.push(hero); } } let superhumans = []; for (let hero of heroes) { if (hero.strength >= 500) { superhumans.push(hero); } }
if
blocks. What if to take out these blocks in functions?
function isFemaleHero(hero) { return (hero.sex === 'f'); } function isSuperhuman(hero) { return (hero.strength >= 500); } let femaleHeroes = []; for (let hero of heroes) { if (isFemaleHero(hero)) { femaleHeroes.push(hero); } } let superhumans = []; for (let hero of heroes) { if (isSuperhuman(hero)) { superhumans.push(hero); } }
true
or false
sometimes called predicates. We use the predicate to decide whether to save the next value from the heroes
array in the new array.
function filter(predicate, arr) { let working = []; for (let item of arr) { if (predicate(item)) { working = working.concat(item); } } return working; } const femaleHeroes = filter(isFemaleHero, heroes); const superhumans = filter(isSuperhuman, heroes);
map
and reduce
functions, JavaScript has the same thing that we wrote here, in the form of the standard filter
method of the Array
object. Therefore, it is not necessary to write your own function, without any obvious need. Using standard tools, the code will look like this:
const femaleHeroes = heroes.filter(isFemaleHero); const superhumans = heroes.filter(isSuperhuman);
for…of
loop? Think about how this can be used in practice. We have a task of the form: "Find all the heroes who ...". As soon as it became clear that the problem can be solved using the standard filter
function, the work is simplified. All that is needed is to inform this function exactly which elements interest us. This is done through the writing of one compact function. You do not have to worry about handling arrays, nor about additional variables. Instead, we write a tiny predicate function and the problem is solved.
filter
allows you to express more information in a smaller amount of code. You do not need to read the entire standard loop code in order to understand what we are filtering. Instead, everything you need to understand is described directly when the method is called.
filter
function can be used to solve this problem:
function isBlackWidow(hero) { return (hero.name === 'Black Widow'); } const blackWidow = heroes.filter(isBlackWidow)[0];
filter
method scans every element of an array. However, it is known that in the array only one hero is called Black Widow, which means that you can stop after this hero is found. At the same time, predicate functions are convenient to use. Therefore, we write a find
function that finds and returns the first matching element:
function find(predicate, arr) { for (let item of arr) { if (predicate(item)) { return item; } } } const blackWidow = find(isBlackWidow, heroes);
const blackWidow = heroes.find(isBlackWidow);
find
function, the task of searching for a specific element comes down to one question: “By what sign can we determine that the search element was found?” You don’t need to worry about details.
reduce
and filter
functions. Using the spread operator from ES2015 allows you to conveniently combine two functions that are used to fold an array into one. Here is a modified code snippet that allows you to traverse the array only once:
function processStrength({strongestHero, combinedStrength}, hero) { return { strongestHero: greaterStrength(strongestHero, hero), combinedStrength: addStrength(combinedStrength, hero), }; } const {strongestHero, combinedStrength} = heroes.reduce(processStrength, {strongestHero: {strength: 0}, combinedStrength: 0});
map
, reduce
, filter
, or find
.
Source: https://habr.com/ru/post/322850/