📜 ⬆️ ⬇️

Expressive JavaScript: Data Structures: Objects and Arrays

Content




I was asked twice: “Tell me, Mr. Babbage, and if you enter the wrong data into the machine, will you get the right answer?” Incomprehensible is the confusion in the heads that leads to such questions.

Charles Babbage, "Excerpts from the life of the philosopher" (1864)
')
Numbers, boolean values ​​and strings are the building blocks from which data structures are built. But you can not make a house of one brick. Objects allow us to group values ​​(including other objects) together — and build more complex structures.

Writing programs that we have been working on so far has made it difficult for them to work only with simple data. This chapter will add an understanding of data structures to your toolkit. By the end of it you will know enough to start writing useful programs.

The chapter will go through a more or less realistic example of programming, introducing concepts as needed. The example code will be built from the functions and variables that we defined earlier.

Werewolf


Sometimes, usually between eight and ten in the evening, Jacques, against his will, turns into a small rodent with a fluffy tail.

On the one hand, Jacques is glad that he does not turn into a classic wolf. Becoming a squirrel leads to fewer problems. Instead of worrying about whether you would not eat your neighbor (it would be embarrassing), he is worried that the neighbor’s cat eats it. After waking up twice on a very thin branch in the crown of an oak tree, naked and disoriented, he learned to lock windows and doors in his room for the night, and put a few nuts on the floor to occupy himself with something.


So problems with a cat and an oak are solved. But Jacques is still suffering from his illness. Irregular treatment suggests that they should be caused by something. At first he thought that this happened only in those days when he touched the trees. He stopped doing it, and even began to avoid approaching them. But the problem did not disappear.

Turning to a more scientific approach, Jacques decided to keep a daily diary of everything he did, writing down whether he turned into a squirrel. So he hopes to narrow the circle of things leading to transformation.

At first, he decided to develop a data structure for storing this information.

Datasets

To work with a piece of data, we first need to find a way to represent them in the memory of the machine. For example, we need to remember a collection of numbers:

2, 3, 5, 7, 11 


You can play with strings - strings can be of any length, you can put a lot of data in them, and use "2 3 5 7 11" to represent this set. But it is inconvenient. We will need to somehow remove the numbers from there or insert new ones into the string.

Fortunately, JavaScript offers a data type specifically for storing sequences of numbers. It is called an array, and is written as a list of values ​​in square brackets, separated by commas:

 var listOfNumbers = [2, 3, 5, 7, 11]; console.log(listOfNumbers[1]); // → 3 console.log(listOfNumbers[1 - 1]); // → 2 


The entry to get an item from an array also uses square brackets. A pair of brackets after the expression, containing inside another expression, will find in the array, which is specified by the first expression, an element, the sequence number of which is given by the second expression.

The number of the first element is zero, not one. Therefore, the first element can be obtained as follows: listOfNumbers [0]. If you have not programmed before, you will have to get used to this numbering. But it has a long tradition, and all the time while it is consistently observed, it works great.

Properties

We saw a lot of suspicious expressions like myString.length (getting the length of a string) and Math.max (getting the maximum) in early examples. These expressions use the properties of quantities. In the first case, we get access to the length property of the myString variable. In the second, access to the max property of the Math object (which is a set of functions and variables related to mathematics).

Almost all variables in JavaScript have properties. Exceptions are null and undefined. If you try to access the non-existent properties of these non-values, you will get an error:

 null.length; // → TypeError: Cannot read property 'length' of null 


The two main ways to access properties are dot and square brackets. value.x and value [x] access the value property — but not necessarily the same. The difference is in how x is interpreted. When using a dot, the record after the dot must be the name of an existing variable, and it thus directly calls the property by name. When using square brackets, the expression in brackets is calculated to get the name of the property. value.x calls a property named “x”, and value [x] evaluates the expression x and uses the result as the name of the property.

If you know that the property you are interested in is called “length”, you write value.length. If you want to extract the property name from the variable i, you write value [i]. And since a property can have any name, to access a property called “2” or “Jon Doe” you will have to use square brackets: value [2] or value [“John Doe”]. This is necessary even when you know the exact name of the property, because “2” or “John Doe” are not valid variable names, so they cannot be accessed with a dot.

Array elements are stored in properties. Since the names of these properties are numbers, and we often have to get their names from the values ​​of variables, we need to use square brackets to access them. The length property of the array indicates how many elements there are. The name of this property is a valid variable name, and we know it in advance, so we usually write array.length, because it is simpler than writing array [“length”].

Methods

The string and array objects contain, in addition to the length property, several properties that reference functions.

 var doh = ""; console.log(typeof doh.toUpperCase); // → function console.log(doh.toUpperCase()); // →  


Each line has a toUpperCase property. When called, it returns a copy of the string in which all the letters are replaced with uppercase. There is also toLowerCase - you can guess what it does.

What is interesting, although the call toUpperCase does not pass any arguments, the function somehow gets access to the line “Duc”, the property of which we called. How it works is described in chapter 6.

Properties containing functions are usually called methods of the variable to which they belong. That is, toUpperCase is a string method.

The following example demonstrates some of the methods available for arrays:

 var mack = []; mack.push(","); mack.push("", ""); console.log(mack); // → [",", "", ""] console.log(mack.join(" ")); // → ,   console.log(mack.pop()); // →  console.log(mack); // → [",", ""] 


The push method is used to add values ​​to the end of the array. pop does the opposite: deletes the value from the end of the array and returns it. An array of strings can be flattened into one string using the join method. The join argument is the string that will be inserted between the elements of the array.

Objects

Let's go back to our squirrel. A set of journal entries can be represented as an array. But the records do not consist only of numbers or lines - each must keep a list of what our hero did, and a boolean value indicating whether Jacques has become a squirrel. Ideally, we would like to group each of the records into a single variable, and then add them to the array.

Variables of type object are collections of arbitrary properties, and we can add and remove object properties at will. One way to create an object is to use curly braces:

 var day1 = { squirrel: false, events: ["", " ", "", "", ""] }; console.log(day1.squirrel); // → false console.log(day1.wolf); // → undefined day1.wolf = false; console.log(day1.wolf); // → false 


In brackets, we can specify a list of properties, separated by commas. Each property is recorded as a name, followed by a colon, then an expression, which is the value of the property. Spaces and line breaks are not taken into account. By breaking the object properties record into several lines, you improve the readability of the code. If the property name is not a valid variable name, it must be enclosed in quotes:

 var descriptions = { work: "  ", " ": "  " }; 


It turns out that braces in JavaScript have two values. Used at the beginning of the instructions, they begin a new block of instructions. In any other place they describe the object. It usually makes no sense to start an instruction with a description of the object, and therefore in programs there is usually no ambiguity about these two uses of curly braces.

If you try to read the value of a non-existent property, you will get undefined - as in the example, when we first tried to read the wolf property.

A property can be assigned a value through the operator =. If it previously had a value, it will be replaced. If the property was missing, it will be created.

Returning to our model with tentacles and variables, we see that the properties are also similar to them. They grab values, but other variables and properties can refer to the same values. Objects are octopuses with an arbitrary number of tentacles, each of which contains a property name.


The delete operator cuts off a tentacle. This is a unary operator applied to a property access expression. This is rarely done, but quite possible.

 var anObject = {left: 1, right: 2}; console.log(anObject.left); // → 1 delete anObject.left; console.log(anObject.left); // → undefined console.log("left" in anObject); // → false console.log("right" in anObject); // → true 


The binary in operator takes a string and the name of an object, and returns a boolean value indicating whether the object has a property with that name. There is a difference between setting the property value to undefined and removing the property. In the first case, the property is retained by the object, it is just empty. In the second, there is no more property, and then in returns false.

It turns out that arrays are a kind of objects that specialize in storing sequences. The typeof expression [1, 2] returns an “object”. They can be considered as long flat octopuses, in which all the tentacles are located in an even row and marked by numbers.



Therefore, the Jacques journal can be represented as an array of objects:

 var journal = [ {events: ["", " ", "", "", ""], squirrel: false}, {events: [" ", "", " ", "", "   ", " "], squirrel: false}, {events: ["", "", "", "", ""], squirrel: true}, /*   ... */ ]; 


Mutability


Soon we will get to the programming. In the meantime, we need to understand the last part of the theory.

We saw that the values ​​of the object can be changed. The types of values ​​that we considered earlier — numbers, strings, booleans — are immutable. You cannot change the existing value of the specified type. You can combine and derive new values ​​from them, but when you work with a certain string value, this value remains constant. The text inside the line can not be changed. If you have a link to the string "cat" in the code you can not change the character in it to make a "midge".

But for objects, the content can be changed by changing the values ​​of their properties.

If we have two numbers, 120 and 120, we can treat them as one and the same, regardless of whether they are stored in the memory in the same place. But when we deal with objects, there is a difference whether we have two references to one object or whether we have two different objects containing the same properties. Consider an example:

 var object1 = {value: 10}; var object2 = object1; var object3 = {value: 10}; console.log(object1 == object2); // → true console.log(object1 == object3); // → false object1.value = 15; console.log(object2.value); // → 15 console.log(object3.value); // → 10 


The variables object1 and object2 hold on to the same object, so changes on object1 lead to changes in object2. The object3 variable points to another object, which initially contains the same properties as object1, but lives its own life.

The == operator when comparing objects returns true only if the objects being compared are the same variable. Comparing different objects will return false, even if they have identical content. There is no “deep” comparison operator that would compare the contents of objects in JavaScript, but it can be done independently (this will be one of the exercises at the end of the chapter).

Werewolf log


So, Jacques runs his favorite JavaScript interpreter and creates the environment needed to store the log.

 var journal = []; function addEntry(events, didITurnIntoASquirrel) { journal.push({ events: events, squirrel: didITurnIntoASquirrel }); } 


Every evening, at ten o'clock - and sometimes tomorrow morning, going down from the top shelf of the cabinet - he writes down his day.

 addEntry(["", " ", "", "", ""], false); addEntry([" ", "", " ", "", "   ", " "], false); addEntry(["", "", "", "", ""], true); 


As soon as he has enough data, he is going to calculate the correlation between his wraps and the events of each day, and ideally find out something useful from their correlations.

Correlation is a measure of the relationship between variables (variables in the statistical sense, not in the sense of JavaScript). It is usually expressed as a coefficient taking values ​​from -1 to 1. Zero correlation means that the variables are not related at all, and correlation 1 means that they are completely connected - if you know one, you automatically know the other. Minus one also means a solid connection of variables, but also their opposite - when one is true, the second is always false.

To measure the correlation of Boolean variables, the phi coefficient (ϕ) is well suited, and it is also relatively easy to calculate. To do this, we need a table containing the number of times that various combinations of two variables were observed. For example, we can take the events of “eating pizza” and “appeal” and present them in the following table:


ϕ can be calculated using the following formula, where n refers to table cells:


n01 denotes the number of measurements when the first event (pizza) is false (0), and the second event (appeal) is true (1). In our example, n01 = 4.

The n1 • entry denotes the sum of all dimensions, where the first event was true, which for our example is 10. Accordingly, n • 0 is the sum of all dimensions, where the “reversal” event was false.

So, for the pizza table, the numerator of the formula is 1 × 76 - 9 × 4 = 40, and the denominator is the root of 10 × 80 × 5 × 85, or √340000. It turns out that ϕ ≈ 0.069, which is rather small. It is unlikely that pizza influenced protein circulation.

Calculate the correlation

A 2x2 table can be represented by an array of four elements ([76, 9, 4, 1]), an array of two elements, each of which is also a two-element array ([76, 9], [4, 1]]), or an object with properties named “11” or “01”. But for us the one-dimensional array is simpler and the expression for accessing it will be shorter. We will treat array indices as two-digit binary numbers, where the left character denotes the turnover variable, and the right one denotes the events. For example, 10 refers to the case when Jacques turned into a squirrel, but an event (for example, “pizza”) did not take place. It happened 4 times. And since binary 10 is decimal 2, we will store this in an array at index 2.

The function that calculates the coefficient ϕ from this array:

 function phi(table) { return (table[3] * table[0] - table[2] * table[1]) / Math.sqrt((table[2] + table[3]) * (table[0] + table[1]) * (table[1] + table[3]) * (table[0] + table[2])); } console.log(phi([76, 9, 4, 1])); // → 0.068599434 


This is just a direct implementation of the ϕ formula in JavaScript. Math.sqrt is a function for extracting the square root of a Math object from a standard JavaScript environment. We need to add two fields of the table to get fields of type n1 •, because we do not store explicitly the sum of columns or rows.

Jacques kept a journal for three months. The result is available on the book's website.
eloquentjavascript.net/code/jacques_journal.js

To extract the 2x2 variable for a specific event, we need to cycle through all the records and count how many times it happens in relation to conversion to a protein.

 function hasEvent(event, entry) { return entry.events.indexOf(event) != -1; } function tableFor(event, journal) { var table = [0, 0, 0, 0]; for (var i = 0; i < journal.length; i++) { var entry = journal[i], index = 0; if (hasEvent(event, entry)) index += 1; if (entry.squirrel) index += 2; table[index] += 1; } return table; } console.log(tableFor("pizza", JOURNAL)); // → [76, 9, 4, 1] 


The hasEvent function checks whether the entry contains the desired item. Arrays have an indexOf method that searches for a given value (in our case, the name of the event) in the array. and returns the index of its position in the array (-1 if it is not in the array). So, if the call to indexOf did not return -1, then there is an event in the record.

The loop body in tableFor calculates which cell of the table each of the log entries falls into. She looks to see if the record contains the desired event, and whether it is associated with conversion to a squirrel. Then the cycle increases by one the elements of the array corresponding to the desired cell.

Now we have all the tools for calculating correlations. It remains only to calculate the correlations for each of the events, and see if something is displayed from the list. But how to store these correlations?

Objects as maps

One way is to store correlations in an array using objects with name and value properties. However, the search for correlations in the array will be rather cumbersome: you will need to go through the entire array to find an object with the desired name. It would have been possible to wrap this process in a function, but the code would have to be written anyway, and the computer would do more work than necessary.

The best way is to use the properties of objects with event names. We can use square brackets to create and read properties and an in operator to check for the existence of a property.

 var map = {}; function storePhi(event, phi) { map[event] = phi; } storePhi("", 0.069); storePhi(" ", -0.081); console.log("" in map); // → true console.log(map[" "]); // → -0.081 


A map is a way to associate values ​​from one area (in this case, the names of events) with values ​​in another (in our case, coefficients ϕ).

With such use of objects, there are a couple of problems - we will discuss them in Chapter 6, but for now we will not worry.

What if we need to collect all the events for which the coefficients are stored? They do not create a predictable sequence, as it would be in an array, so the for loop will not work. JavaScript offers a loop construct specifically for traversing all the properties of an object. It is similar to a for loop, but uses the in command.

 for (var event in map) console.log("  '" + event + "'  " + map[event]); // →   ''  0.069 // →   ' '  -0.081 


Final analysis


To find all types of events represented in a dataset, we process each occurrence in turn, and then create a loop over all occurrences of the occurrence. We store the phis object, which contains the correlation coefficients for all types of events that we have already found. If we meet a new type that has not yet been in phis, we calculate its correlation and add it to the object.

 function gatherCorrelations(journal) { var phis = {}; for (var entry = 0; entry < journal.length; entry++) { var events = journal[entry].events; for (var i = 0; i < events.length; i++) { var event = events[i]; if (!(event in phis)) phis[event] = phi(tableFor(event, journal)); } } return phis; } var correlations = gatherCorrelations(JOURNAL); console.log(correlations.pizza); // → 0.068599434 


We look. what happened:

 for (var event in correlations) console.log(event + ": " + correlations[event]); // → : 0.0140970969 // → : 0.0685994341 // → : 0.1371988681 // → : -0.0757554019 // → : -0.0648203724 //   ... 


Most correlations are close to zero. Carrots, bread and pudding are obviously not related to squirrel circulation. But it seems to happen more often on weekends. Let's filter the results to show only correlations greater than 0.1 or less than -0.1

 for (var event in correlations) { var correlation = correlations[event]; if (correlation > 0.1 || correlation < -0.1) console.log(event + ": " + correlation); } // → : 0.1371988681 // →  : -0.3805211953 // → : 0.1296407447 // → : -0.1371988681 // → : 0.2425356250 // → : 0.1106828054 // → : 0.5902679812 


Aha The two correlation factors are noticeably larger than the rest. Peanuts greatly affect the likelihood of becoming a protein, while brushing your teeth, on the contrary, prevents this.

Interesting. Let's try this:

 for (var i = 0; i < JOURNAL.length; i++) { var entry = JOURNAL[i]; if (hasEvent("", entry) && !hasEvent(" ", entry)) entry.events.push(" "); } console.log(phi(tableFor("  ", JOURNAL))); // → 1 


There can be no mistakes! The phenomenon happens exactly when Jacques is a peanut and does not brush his teeth. If he had not been such a slob about oral hygiene, he would not have noticed his misfortune at all.

Knowing this, Jacques simply stops eating peanuts and discovers that the transformation has stopped.

Jacques is doing well for a while.But after a few years, he loses his job, and eventually he has to go to a circus, where he acts as an Amazing Human Protein, picking up a mouthful of peanut butter before the show. Once, tired of such a miserable existence, Jacques does not turn back into a man, sneaks through a hole in a circus tent and disappears into the forest. No one else saw him.

Further massology


At the end of the chapter I want to introduce you to a few more concepts related to objects. Let's start with the useful methods available in arrays.

We have seen the push and pop methods, which add and take away elements at the end of an array. The corresponding methods for starting the array are called unshift and shift.

 var todoList = []; function rememberTo(task) { todoList.push(task); } function whatIsNext() { return todoList.shift(); } function urgentlyRememberTo(task) { todoList.unshift(task); } 


This program manages the to-do list. You add things to the end of the list by calling rememberTo, and when you're ready to do something, call whatIsNext () to get (and remove) the first item in the list. The urgentlyRememberTo function also adds a task, but only to the top of the list.

The indexOf method has a relative named lastIndexof, which starts searching for an element in the array from the end:

 console.log([1, 2, 3, 2, 1].indexOf(2)); // → 1 console.log([1, 2, 3, 2, 1].lastIndexOf(2)); // → 3 


Both methods, indexOf and lastIndexOf, take an optional second argument that specifies the starting position of the search.

Another important method is slice, which takes the numbers of the start (start) and end (end) elements, and returns an array consisting only of elements falling into this gap. Including the one that is on the start index, but excluding the one that is on the end index.

 console.log([0, 1, 2, 3, 4].slice(2, 4)); // → [2, 3] console.log([0, 1, 2, 3, 4].slice(2)); // → [2, 3, 4] 


When end is not set, slice selects all items after the start index. Strings have a similar method that works the same way.

The concat method is used for splicing arrays, something like the + operator sticks together strings. The example shows the methods concat and slice in the case. The function takes an array and an index index, and returns a new array, which is a copy of the previous one, with the exception of the deleted item at index index.

 function remove(array, index) { return array.slice(0, index).concat(array.slice(index + 1)); } console.log(remove(["a", "b", "c", "d", "e"], 2)); // → ["a", "b", "d", "e"] 


Strings and their properties


We can get the values ​​of the properties of strings, for example, length and toUpperCase. But an attempt to add a new property will not lead to anything:

 var myString = ""; myString.myProperty = ""; console.log(myString.myProperty); // → undefined 


String, number, and Boolean values ​​are not objects, and although the language does not complain about attempts to assign new properties to them, it does not actually save them. Values ​​are immutable.

But they have their own built-in properties. Each line has a set of methods. The most useful, perhaps - slice and indexOf, resembling the same methods in arrays.

 console.log("".slice(3, 6)); // →  console.log("".indexOf("")); // → 4 


The difference is that for a string, the indexOf method can accept a string containing more than one character, and for arrays, this method only works with one element.

 console.log("  ".indexOf("")); // → 5 


The trim method removes spaces (as well as line breaks, tabs, and other similar characters) at both ends of the line.

 console.log("  \n ".trim()); // →  


We have already encountered the string property length. Access to individual characters of the line can be obtained through the method charAt, as well as simply through the numbering of positions, as in an array:

 var string = "abc"; console.log(string.length); // → 3 console.log(string.charAt(0)); // → a console.log(string[1]); // → b 


Arguments object


When a function is called, a special variable called arguments is added to the environment of the executable function body. It points to an object containing all the arguments passed to the function. Remember that in JavaScript you can pass more or less arguments to functions than declared with parameters.

 function noArguments() {} noArguments(1, 2, 3); //  function threeArguments(a, b, c) {} threeArguments(); //    


The arguments object has a length property that contains the actual number of arguments passed to the function. It also has properties for each argument under the names 0, 1, 2, etc.

If it seems to you that this is very similar to an array, you are right. This is very similar to an array. Unfortunately, this object has no methods of type slice or indexOf, which makes accessing it harder.

 function argumentCounter() { console.log("  ", arguments.length, "."); } argumentCounter("", "", ""); // →    3 . 


Some functions are designed for any number of arguments, like console.log. They usually loop through the properties of the arguments object. This can be used to create user-friendly interfaces. For example, remember how we created entries for Jacques magazine:

 addEntry(["", " ", "", "", ""], false); 


Since we often call this function, we can make an alternative that is easier to call:

 function addEntry(squirrel) { var entry = {events: [], squirrel: squirrel}; for (var i = 1; i < arguments.length; i++) entry.events.push(arguments[i]); journal.push(entry); } addEntry(true, "", " ", "", "", ""); 


This version reads the first argument as usual, and for the rest passes in a loop (starting at index 1, skipping the first argument) and assembles them into an array.

Math object


We have already seen that Math is a set of tools for working with numbers, such as Math.max (maximum), Math.min (minimum), and Math.sqrt (square root).

The Math object is simply used as a container to group related functions. There is only one Math object, and it is almost never used as a value. It simply provides a namespace for all these functions and values, so that they do not need to be made global.

Too many global variables pollute the namespace. The more names are taken, the more likely it is to accidentally use one of them as a variable. For example, it is very likely that you will want to use the name max for something in your program. Since the max’s built-in JavaScript function is safely packaged in a Math object, we don’t need to worry about overwriting it.

Many languages ​​will stop you, or at least warn you, when you define a variable with a name that is already taken. JavaScript will not do this, so be careful.

Returning to the Math object. If you need trigonometry, he will help you. It has cos (cosine), sin (sine), and tan (tangent), their inverse functions are acos, asin, and atan. The number π (pi) - or at least its close approximation, which fits in the JavaScript number - is also available as Math.PI. (There is such an old tradition in programming - to write the names of constants in upper case).

 function randomPointOnCircle(radius) { var angle = Math.random() * 2 * Math.PI; return {x: radius * Math.cos(angle), y: radius * Math.sin(angle)}; } console.log(randomPointOnCircle(2)); // → {x: 0.3667, y: 1.966} 


If you are unfamiliar with sines and cosines - do not despair. We will use them in chapter 13, and then I will explain them.

The previous example uses Math.random. This is a function that returns with each call a new pseudo-random number between zero and one (including zero).

 console.log(Math.random()); // → 0.36993729369714856 console.log(Math.random()); // → 0.727367032552138 console.log(Math.random()); // → 0.40180766698904335 


Although computers are deterministic machines (they always react the same way to the same input data), it is possible to make them give out seemingly random numbers. For this, the machine keeps several numbers in its internal state. Each time a random number is requested, it performs various complex deterministic calculations and returns part of the result of the calculations. She uses this result in order to change her internal state, so the next "random" number is obtained by another.

If you need a whole random number, not a fraction, you can use Math.floor (rounds the number down to the nearest integer) on the result of Math.random.

 console.log(Math.floor(Math.random() * 10)); // → 2 


Multiplying a random number by 10, we get a number from zero to 10 (including zero). Since Math.floor rounds down, we get a number from 0 to 9 inclusive.

There is also a Math.ceil ("ceiling") function that rounds up to the nearest integer and Math.round (rounds to the nearest integer).

Global object


The global scope where global variables live can be accessed in the same way as an object. Each global variable is a property of this object. In browsers, the global scope is stored in the variable window.

 var myVar = 10; console.log("myVar" in window); // → true console.log(window.myVar); // → 10 


Total


Objects and arrays (which are subspecies of objects) allow us to group several values ​​into one. In principle, this allows us to shove a few interconnected things into a bag and run around with it in circles, instead of trying to grab all these things with our hands and try to hold them each separately.

Most values ​​in JavaScript have properties, with the exception of null and undefined. We access them via value.propName or value [“propName”]. Objects use names to store properties and store a more or less fixed number of them. Arrays usually contain a variable number of values ​​of the same type, and use numbers (starting from zero) as names for these values.

Also in arrays there are named properties, such as length, and several methods. Methods are functions that live among properties and (usually) work on the value whose property they are.

Objects can also work as maps, associating values ​​with names. The in operator is used to find out if an object contains a property with the given name. The same keyword is used in the for loop (for (var name in object)) to iterate through all the properties of the object.

Exercises


Amount range

The introduction mentioned a convenient way to calculate the sum of the ranges of numbers:

 console.log(sum(range(1, 10))); 


Write a range function that takes two arguments, the beginning and end of a range, and returns an array that contains all the numbers from it, including the start and end.

Then write a sum function that takes an array of numbers and returns their sum. Run the above instruction and make sure that it returns 55.

As a bonus, add the range function so that it can take the optional third argument - the step for constructing an array. If it is not specified, the step is one. The function call range (1, 10, 2) should return [1, 3, 5, 7, 9]. Make sure that it works in negative steps so that the call range (5, 2, -1) returns [5, 4, 3, 2].

 console.log(sum(range(1, 10))); // → 55 console.log(range(5, 2, -1)); // → [5, 4, 3, 2] 


Reversing the array

Arrays have a reverse method that reverses the order of the elements in the array. As an exercise, write two functions, reverseArray and reverseArrayInPlace. The first one takes an array as an argument and returns a new array, with the reverse order of the elements. The second works as the original reverse method — it reverses the order of the elements in the array that was passed to it as an argument. Do not use the standard reverse method.

If you keep in mind the side effects and the pure functions from the previous chapter, which of the options seems more useful to you? Which one is more effective?

 console.log(reverseArray(["A", "B", "C"])); // → ["C", "B", "A"]; var arrayValue = [1, 2, 3, 4, 5]; reverseArrayInPlace(arrayValue); console.log(arrayValue); // → [5, 4, 3, 2, 1] 


List

Objects can be used to build various data structures. Often found structure - the list (do not confuse with an array). A list is a related set of objects, where the first object contains a link to the second, the second to the third, and so on.

 var list = { value: 1, rest: { value: 2, rest: { value: 3, rest: null } } }; 


As a result, objects form a chain:


Lists are convenient in that they can share part of their structure. For example, you can make two lists, {value: 0, rest: list} and {value: -1, rest: list}, where list is a reference to a previously declared variable. These are two independent lists, and they have a common list structure, which includes the last three elements of each of them. In addition, the original list also saves its properties as a separate list of three items.

Write the function arrayToList, which builds such a structure, taking as argument [1, 2, 3], as well as the function listToArray, which creates an array from the list. Also, write the auxiliary function prepend, which gets the element and creates a new list, where this element is added to the original list, and the nth function, which takes a list and a number as arguments, and returns the element at a specified position in the list, or undefined in case the absence of such an element.

If your version of nth is not recursive, then write its recursive version.

 console.log(arrayToList([10, 20])); // → {value: 10, rest: {value: 20, rest: null}} console.log(listToArray(arrayToList([10, 20, 30]))); // → [10, 20, 30] console.log(prepend(10, prepend(20, null))); // → {value: 10, rest: {value: 20, rest: null}} console.log(nth(arrayToList([10, 20, 30]), 1)); // → 20 


Deep comparison

The == operator compares object variables, checking if they refer to a single object. But sometimes it would be useful to compare objects by content.

Write a deepEqual function that takes two values ​​and returns true only if these are two identical values ​​or are objects whose properties have the same values ​​if you compare them with a recursive call to deepEqual.

To find out when to compare values ​​using ===, and when to compare objects by content, use the typeof operator. If it yields “object” for both quantities, then you need to make a deep comparison. Do not forget about one stupid exception, which happened because of historical reasons: “typeof null” also returns “object”.

 var obj = {here: {is: "an"}, object: 2}; console.log(deepEqual(obj, obj)); // → true console.log(deepEqual(obj, {here: 1, object: 2})); // → false console.log(deepEqual(obj, {here: {is: "an"}, object: 2})); // → true 

Source: https://habr.com/ru/post/240813/


All Articles