One of the most important tools in programming is loops. They are useful in cases where you need to do something a certain number of times.

If there is such a task, for example, to display the line “hello everyone” 1000 times. Then, without using a cycle, firstly it will take a lot of time and secondly, it will not look very nice. That's why you need to know cycles perfectly because they are used very, very often.

There are four loops in programming, these are while, do-while, for and foreach. Each of them has its own syntax and each is used in certain cases.

The most commonly used loops are for and foreach, followed by while, and the do-while loop is very rare.

And we'll start with the while loop.

The syntax of the while loop is as follows:


First, we declare the variable i, which is a counter, and inside the loop we increment this counter. Inside parentheses We write the condition for entering/exiting the loop.

Comment! Write the exit condition correctly, otherwise it may happen an infinite loop and then the script will freeze. Such a cycle can happen if, for example, we simply write true in the exit condition.

For example, let's print the string "Hello everyone!" 10 times.

Var i = 0; while(i "); i++; )

The variable i can start from 0 or 1 or from any other number.

The exit condition is at the same time the entry condition. The loop works as follows: First, it checks if the variable i is less than 10, and if the condition is true, then we enter the loop, otherwise, not. In this case, if the variable i is equal to 30, for example, then the loop will not execute, because 30 is not less than 10.

We entered the cycle, printed the line “Hello everyone”, incremented the counter and again go to the condition, where we again check if the value of the variable i is less than 10, then we enter the cycle, otherwise we exit it. And this happens until the moment when the entry condition becomes false, that is, the value of the variable i will be 10. 10 is not less than 10, so we no longer enter the loop, but move on.

Comment! Don't forget to increment the counter (i++), otherwise you'll end up with an infinite loop.

We've dealt with the while loop, now let's move on to the do-while loop.

The syntax of the do-while loop is as follows:


The difference between a while loop and a do-while loop is that a do-while loop can be executed at least once, regardless of the condition, whereas a while loop will not be executed at all if the condition is false.

Comment! Just like the while loop, don't forget to increment the i counter.

Let's move on to practice. For example, let's calculate the product of numbers from 1 to 10.

Var i = 1; var production = 1; do( production *= i; i++; )while(i

The result will be the number 3628800. At the first step, we immediately entered the loop, despite its condition, where the operation production *= i was performed (this is the same as production = production * 1). Then we increment the counter. After incrementation, it has a value of 2. And at the end we check the condition, if the counter value is less than or equal to 10, then we go to the next iteration of the loop, otherwise we exit the loop and move on.

for loop

As I wrote above, the for loop occurs quite often, so you need to know it very well.

for loop syntax next:


For a better understanding, let's solve a simple problem. Let's say we need to calculate the sum of numbers from 1 to 1000 using a for loop.

Var sum = 0; for(var i = 1; i

We save the document, open it in the browser and see that the result is 500500.

Comment! If there is only one statement in a loop, then braces optional.

To demonstrate, let’s display some string on the screen 5 times, for example “Hello!”

For(var i = 1; i

Comment! After the loop is executed, the last value remains in variable i.

Now let's solve the problem a little more complicated, for example we need to print the string "Hello" 100 times. And so that all this does not appear in one row, then after every 10th iteration, we move to a new line. And at the end we will print the value of the variable i.

For(var i = 1; i<= 100; i++){ document.write("привет!"); if(i % 10 == 0)  document.write("
"); ) document.write("

Variable i = " + i + "

"); // i = 101

for loop each Typically used to iterate over objects and arrays. Therefore, I will talk about it in the article describing working with arrays.

break statement is intended to forcefully exit the loop.

Continue operator allows you to interrupt the current iteration of the loop and move on to the next one.

For a better understanding, we will also solve a simple problem. Let's say we want to calculate the sum of odd numbers from 1 to 20. And when we reach the 15th iteration, we will exit the loop.

Var sum = 0; for(var i = 1; i<= 20; i++){ //Пропускаем текущею итерацию цикла if(i % 2 == 0) continue; summa += i; //Выходим совсем из цикла. if(i == 15) break; document.write(i + ". Итерация
"); ) document.write("

summa= " + summa + "

"); //summa = 64

We save the document, open it in the browser and look at the result.

To practice, try changing the written script so that it calculates the sum of even numbers.

This ends this article. Now you know syntax of while, do-while, for loops and how to work with them. We also met break and continue statements.

The history of the formation of modern programming language syntax is akin to understanding the processes of formation of the Universe. What and how it was in the beginning... But now everything is simple and accessible.

Ultimately, the algorithm is always a sequential chain of commands. Parallelism in programming is a set of somehow combined sequences. sequential or parallel chain of command has never been more practical. Labels, transitions and conditions - everything was enough for any solution. Functional languages ​​have made these ideas irrelevant, but the need to repeat sections of code remains.

Browser: DOM, its language + server

In JavaScript, loops remain, although functional ideas have acquired a special meaning. There may be something left over from "Lisp" and "Prolog", but most likely the area where JavaScript lives led to what is there, but it is doubtful that this is the last solution.

JavaScript runs inside the browser, which receives the page, parses it into the DOM, and runs the first script. All other pages, including those loaded on this one, are the work of the developer manipulating the language, through which the code on the server can be called and the result obtained using the AJAX mechanism.

The browser executes JavaScript code, which can use browser objects, including the one that transmits information to the server and receives a response, which can be HTML markup, styles, and the code itself. The response can be represented by arrays and objects. The point of using loops in JavaScript is lost, there are plenty of opportunities to do without them, and the risk of hanging the browser with an endless sequence of commands is not the best solution.

Loops themselves are present in most JavaScript syntactic constructs; the developer can supplement the standard constructs with his own functions.

JavaScript's Position in Code Space

A modern programmer does not even think that the while, do while, ...) he uses is ultimately a series of processor cycles, a simple sequence of binary operations, interrupted by counter checks, that is, conditions.

There is no loop as such at the machine language level: there is a combination of ordinary commands, conditional operations and transitions. One level up, no matter what tool is used to develop the browser and JavaScript interpreter, there will definitely be loops. Moreover, “pieces of code” will be presented at different times and by different generations of programmers. The floor above is the JavaScript "building". The syntax of which offers modern JavaScript loops.

JS is a wonderful language: practical, modern and full-featured. The syntax of this tool includes all the constructs that have stood the test of time and have become the unshakable foundation of any algorithm. But are cycles really necessary? Progress in programming often asked itself questions of a fundamental nature, but only in some cases did it find a solution.

Objective grounds

A cycle can have only two options: by condition or by counter, but essentially (at the lowest level) any cycle is only by condition. In some languages ​​there is a cycle "for each". In JavaScript, foreach loops are represented by the prop in object construct, but you can use the array.forEach(...) variant.

In any case, there are two options: the programmer who ultimately executes all the programmer’s algorithms, even those writing in interpretive languages, has no other options for repeating the chain of commands: he can execute something again until:

  • the counter counts;
  • as long as the condition is met.

JavaScript is a typical interpreter. Its peculiarity: it operates inside the browser, uses its objects and allows you to execute algorithms on the client side, both when the page is loaded into the browser and during its operation.

Simple loop one by one

In JavaScript, foreach loops look like applying a function to an array:

The use of such cycles does not pose any difficulties. Formally, there is no cycle as such. There is a sequential function call to the elements of the array.

Loop on a counter

For loops look more familiar in JavaScript:

Here the counter is a variable whose value changes according to a formula and a condition is a sign of the end of the cycle. The formula and condition do not need to include a loop variable. But control over the moment the cycle ends is completely determined by their content.

Conditional loops

JavaScript offers an option with while depending on when the condition needs to be checked. If the body of the loop may not be executed even once, this is one thing; if the body must be executed at least once, this is another:

In the first case, when interpreting the while construct, JavaScript first checks the condition, and if it is true, it executes the loop. In the second case, the loop will be executed first. If, as a result of changing the variables specified in the design condition do while, it will evaluate to false and the loop will stop executing.

Massive combinations of simple algorithms

The main task (component part) of any algorithm is to find, and only then make a decision about what to do next. The most primitive search option is accessing a variable, the result is obtained directly. If there are many variables, or it has many values ​​(an array), then to select a value it is necessary to find something that will determine the further behavior of the script.

Such a simple doctrine made a loop with a counter in JavaScript is a kind of panacea for all problems. Modern computers are fast. There is plenty of time to run scripts in the browser, there is no need to rush. It’s easier than ever to go through something for the sake of something. As a result, on J AvaScript for loops have become very popular.

There seems to be nothing bad about this. But behind this approach, the essence for which this or that algorithm is written is easily lost. Data is not meaningless. Everything for which any program is written has meaning. Excessive use on J avaScript for loops, the developer may not recognize the required entity and not create an adequate algorithm.

Functionality, another reflection of reality

Applying JavaScript loops, examplescode of the same type can be represented by functions - the algorithm will immediately transform, the main body of the script will decrease in size, everything will become readable and understandable.

This is not a radically new solution, but in essence it does not go beyond other language constructs. In particular, J AvaScript loops can be found in the classic split() function:

var cResult = "9,8,7,6,5,4" ;
var aResult = cResult .split ( "," );

There is no loop here, but how else can this be accomplished other than by looking for the "," symbol and using it to separate one number from another.

Abstracting from how this is implemented inside the split() function, you can supplement JavaScript with your own functionality that uses loops, which is more convenient from a usage point of view. It is important that this approach leads to the development of functionality for each task, respectively, but the general will still be with this approach.

These functions allt(), padc(), padl() and padr() are something that JavaScript doesn't have, but sometimes you need to remove spaces from a string or align the length of a string on the left, right, or both sides. The bodies of these functions contain JavaScript loops. Simple, accessible, and the algorithm that uses this will never crash.

Variants of functions for converting numbers from hexadecimal to 10th number system and back, more simply put, from one data format to another, are performed here using do while loops. Very compact and efficient language syntax.

Correct cycles - a reflection of reality

JavaScript is no match for other programming languages ​​and does not differ in the variety of versions, and most importantly, it strives not to change the syntax, but to develop and expand it.

The mindset of a programmer using JS is different from the thinking of a PHP programmer (in particular, and other languages ​​in general, except that “Prolog” and its followers are not included in the general mainstream), when the algorithm is not limited to variables, arrays, assignment operators, and cyclic constructs.

If we imagine that there are no cycles, but the problem needs to be solved, then the simplest option (blindfold) is to assume that the program processes data that is a point or a system of points in the information space. What a point is and what a system of points is is a matter of a specific subject area. For a programmer, this thesis means: there is a simple given and there is a collection of simple data. Naturally, a simple datum of one level will be a system for a level below, and a point for a level above.

With this approach, the point's concern is to manifest its essence through its methods. When a point is in a supersystem, then the function of the system is to manifest its essence as a collection of essences of the points included in it.

This approach is as old as the idea of ​​programming languages, but has not yet been adequately reflected in programming. Many programmers think correctly, but the result of their creativity leaves much to be desired.

It helps to wear a blindfold sometimes to see the world!

Cycles

To understand the effect of conditional statements, we suggested imagining them as forks in the road along which the JavaScript interpreter moves. Loops can be thought of as a U-turn in the road that takes you back, forcing the interpreter to go through the same piece of code over and over again.

JavaScript has four loops: while, do/while, for, and for/in. One of the following subsections is devoted to each of them. One common use of loops is to traverse the elements of an array.

while loop

The if statement is the basic conditional statement in JavaScript, and the basic loop for JavaScript is the while loop. It has the following syntax:

while (expression) (instruction)

The while loop begins by evaluating an expression. If this expression evaluates to false, the interpreter skips the statement that makes up the body of the loop and moves on to the next statement in the program. If the expression evaluates to true, then the statement that forms the body of the loop is executed, then control is transferred to the beginning of the loop and the expression is evaluated again. In other words, the interpreter executes the loop body instruction over and over again as long as the value of the expression remains true. Please note that it is possible to create an infinite loop using the while(true) syntax.

Typically you don't want the JavaScript interpreter to perform the same operation over and over again. In almost every loop, with each iteration of the loop, one or more variables change their values. Because the variable changes, what the instruction does may differ each time it passes through the loop body.

Additionally, if the variable(s) being modified are present in the expression, the value of the expression may change with each pass of the loop. This is important because otherwise the expression whose value was true will never change and the loop will never end! Below is an example of a while loop that prints the numbers from 0 to 9:

Var count = 0; while (count

As you can see, the count variable is set to 0 at the beginning, and then its value is incremented each time the body of the loop is executed. After the loop has been executed 10 times, the expression will return false (that is, the count variable is no longer less than 10), the while statement will end, and the interpreter will move on to the next statement in the program. Most loops have counter variables similar to count. Most often, the variables named i, j, and k act as loop counters, although in order to make the program code more understandable, you should give the counters more descriptive names.

do/while loop

A do/while loop is similar in many ways to a while loop, except that the loop expression is tested at the end rather than at the beginning. This means that the body of the loop is always executed at least once. This instruction has the following syntax:

do (statement) while (expression);

The do/while loop is used less frequently than its sister while loop. The fact is that in practice, the situation when you are sure in advance that you will need to execute the body of the loop at least once is somewhat unusual. Below is an example of using a do/while loop:

Function printArray(a) ( var len = a.length, i = 0; if (len == 0) console.log("Empty array"); else ( do ( console.log(a[i]); ) while (++i

There are two differences between a do/while loop and a regular while loop. First, a do loop requires both the do keyword (to mark the start of the loop) and the while keyword (to mark the end of the loop and specify a condition). Second, unlike a while loop, a do loop ends with a semicolon. A while loop does not need to end with a semicolon if the body of the loop is enclosed in curly braces.

for loop

A for loop is a loop construct that is often more convenient than a while loop. The for loop makes it easy to construct loops that follow a pattern common to most loops. Most loops have some kind of counter variable. This variable is initialized before the loop starts and is checked before each iteration. Finally, the counter variable is incremented or otherwise modified at the end of the loop body, just before the variable is checked again. Initialization, verification, and update are the three key operations performed on a loop variable. The for statement makes these three steps explicit part of the loop syntax:

for(initialization; check; increment) (instruction)

Initialize, check, and increment are three expressions (separated by semicolons) that are responsible for initializing, checking, and incrementing a loop variable. Placing them on the first line of the loop makes it easier to understand what the for loop is doing and prevents you from forgetting to initialize or increment a loop variable.

The easiest way to explain the for loop is to show the equivalent while loop:

initialization; while(check) ( instruction; increment; )

In other words, the initialization expression is evaluated once before the loop begins. This expression is typically an expression with side effects (usually an assignment). JavaScript also allows the initialization expression to be a var variable declaration statement, so it is possible to declare and initialize a loop counter at the same time.

The test expression is evaluated before each iteration and determines whether the body of the loop will be executed. If the result of the test is true, the instruction that is the body of the loop is executed. At the end of the loop, the increment expression is evaluated. For this expression to be meaningful, it must be an expression with side effects. Typically this is either an assignment expression or an expression using the ++ or -- operator.

You can also print the numbers 0 through 9 using a for loop, as shown below, as opposed to the equivalent while loop shown in the example earlier:

For (var count = 0; count

Of course, loops can be much more complex than these simple examples, and sometimes multiple variables change in each iteration of the loop. This situation is the only time in JavaScript where the comma operator is often used - it allows you to combine multiple initialization and incrementing expressions into a single expression suitable for use in a for loop:

Var i,j; for (i = 0, j = 0; i

for/in loop

The for/in loop uses the for keyword, but it is completely different from a regular for loop. The for/in loop has the following syntax:

for (variable in object) (statement)

The variable here is usually the name of the variable, but you can also use the var statement, which declares a single variable. The object parameter is an expression that returns an object. And as usual, an instruction is an instruction or block of instructions that forms the body of a loop.

To traverse the elements of an array, it is natural to use a regular for loop:

Var arr = ; for (var i = 0; i

The for/in statement also naturally allows you to traverse the properties of an object:

// Create a new object var obj = (name:"Alex", password:"12345" ); for (var i in obj) ( // Print the value of each object property console.log(obj[i]); )

To execute a for/in statement, the JavaScript interpreter first evaluates the expression object. If it returns null or undefined, the interpreter skips the loop and moves on to the next statement. If the expression returns a simple value, it is converted to an equivalent wrapper object. Otherwise, the expression returns an object. The interpreter then executes one iteration of the loop for each enumerable property of the object. Before each iteration, the interpreter evaluates the value of the expression, stores it in a variable, and assigns it a property name (a string value).

That indentation can be considered an indicator of code complexity (albeit a rather rough one). Indentations themselves are neutral, since they are just a means of formatting text, but the whole point is that they are used to highlight special blocks of programs, for example, control structures. When reading code and encountering an indentation, the programmer is forced to take into account what the indentation indicates, to keep in mind the context in which the selected block exists. This, naturally, is repeated if another special fragment appears in the indented section of code.

If you do not pay attention to the content of the texts, then this is what complex code usually looks like, sections of which look like the letters “V” lying on its side, and simple code, a block of which, if you do not take into account the different lengths of the lines, looks like a rectangle.


The more indentations, the more complex the code usually is.

Constructs that need to be indented will always be in the code; there is no talk of getting rid of them completely. However, we have the power to reduce the complexity of the programs we write by rationally choosing abstractions to solve the problems we face.

Let's take arrays for example. Traditionally, various types of cycles are used to process them. The concepts of “array” and “loop” are inextricably linked in the minds of many programmers. However, the cycle is a very ambiguous construction. Here's what Louis Atentzio writes about loops in his book Functional Programming in JavaScript: “A loop is a rigid control construct that is not easy to reuse and difficult to integrate with other operations. Also, using loops means creating code that changes with each iteration."


Is it possible to get rid of cycles?

The cycle is one of the main structural control structures, and, in fact, we are not going to say that cycles are an evil that needs to be gotten rid of. Our main goal is to reduce the complexity of our own code by minimizing the use of loops when processing arrays. Is this possible? We invite you to find out together.

Cycles

We've already talked about how control constructs like loops add complexity to your code. But why is this so? Let's take a look at how loops work in JavaScript.

There are several ways to organize loops in JS. In particular, one of the basic types of loops is while . Before we delve into the details, let's prepare a little. Namely, we will create a function and an array with which we will work.

// oodlify:: String -> String function oodlify(s) ( return s.replace(//g, "oodle"); ) const input = [ "John", "Paul", "George", "Ringo", ];
So, we have an array, each element of which we are going to process using the oodlify function. If you use a while loop to solve this problem, you will 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; }
Note that we use the i counter to keep track of the currently processed array element. It must be initialized to zero and incremented 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.

This pattern is so common that JavaScript has an easier way to do this: a for loop. Such a loop 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); }
The for loop is a useful construct because it puts all the standard auxiliary counter operations into top part block. Using while , it's easy to forget about the need to increment counter i , which will start an infinite loop. The for loop is definitely much more convenient than the while loop. 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 the result into a new array. The counter itself, used to access array elements, is not of interest to us.

This pattern of working with arrays, which involves performing certain actions on each element, is very common. As a result, ES2015 introduced a new loop design that allows you to forget about the counter. This is a for...of loop. Each iteration of this loop provides the next element of the array. It looks like this:

Let output = ; for (let item of input) ( let newItem = oodlify(item); output.push(newItem); )
The code looks much cleaner. Please note that there is no counter or comparison operation. With this approach, you don't even need to access a specific array element by index. The for…of loop takes care of all the auxiliary operations.

If we complete our study of ways to work with arrays and use for…of loops everywhere instead of for loops, this will already be a good step forward by simplifying the code. But... we can go further.

Transformation of arrays

The for...of loop looks much cleaner than the for loop, but it also has a lot of auxiliary elements in the code. So, you need to initialize the output array and call the push() method in each iteration of the loop. The code can be made even more compact and expressive, but before we do that, let's expand the demo problem a little. 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", ];
A completely obvious solution is to use two loops and process the arrays in them:

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); )
Quite a working option. And code that works is much better than code that does not solve the problem assigned to it. But two very similar pieces of code don't fit particularly well with the DRY software design principle. The code can be refactored to reduce repetition.

Following this idea, we create the following function:

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);
This looks much better, but what if there is another function with which we also want to process array elements?

Function izzlify(s) ( return s.replace(/+/g, "izzle"); )
Now the oodlifyArray() function will not help. However, if we create another similar function, this time izzlufyArray() , we will repeat ourselves again. Still, 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;
The two functions are incredibly similar. Maybe we can summarize the pattern they follow? Our goal is this: “We have an array and a function. We need to get a new array into which the results of processing each element of the original array using the function will be written.” This method of processing arrays is called “mapping” or “transformation” (mapping in English terminology). Functions that perform such operations are usually called "map". This is what our version of such a function looks like:

Function map(f, a) ( let output = ; for (let item of a) ( output.push(f(item)); ) return output; )
Although the loop is now a separate function, it was not possible to completely get rid of it. If you go all the way and try to do without cyclic constructs entirely, you can write a recursive version of the same thing:

Function map(f, a) ( if (a.length === 0) ( return ; ) return .concat(map(f, a.slice(1))); )
The recursive solution looks very elegant. Just a couple of lines of code and a minimum of indentation. But recursive implementations of algorithms are usually used with great caution, and they also suffer from poor performance in older browsers. And, in fact, we don't really need to write a function to implement the mapping operation ourselves, unless there is a good reason for doing so. What our map function does is such a common task that JavaScript has a built-in map() method. If you use this method, the code will look like this:

Let bandoodle = band.map(oodlify); let floodleship = fellowship.map(oodlify); let bandizzle = band.map(izzlify); let fellowship = fellowship.map(izzlify);
Notice that there is no indentation or looping at all. Of course, when processing data, somewhere in the depths of JavaScript, loops can be used, but this is no longer our concern. Now the code is both concise and expressive. Moreover, it is simpler.

Why is this code simpler? This may seem like a stupid question, but think about it. Is it simpler because it's shorter? No. Compact code is not a sign of simplicity. It is simpler because with this approach we have broken the problem into parts. Namely, there are two functions that work with strings: oodlify and izzlify. These functions don't need to know anything about arrays or loops. There is another function - map, which works with arrays. At the same time, it is completely indifferent to what type of data is in the array, or what exactly we want to do with this data. It simply executes any function passed to it, passing it the elements of the array. Instead of mixing everything up, we separated string processing and array processing. That is why the final code turned out to be simpler.

Array convolution

So, the map function is very useful, but it does not cover all array processing options that use loops. It is good in cases where, based on a certain array, you need to create a new one with the same length. But what if we need, for example, to add up all the elements of a numeric array? Or, what if you need to find the shortest string in the list? Sometimes you need to process an array and, in fact, generate a single value based on it.

Let's look at an example. Let's say we have a list of objects, each of which represents a superhero:

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), ];
We need to find the strongest hero. In order to do this, you can use the for…of loop:

Let strongest = (strength: 0); for (let hero of heroes) ( if (hero.strength > strongest.strength) ( strongest = hero; ) )
All things considered, this code isn't that bad. We loop through the array, storing the object of the strongest hero we looked at in the strongest variable. In order to see the pattern of working with an array more clearly, let’s imagine that we also need to find out the total strength of all the heroes.

Let combinedStrength = 0; for (let hero of heroes) ( combinedStrength += hero.strength; )
In each of these two examples there is a work variable that is initialized before the loop starts. Then, in each iteration, one element of the array is processed and the variable is updated. In order to highlight the work scheme even better, we will move the operations performed inside the loops into functions, and rename the variables in order to emphasize the similarity of the actions performed.

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;
If everything is rewritten as shown above, the two loops end up being very similar. The only thing that distinguishes them is the functions called in them and the initial values ​​of the variables. In both loops the array is collapsed to a single value. In English terminology, such an operation is called “reducing”. Therefore, we will create a reduce function that implements the discovered pattern.

Function reduce(f, initialVal, a) ( let working = initialVal; for (let item of a) ( working = f(working, item); ) return working; )
It should be noted that, as with the map function pattern, the reduce function pattern is so widespread that JavaScript provides it as a built-in array method. Therefore, there is no need to write your own method, unless there is a special reason for it. Using standard method the code will look like this:

Const strongestHero = heroes.reduce(greaterStrength, (strength: 0)); const combinedStrength = heroes.reduce(addStrength, 0);
If you take a closer look at the final result, you will find that the resulting code is not much shorter than what was before, the savings are very small. If we had used the reduce function, written ourselves, then, in general, the code would have been larger. 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 they have reduced it. We have separated the looping code from the code that processes array elements. As a result, individual sections of the program became more independent. The code turned out simpler.

At first glance, the reduce function may seem quite primitive. Most examples of this function demonstrate simple things like adding all the elements of numeric arrays. However, nowhere does it say that the value that reduce returns must be a primitive type. It could be an object or even another array. When I first realized this, it amazed me. You can, for example, write an implementation of an array mapping or filtering operation using reduce . I suggest you try it yourself.

Filtering Arrays

So, there is a map function to perform operations on each element of the array. There is a reduce function that allows you to compress an array to a single value. But what if you only need to extract some of the elements from the array? In order to explore this idea, let's expand the list of superheroes and 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"), ]);
Now suppose there are two problems:

  1. Find all female heroes.
  2. Find all heroes whose power exceeds 500.
It is quite possible to approach the solution of these problems using the good old for…of loop:

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); ) )
In general, it looks quite decent. But here a repeating pattern is visible to the naked eye. In fact, the loops are exactly the same, they differ only in the if blocks. What if we put these blocks into 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); ) )
Functions that return only true or false are sometimes called predicates. We use a predicate to decide whether to store the next value from the heroes array in a new array.

The way we rewrote the code made it longer. But, after highlighting the predicate functions, the repeating sections of the program became better visible. Let's create a function that will allow us to get rid of these repetitions:

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);
Here, as with the built-in map and reduce functions, JavaScript has the same thing that we wrote here in the form of a standard filter method on the Array object. Therefore, there is no need to write your own function unless clearly necessary. Using standard means the code will look like this:

Const femaleHeroes = heroes.filter(isFemaleHero); const superhumans = heroes.filter(isSuperhuman);
Why is this approach so much better than using a for…of loop? Think about how you can use this in practice. We have a task like: “Find all the heroes who...”. Once you figure out that you can solve the problem using the standard filter function, the job becomes easier. All we need to do is tell this function which elements we are interested in. This is done by writing one compact function. There is no need to worry about processing arrays or additional variables. Instead, we write a tiny predicate function and the problem is solved.

And, as with other functions that operate on arrays, using filter allows you to express more information in less code. You don't need to read all the standard loop code to figure out what exactly we're filtering. Instead, everything you need to understand is described right when the method is called.

Search in arrays

Filtration is a very useful operation. But what if you only need to find one superhero from the list? Let's say we are interested in Black Widow. The filter function can be used to solve this problem:

Function isBlackWidow(hero) ( return (hero.name === "Black Widow"); ) const blackWidow = heroes.filter(isBlackWidow);
The main problem here is that such a solution is not effective. The filter method goes through each element of the 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, let's write a find function that will find and return the first matching element:

Function find(predicate, arr) ( for (let item of arr) ( if (predicate(item)) ( return item; ) ) const blackWidow = find(isBlackWidow, heroes);
Here, again, it must be said that JavaScript has a built-in function that does exactly what is needed:

Const blackWidow = heroes.find(isBlackWidow);
As a result, as before, we were able to express our idea more concisely. Using the built-in find function, the task of searching for a specific element comes down to one question: “By what criteria can we determine that the desired element has been found?” You don't have to worry about the details.

About the reduce and filter functions

Readers have noticed that it is inefficient to iterate through the list of heroes twice in the examples above for the reduce and filter functions. Using the spread operator from ES2015 allows you to conveniently combine two array folding functions into one. Here's a modified piece of code that allows you to iterate through the array only once:

Function processStrength((strongestHero, combinedStrength), hero) ( return ( strongerHero: greaterStrength(strongestHero, hero), combinedStrength: addStrength(combinedStrength, hero), ); ) const (strongestHero, combinedStrength) = heroes.reduce(processStrength, (strongestHero : (strength: 0), combinedStrength: 0));
I can't help but notice that this version will be a little more complicated than the one in which the array was traversed twice, but if the array is huge, reducing the number of passes over it can be very useful. In any case, the order of complexity of the algorithm remains O(n).

Results

I think the features presented here are a great example of why thoughtfully chosen abstractions are both useful and look good in code. Let's say that we use built-in functions wherever possible. In each case the following results:
  1. We get rid of loops, which makes the code more concise and, most likely, easier to read.
  2. The template used is described using a suitable standard method name. That is, map, reduce, filter, or find.
  3. The scale of the task is reduced. Instead of writing code to process the array yourself, you just need to tell the standard function what to do with the array.
Note that in each case compact pure functions are used to solve the problem.

In fact, if you think about all this, you can come to a conclusion that, at first, seems surprising. It turns out that if you use only the four array processing patterns described above, you can remove almost all loops from your JS code. After all, what is done in almost every loop written in JavaScript? It either processes or constructs a certain array, or does both. In addition, JS has other standard functions for working with arrays, you can easily learn them yourself. Getting rid of loops almost always reduces the complexity of programs and writes code that is easier to read and maintain.

Dear JavaScript developers, do you have any standard functions in mind that can improve the code by getting rid of some common “homemade” constructs?

Tags: Add tags


Close