diff --git a/1-js/2-first-steps/18-function-parameters/article.md b/1-js/2-first-steps/18-function-parameters/article.md index 3145bf5f..5f23fb18 100644 --- a/1-js/2-first-steps/18-function-parameters/article.md +++ b/1-js/2-first-steps/18-function-parameters/article.md @@ -119,13 +119,16 @@ The `...rest` must always be the last. ````smart header="The `arguments` variable" -In old times, there were no rest operator. But there was a special variable named `arguments` that contained all arguments by their index. It is still supported and can be used like this: +In old times, there was no rest operator. But there is a special variable named `arguments` that contains all arguments by their index. It is still supported and can be used like this: ```js run function showName() { alert( arguments[0] ); alert( arguments[1] ); alert( arguments.length ); + + // for..of works too + // for(let arg of arguments) alert(arg); } // shows: Julius, Caesar, 2 @@ -135,7 +138,7 @@ showName("Julius", "Caesar"); showName("Ilya"); ``` -The downside is that `arguments` looks like an array, but it's not. It does not support many useful array features. It mainly exists for backwards compatibility, usually the rest operator is better. +The downside is that though `arguments` looks like an array, but it's not. It does not support many useful array methods that we'll study later, and if they're needed, then the rest operator should be used. ```` ## Destructuring parameters @@ -158,7 +161,7 @@ Like this? showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) ``` -That's ugly. And becomes unreadable if we deal with more parameters. +That's ugly. And becomes unreadable when we deal with more parameters. Destructuring comes to the rescue! @@ -235,13 +238,13 @@ showMenu(); // Menu 100 200 In the code above, the whole arguments object is `{}` by default, so there's always something to destructurize. -## The spread operator +## The spread operator [#spread-operator] As we've seen before, the rest operator `...` allows to gather parameters in the array. But there's a reverse named "the spread operator". It also looks like `...` and works at call-time. -The spread operator allows to "unfurl" an array into a list of parameters, like this: +The spread operator allows to convert an array into a list of parameters, like this: ```js run let fullName = ["Gaius", "Julius", "Caesar"]; @@ -256,7 +259,6 @@ function showName(firstName, secondName, lastName) { showName(...fullName); ``` - Let's see a more real-life example. There exist a built-in function [Math.max](mdn:js/Math/max) that takes a list of values and returns the greatest one: @@ -276,13 +278,14 @@ alert( Math.max(...arr) ); // 7 In short: - When `...` occurs in function parameters, it's called a "rest operator" and gathers parameters into the array. -- When `...` occurs in a function call, it's called a "spread operator" and unfurls an array into the list. +- When `...` occurs in a function call, it's called a "spread operator" and converts an array into the list. Together they help to travel between a list and an array of parameters with ease. ## Summary TODO +[todo] Основные улучшения в функциях: - Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта. diff --git a/1-js/2-first-steps/19-function-create-advanced/article.md b/1-js/2-first-steps/19-function-expressions-arrows/article.md similarity index 99% rename from 1-js/2-first-steps/19-function-create-advanced/article.md rename to 1-js/2-first-steps/19-function-expressions-arrows/article.md index fe575f2d..0eb3934d 100644 --- a/1-js/2-first-steps/19-function-create-advanced/article.md +++ b/1-js/2-first-steps/19-function-expressions-arrows/article.md @@ -1,4 +1,4 @@ -# Function expressions and more +# Function expressions and arrows In JavaScript, a function is a value. diff --git a/1-js/2-first-steps/20-object-methods/2-check-syntax/solution.md b/1-js/2-first-steps/20-object-methods/2-check-syntax/solution.md new file mode 100644 index 00000000..fc22bac1 --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/2-check-syntax/solution.md @@ -0,0 +1,43 @@ +**Error**! + +Try it: + +```js run +let user = { + name: "John", + go: function() { alert(this.name) } +} + +(user.go)() // error! +``` + +The error message in most browsers does not give understanding what went wrong. + +**The error appears because a semicolon is missing after `user = {...}`.** + +Javascript does not assume a semicolon before a bracket `(user.go)()`, so it reads the code like: + +```js no-beautify +let user = { go:... }(user.go)() +``` + +Then we can also see that such a joint expression is syntactically a call of the object `{ go: ... }` as a function with the argument `(user.go)`. And that also happens on the same line with `let user`, so the `user` object has not yet even been defined, hence the error. + +If we insert the semicolon, all is fine: + +```js run +let user = { + name: "John", + go: function() { alert(this.name) } +}*!*;*/!* + +(user.go)() // John +``` + +Please note that brackets around `(user.go)` do nothing here. Usually they setup the order of operations, but here the dot `.` works first anyway, so there's no effect. Only the semicolon thing matters. + + + + + + diff --git a/1-js/2-first-steps/20-object-methods/2-check-syntax/task.md b/1-js/2-first-steps/20-object-methods/2-check-syntax/task.md new file mode 100644 index 00000000..bb4d5b86 --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/2-check-syntax/task.md @@ -0,0 +1,19 @@ +importance: 2 + +--- + +# Syntax check + +What is the resule of this code? + + +```js no-beautify +let user = { + name: "John", + go: function() { alert(this.name) } +} + +(user.go)() +``` + +P.S. There's a pitfall :) \ No newline at end of file diff --git a/1-js/2-first-steps/20-object-methods/3-why-this/solution.md b/1-js/2-first-steps/20-object-methods/3-why-this/solution.md new file mode 100644 index 00000000..89bc0d72 --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/3-why-this/solution.md @@ -0,0 +1,22 @@ + +Here's the explanations. + +1. That's a regular object method call. + +2. The same, brackets do not change the order of operations here, the dot is first anyway. + +3. Here we have a more complex call `(expression).method()`. The call works as if it were split into two lines: + + ```js no-beautify + f = obj.go; // calculate the expression + f(); // call what we have + ``` + + Here `f()` is executed as a function, without `this`. + +4. The similar thing as `(3)`, to the left of the dot `.` we have an expression. + +To explain the behavior of `(3)` and `(4)` we need to recall that property accessors (dot or square brackets) return a value of the Reference Type. + +Any operation on it except a method call (like assignment `=` or `||`) turns it into an ordinary value, which does not carry the information allowing to set `this`. + diff --git a/1-js/2-first-steps/20-object-methods/3-why-this/task.md b/1-js/2-first-steps/20-object-methods/3-why-this/task.md new file mode 100644 index 00000000..2b6e8cd1 --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/3-why-this/task.md @@ -0,0 +1,26 @@ +importance: 3 + +--- + +# Explain the value of "this" + +In the code above we intend to call `user.go()` method 4 times in a row. + +But calls `(1)` and `(2)` works differently from `(3)` and `(4)`. Why? + +```js run no-beautify +let obj, method; + +obj = { + go: function() { alert(this); } +}; + +obj.go(); // (1) [object Object] + +(obj.go)(); // (2) [object Object] + +(method = obj.go)(); // (3) undefined + +(obj.go || obj.stop)(); // (4) undefined +``` + diff --git a/1-js/2-first-steps/20-object-methods/4-object-property-this/solution.md b/1-js/2-first-steps/20-object-methods/4-object-property-this/solution.md new file mode 100644 index 00000000..f5773ec2 --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/4-object-property-this/solution.md @@ -0,0 +1,46 @@ +**Answer: an error.** + +Try it: +```js run +function makeUser() { + return { + name: "John", + ref: this + }; +}; + +let user = makeUser(); + +alert( user.ref.name ); // Error: Cannot read property 'name' of undefined +``` + +That's because rules that set `this` do not look at object literals. + +Here the value of `this` inside `makeUser()` is `undefined`, because it is called as a function, not as a method. + +And the object literal itself has no effect on `this`. The value of `this` is one for the whole function, code blocks and object literals do not affect it. + +So `ref: this` actually takes current `this` of the function. + +Here's the opposite case: + +```js run +function makeUser() { + return { + name: "John", +*!* + ref() { + return this; + } +*/!* + }; +}; + +let user = makeUser(); + +alert( user.ref().name ); // John +``` + +Now it works, because `user.ref()` is a method. And the value of `this` is set to the object before dot `.`. + + diff --git a/1-js/2-first-steps/20-object-methods/4-object-property-this/task.md b/1-js/2-first-steps/20-object-methods/4-object-property-this/task.md new file mode 100644 index 00000000..4784b082 --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/4-object-property-this/task.md @@ -0,0 +1,23 @@ +importance: 5 + +--- + +# Using "this" in object literal + +Here the function `makeUser` returns an object. + +What is the result of accessing its `ref`? Why? + +```js +function makeUser() { + return { + name: "John", + ref: this + }; +}; + +let user = makeUser(); + +alert( user.ref.name ); // What's the result? +``` + diff --git a/1-js/2-first-steps/20-object-methods/7-calculator/_js.view/solution.js b/1-js/2-first-steps/20-object-methods/7-calculator/_js.view/solution.js new file mode 100644 index 00000000..9ccbe43a --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/7-calculator/_js.view/solution.js @@ -0,0 +1,14 @@ +let calculator = { + sum() { + return this.a + this.b; + }, + + mul() { + return this.a * this.b; + }, + + read() { + this.a = +prompt('a?', 0); + this.b = +prompt('b?', 0); + } +}; \ No newline at end of file diff --git a/1-js/2-first-steps/20-object-methods/7-calculator/_js.view/test.js b/1-js/2-first-steps/20-object-methods/7-calculator/_js.view/test.js new file mode 100644 index 00000000..1f71eda4 --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/7-calculator/_js.view/test.js @@ -0,0 +1,28 @@ + + +describe("calculator", function() { + + context("when 2 and 3 entered", function() { + beforeEach(function() { + sinon.stub(window, "prompt"); + + prompt.onCall(0).returns("2"); + prompt.onCall(1).returns("3"); + + calculator.read(); + }); + + afterEach(function() { + prompt.restore(); + }); + + it("the sum is 5", function() { + assert.equal(calculator.sum(), 5); + }); + + it("the multiplication product is 6", function() { + assert.equal(calculator.mul(), 6); + }); + }); + +}); diff --git a/1-js/2-first-steps/20-object-methods/7-calculator/solution.md b/1-js/2-first-steps/20-object-methods/7-calculator/solution.md new file mode 100644 index 00000000..22c4bf18 --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/7-calculator/solution.md @@ -0,0 +1,23 @@ + + +```js run demo +let calculator = { + sum() { + return this.a + this.b; + }, + + mul() { + return this.a * this.b; + }, + + read() { + this.a = +prompt('a?', 0); + this.b = +prompt('b?', 0); + } +}; + +calculator.read(); +alert( calculator.sum() ); +alert( calculator.mul() ); +``` + diff --git a/1-js/2-first-steps/20-object-methods/7-calculator/task.md b/1-js/2-first-steps/20-object-methods/7-calculator/task.md new file mode 100644 index 00000000..aa22608e --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/7-calculator/task.md @@ -0,0 +1,24 @@ +importance: 5 + +--- + +# Create a calculator + +Create an object `calculator` with three methods: + +- `read()` prompts for two values and saves them as object properties. +- `sum()` returns the sum of saved values. +- `mul()` multiplies saved values and returns the result. + +```js +let calculator = { + // ... your code ... +}; + +calculator.read(); +alert( calculator.sum() ); +alert( calculator.mul() ); +``` + +[demo] + diff --git a/1-js/2-first-steps/20-object-methods/8-chain-calls/solution.md b/1-js/2-first-steps/20-object-methods/8-chain-calls/solution.md new file mode 100644 index 00000000..41edd723 --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/8-chain-calls/solution.md @@ -0,0 +1,40 @@ +The solution is to return the object itself from every call. + +```js run +let ladder = { + step: 0, + up() { + this.step++; +*!* + return this; +*/!* + }, + down() { + this.step--; +*!* + return this; +*/!* + }, + showStep() { + alert( this.step ); +*!* + return this; +*/!* + } +} + +ladder.up().up().down().up().down().showStep(); // 1 +``` + +We also can write a single call per line. For long chains it's more readable: + +```js +ladder + .up() + .up() + .down() + .up() + .down() + .showStep(); // 1 +``` + diff --git a/1-js/2-first-steps/20-object-methods/8-chain-calls/task.md b/1-js/2-first-steps/20-object-methods/8-chain-calls/task.md new file mode 100644 index 00000000..2fe6a67e --- /dev/null +++ b/1-js/2-first-steps/20-object-methods/8-chain-calls/task.md @@ -0,0 +1,39 @@ +importance: 2 + +--- + +# Chaining + +There's a `ladder` object that allows to go up and down: + +```js +let ladder = { + step: 0, + up() { + this.step++; + }, + down() { + this.step--; + }, + showStep: function() { // shows the current step + alert( this.step ); + } +}; +``` + +Now, if we need to make several calls in sequence, can do it like this: + +```js +ladder.up(); +ladder.up(); +ladder.down(); +ladder.showStep(); // 1 +``` + +Modify the code of `up` and `down` to make the calls chainable, like this: + +```js +ladder.up().up().down().showStep(); // 1 +``` + +Such approach is widely used across Javascript libraries. diff --git a/1-js/2-first-steps/20-object-methods/article.md b/1-js/2-first-steps/20-object-methods/article.md index 99cfbb78..228da9b8 100644 --- a/1-js/2-first-steps/20-object-methods/article.md +++ b/1-js/2-first-steps/20-object-methods/article.md @@ -213,11 +213,9 @@ If you forget `use strict`, then you may see something like "window" in the exam That's one of the odd things of the previous standard that `"use strict"` fixes. ``` -## "this" is for direct calls +## Reference Type -The value of `this` is only passed the right way if the function is called using a dot `'.'` or square brackets. - -A more intricate call would lead to losing `this`, for instance: +An intricate method call can loose `this`, for instance: ```js run let user = { @@ -230,21 +228,37 @@ user.hi(); // John (the simple call works) *!* // now let's call user.hi or user.bye depending on the name -(user.name == "John" ? user.hi : user.bye)(); // undefined +(user.name == "John" ? user.hi : user.bye)(); // Error! */!* ``` -On the last line the method is retrieved during the execution of the ternary `?`, and immediately called. But `"this"` is lost, the result is not `"John"` how it should be. +On the last line the method `user.hi` is retrieved during the execution of the ternary `?`, and immediately called with brackets `()`. But that doesn't work right. You can see that the call results in an error, cause the value of `"this"` inside the call becomes `undefined`. -If we want to understand why it happens -- the reason is in the details of how `obj.method()` works. +If we want to understand why it happens -- the reason is in the details of how `obj.method()` call works. -The method call has two independant operations in it: a dot `'.'` to access the property and brackets `()` to execute it (assuming that's a function). +The method call has two successive operations in it: +- the dot `'.'` retrieves the property +- brackets `()` execute it (assuming that's a function). -As we've already seen, the function is a value of its own. It does not memorize the object by itself. So to "carry" it to the brackets, Javascript uses a trick -- the dot `'.'` returns not a function, but a value of the special Reference Type. +So, you might have already asked yourself, why does it work? That is, if we put these operations on separate lines, then `this` is guaranted to be lost: -The Reference Type is a "specification type". It does not exist in real, but used internally to explain how some language features work. +```js run +let user = { + name: "John", + hi() { alert(this.name); } +} -The value of the Reference Type is a tuple `(base, name, strict)`, where: +let hi = user.hi; +hi(); // Error, because this is undefined +``` + +...But for a single-line call `user.hi()` all is fine. + +That's because a function is a value of its own. It does not carry the object. So to pass it to the brackets, Javascript uses a trick -- the dot `'.'` returns not a function, but a value of the special [Reference Type](https://tc39.github.io/ecma262/#sec-reference-specification-type). + +The Reference Type is a "specification type". It does not exist in real, but is used internally. Engines are not required to implement it, just to make sure that code works as described. + +The value of the Reference Type is a combination `(base, name, strict)`, where: - `base` is the object. - `name` is the property. @@ -253,20 +267,25 @@ The value of the Reference Type is a tuple `(base, name, strict)`, where: The result of a property access `'.'` is a value of the Reference Type. For `user.sayHi` in strict mode it is: ```js -// base name strict +// Reference Type value (user, "sayHi", true) ``` -Any operation on the Reference Type immediately "resolves" it: +Then, when brackets `()` are called on the Reference Type, they receive the full information about the object and it's method, and can set the right `this = base`. -- Brackets `()` get the property `base[name]` and execute it with `this = base`. -- Other operators just get `base[name]` and use it. +Any other operation just gets `base[name]` value and uses it, discarding the reference type as a whole. So any operation on the result of dot `'.'` except a direct call discards `this`. + +That's why the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj[method]()` syntax (they do the same here). + + ## Explicit "this" with "call/apply" [#call-apply] -We can call a function explicitly providing the value of `"this"`. +The value of `this` does not have to come from the aforementioned rules. + +We can explicitly set it to any object using `func.call`. The syntax is: @@ -285,31 +304,33 @@ let user = { name: "John" }; let admin = { name: "Admin" }; // use call to pass different objects as "this" -sayHi.call( user ); // John -sayHi.call( admin ); // Admin +sayHi.call( user ); // John +sayHi.call( admin ); // Admin ``` The first parameter of `call` is the intended value of `"this"`, the latter are arguments. +So `sayHi.call(admin)` runs the function `sayHi` with `this = admin`, hence `this.name` in it becomes `"Admin"`. + These calls are roughly equivalent: ```js func(1, 2, 3); func.call(obj, 1, 2, 3) ``` -...Except that the `call` sets "this" of course! +They both call `func` with arguments `1`, `2` and `3`. The only difference is that `call` also sets `"this"`. -That's handy when we want to use a function in the context of different objects, but do not want to actually assign it to them. +The method `func.call` is used when we'd like to use a function in the context of different objects, but do not want to actually assign it to them. We'll see more examples of it soon. ### "func.apply" -There's also a similar syntax: +There's also a similar method `func.apply`: ```js func.apply(context, args) ``` -It does the same as `call`: executes the function providing `context` as `this`, but where `call` awaits a list of arguments, `apply` awaits a single array of arguments. +It does the same as `call`: executes the function providing `context` as `this`, but where `call` awaits a list of arguments, `apply` awaits an array. These two calls do the same: @@ -318,9 +339,9 @@ func.call(obj, 1, 2, 3); func.apply(obj, [1, 2, 3]); ``` -In old times `apply` was more powerful, because it allows to form the array of arguments dynamically. +In old times `apply` was more powerful, because it allows to create the array of arguments dynamically. Their number is not hardcoded at code-write time. -But in the modern language, we have the spread operator `'...'` and can use it to unfurl an array into the list of for `call`, so these two are equal: +But in the modern language, we have the spread operator `'...'` and can use it to convert an array into a list of for `call`, so these two are equal: ```js let args = [1, 2, 3]; @@ -329,7 +350,7 @@ func.call(obj, ...args); func.apply(obj, args); ``` -So the use of `apply` over `call` is mainly a metter of personal preference. And it's somewhat better optimized than the spread operator, because it exists longer. +Nowadays the use of `apply` or `call` is mainly a metter of personal preference. But `apply` is somewhat better optimized in engines than the call + spread combination, because it exists longer. So it would execute a little bit faster. ## Binding "this" with "bind" diff --git a/1-js/4-data-structures/3-string/article.md b/1-js/4-data-structures/3-string/article.md index 0319657e..c6c31046 100644 --- a/1-js/4-data-structures/3-string/article.md +++ b/1-js/4-data-structures/3-string/article.md @@ -163,6 +163,13 @@ alert( str[1000] ); // undefined alert( str.charAt(1000) ); // '' (an empty string) ``` +Also we can iterate over characters using `for..of`: + +```js run +for(let char of "Hello") { + alert(char); // H,e,l,l,o (char becomes "H", then "e", then "l" etc) +} +``` ## Strings are immutable @@ -337,9 +344,9 @@ Just remember: `if (~str.indexOf(...))` reads as "if found". ### includes, startsWith, endsWith -The more modern method [str.includes(substr)](mdn:js/String/includes) returns `true/false` depending on whether `str` has `substr` as its part. +The more modern method [str.includes(substr, pos)](mdn:js/String/includes) returns `true/false` depending on whether `str` has `substr` as its part. -It's the right choice if we need to test for the match, without the position: +It's the right choice if we need to test for the match, but don't need its position: ```js run alert( "Widget with id".includes("Widget") ); // true @@ -347,6 +354,13 @@ alert( "Widget with id".includes("Widget") ); // true alert( "Hello".includes("Bye") ); // false ``` +The optional second argument of `str.includes` is the position to start searching from: + +```js run +alert( "Midget".includes("id") ); // true +alert( "Midget".includes("id", 3) ); // false, from position 3 there is no "id" +``` + The methods [str.startsWith](mdn:js/String/startsWith) and [str.endsWith](mdn:js/String/endsWith) do exactly what they say: ```js run @@ -354,7 +368,6 @@ alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid" alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get" ``` - ## Getting a substring There are 3 methods in JavaScript to get a substring: `substring`, `substr` and `slice`. @@ -561,7 +574,7 @@ Note that surrogate pairs did not exist at the time when Javascript was created, We actually have a single symbol in each of the strings above, but the `length` shows the length of `2`. -`String.fromCodePoint` and `str.codePointAt` are notable exceptions that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt). These methods are actually the same as `fromCodePoint/codePointAt`, but don't work with surrogate pairs. +`String.fromCodePoint` and `str.codePointAt` are few rare methods that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt). These methods are actually the same as `fromCodePoint/codePointAt`, but don't work with surrogate pairs. But, for instance, getting a symbol can be tricky, because surrogate pairs are treated as two characters: @@ -574,7 +587,52 @@ Note that pieces of the surrogate pair have no meaning without each other. So, t How to solve this problem? First, let's make sure you have it. Not every project deals with surrogate pairs. -But if you do, then search the internet for libraries which implement surrogate-aware versions of `slice`, `indexOf` and other functions. Technically, surrogate pairs are detectable by their codes: the first character has the code in the interval of `0xD800..0xDBFF`, while the second is in `0xDC00..0xDFFF`. So if we see a character with the code, say, `0xD801`, then the next one must be the second part of the surrogate pair. Libraries rely on that to split stirngs right. Unfortunately, there's no single well-known library to advise yet. +If you do, then there are some more tricks to deal with surrogates: + +Use `for..of` to iterate over characters. +: The `for..of` loop respects surrogate pairs: + + ```js run + let str = '𝒳😂'; + for(let char of str) { // loop over characters of str + alert(char); // 𝒳, and then 😂 + } + ``` + +Create an array of characters using `Array.from(str)`: +: Here's the trick: + + ```js run + let str = '𝒳😂'; + + // splits str into array of characters + let chars = Array.from(str); + + alert(chars[0]); // 𝒳 + alert(chars[1]); // 😂 + alert(chars.length); // 2 + ``` + + The [Array.from](mdn:js/Array/from) takes an array-like object and turns it into a real array. It works with other array-like objects too. + + Technically here it does the same as: + + ```js run + let str = '𝒳😂'; + + let chars = []; // Array.from internally does the same loop + for(let char of str) { + chars.push(char); + } + + alert(chars); + ``` + + ...But is shorter. + +There are probably libraries in the internet that build surrogate-aware versions of other string calls based on that. + +Technically, surrogate pairs are also detectable by their codes: the first character has the code in the interval of `0xD800..0xDBFF`, while the second is in `0xDC00..0xDFFF`. So if we see a character with the code, say, `0xD801`, then the next one must be the second part of the surrogate pair. ### Diacritical marks diff --git a/1-js/4-data-structures/4-object/article.md b/1-js/4-data-structures/4-object/article.md index c5f8ca93..569a3b78 100644 --- a/1-js/4-data-structures/4-object/article.md +++ b/1-js/4-data-structures/4-object/article.md @@ -7,17 +7,17 @@ Objects in JavaScript combine two functionalities. Here we concentrate on the first part: using objects as a data store, and we will study it in-depth. That's the required base for studying the second part. -An [associative array](https://en.wikipedia.org/wiki/Associative_array), also called "a hash" or "a dictionary" -- is a data structure for storing arbitrary data in the key-value format. +Let's recap what we know about objects and add a bit more. + [cut] +## Object literals + We can imagine it as a cabinet with signed files. Every piece of data is stored in it's file. It's easy to find a file by it's name or add/remove a file. ![](object.png) - -## Object literals - An empty object ("empty cabinet") can be created using one of two syntaxes: ```js @@ -41,6 +41,44 @@ let user = { ![](object-user-props.png) +In real code we quite often want to create an object with a property from a variable. + +For instance: + +```js run +function makeUser(name, age) { + return { + name: name, + age: age; + } +} + +let user = makeUser("John", 30); +alert(user.name); // John +``` + +There's a *property value shorthand* to make it shorter. + +Instead of `name: name` we can just write `name`, like this: + +```js +function makeUser(name, age) { + return { + name, + age; + } +} +``` + +We can also combine normal properties and shorthands: + +```js +let user = { + name, // same as name:name + age: 30 +}; +``` + ````smart header="Trailing comma" The last property may end with a comma: @@ -221,37 +259,6 @@ In the code above, the property `obj.test` technically exists. So the `in` opera Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code. ```` -## Property shorthands - -There are two more syntax features to write a shorter code. - -Property value shorthands -: To create a property from a variable: - - ```js - let name = "John"; - - // same as { name: name } - let user = { name }; - ``` - - If we have a variable and want to add a same-named property, that's the way to write it shorter. - - ```js - // can combine normal properties and shorthands - let user = { name, age: 30 }; - ``` - -Methods definitions -: For properties that are functions, we've already seen a shorter syntax. - - ```js - let user = { - sayHi() { // same as "sayHi: function()" - alert("Hello"); - } - }; - ``` ## Loops @@ -402,9 +409,9 @@ alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference Quite obvious, if we used one of the keys (`admin`) and changed something inside the cabinet, then if we use another key later (`user`), we find things modified. -### Comparison with objects +### Comparison by reference -Two objects are equal only when they are one object: +Two object variabls are equal only when reference the same object: ```js run let a = {}; @@ -426,9 +433,9 @@ let b = {}; // two independents object alert( a == b ); // false ``` -For unusual equality checks like: object vs a priimtive, or an object less/greater `< >` than another object, objects are converted to numbers. To say the truth, such comparisons occur very rarely in real code and usually are a result of a mistake. +For unusual equality checks like: object vs a primitive (`obj == 5`), or an object less/greater than another object (`obj1 > obj2`), objects are converted to numbers. To say the truth, such comparisons occur very rarely in real code and usually are a result of a coding mistake. -## Cloning, Object.assign +## Cloning and Object.assign What if we need to duplicate an object? Create an independant copy, a clone? diff --git a/1-js/4-data-structures/7-array/2-item-value/solution.md b/1-js/4-data-structures/6-array/1-item-value/solution.md similarity index 100% rename from 1-js/4-data-structures/7-array/2-item-value/solution.md rename to 1-js/4-data-structures/6-array/1-item-value/solution.md diff --git a/1-js/4-data-structures/7-array/2-item-value/task.md b/1-js/4-data-structures/6-array/1-item-value/task.md similarity index 79% rename from 1-js/4-data-structures/7-array/2-item-value/task.md rename to 1-js/4-data-structures/6-array/1-item-value/task.md index d09ef022..41215325 100644 --- a/1-js/4-data-structures/7-array/2-item-value/task.md +++ b/1-js/4-data-structures/6-array/1-item-value/task.md @@ -9,10 +9,11 @@ What this code is going to show? ```js let fruits = ["Apples", "Pear", "Orange"]; +// push a new value into the "copy" let shoppingCart = fruits; - shoppingCart.push("Banana"); +// what's in fruits? alert( fruits.length ); // ? ``` diff --git a/1-js/4-data-structures/7-array/10-maximal-subarray/_js.view/solution.js b/1-js/4-data-structures/6-array/10-maximal-subarray/_js.view/solution.js similarity index 100% rename from 1-js/4-data-structures/7-array/10-maximal-subarray/_js.view/solution.js rename to 1-js/4-data-structures/6-array/10-maximal-subarray/_js.view/solution.js diff --git a/1-js/4-data-structures/7-array/10-maximal-subarray/_js.view/test.js b/1-js/4-data-structures/6-array/10-maximal-subarray/_js.view/test.js similarity index 100% rename from 1-js/4-data-structures/7-array/10-maximal-subarray/_js.view/test.js rename to 1-js/4-data-structures/6-array/10-maximal-subarray/_js.view/test.js diff --git a/1-js/4-data-structures/7-array/10-maximal-subarray/solution.md b/1-js/4-data-structures/6-array/10-maximal-subarray/solution.md similarity index 100% rename from 1-js/4-data-structures/7-array/10-maximal-subarray/solution.md rename to 1-js/4-data-structures/6-array/10-maximal-subarray/solution.md diff --git a/1-js/4-data-structures/7-array/10-maximal-subarray/task.md b/1-js/4-data-structures/6-array/10-maximal-subarray/task.md similarity index 100% rename from 1-js/4-data-structures/7-array/10-maximal-subarray/task.md rename to 1-js/4-data-structures/6-array/10-maximal-subarray/task.md diff --git a/1-js/4-data-structures/7-array/3-create-array/solution.md b/1-js/4-data-structures/6-array/2-create-array/solution.md similarity index 100% rename from 1-js/4-data-structures/7-array/3-create-array/solution.md rename to 1-js/4-data-structures/6-array/2-create-array/solution.md diff --git a/1-js/4-data-structures/7-array/3-create-array/task.md b/1-js/4-data-structures/6-array/2-create-array/task.md similarity index 100% rename from 1-js/4-data-structures/7-array/3-create-array/task.md rename to 1-js/4-data-structures/6-array/2-create-array/task.md diff --git a/1-js/4-data-structures/6-array/3-call-array-this/solution.md b/1-js/4-data-structures/6-array/3-call-array-this/solution.md new file mode 100644 index 00000000..e994ae07 --- /dev/null +++ b/1-js/4-data-structures/6-array/3-call-array-this/solution.md @@ -0,0 +1,15 @@ +The call `arr[2]()` is syntactically the good old `obj[method]()`, in the role of `obj` we have `arr`, and in the role of `method` we have `2`. + +So we have a call of the function `arr[2]` as an object method. Naturally, it receives `this` referencing the object `arr` and outputs the array: + +```js run +let arr = ["a", "b"]; + +arr.push(function() { + alert( this ); +}) + +arr[2](); // "a","b",function +``` + +The array has 3 values: initially it had two, plus the function. diff --git a/1-js/4-data-structures/6-array/3-call-array-this/task.md b/1-js/4-data-structures/6-array/3-call-array-this/task.md new file mode 100644 index 00000000..340c5fee --- /dev/null +++ b/1-js/4-data-structures/6-array/3-call-array-this/task.md @@ -0,0 +1,18 @@ +importance: 5 + +--- + +# Calling in an array context + +What is the result? Why? + +```js +let arr = ["a", "b"]; + +arr.push(function() { + alert( this ); +}) + +arr[2](); // ? +``` + diff --git a/1-js/4-data-structures/7-array/4-random-from-array/solution.md b/1-js/4-data-structures/6-array/4-random-from-array/solution.md similarity index 100% rename from 1-js/4-data-structures/7-array/4-random-from-array/solution.md rename to 1-js/4-data-structures/6-array/4-random-from-array/solution.md diff --git a/1-js/4-data-structures/7-array/4-random-from-array/task.md b/1-js/4-data-structures/6-array/4-random-from-array/task.md similarity index 100% rename from 1-js/4-data-structures/7-array/4-random-from-array/task.md rename to 1-js/4-data-structures/6-array/4-random-from-array/task.md diff --git a/1-js/4-data-structures/7-array/5-array-input-sum/solution.md b/1-js/4-data-structures/6-array/5-array-input-sum/solution.md similarity index 100% rename from 1-js/4-data-structures/7-array/5-array-input-sum/solution.md rename to 1-js/4-data-structures/6-array/5-array-input-sum/solution.md diff --git a/1-js/4-data-structures/7-array/5-array-input-sum/task.md b/1-js/4-data-structures/6-array/5-array-input-sum/task.md similarity index 100% rename from 1-js/4-data-structures/7-array/5-array-input-sum/task.md rename to 1-js/4-data-structures/6-array/5-array-input-sum/task.md diff --git a/1-js/4-data-structures/7-array/array-pop.png b/1-js/4-data-structures/6-array/array-pop.png similarity index 100% rename from 1-js/4-data-structures/7-array/array-pop.png rename to 1-js/4-data-structures/6-array/array-pop.png diff --git a/1-js/4-data-structures/7-array/array-pop@2x.png b/1-js/4-data-structures/6-array/array-pop@2x.png similarity index 100% rename from 1-js/4-data-structures/7-array/array-pop@2x.png rename to 1-js/4-data-structures/6-array/array-pop@2x.png diff --git a/1-js/4-data-structures/7-array/array-shift.png b/1-js/4-data-structures/6-array/array-shift.png similarity index 100% rename from 1-js/4-data-structures/7-array/array-shift.png rename to 1-js/4-data-structures/6-array/array-shift.png diff --git a/1-js/4-data-structures/7-array/array-shift@2x.png b/1-js/4-data-structures/6-array/array-shift@2x.png similarity index 100% rename from 1-js/4-data-structures/7-array/array-shift@2x.png rename to 1-js/4-data-structures/6-array/array-shift@2x.png diff --git a/1-js/4-data-structures/7-array/array-speed.png b/1-js/4-data-structures/6-array/array-speed.png similarity index 100% rename from 1-js/4-data-structures/7-array/array-speed.png rename to 1-js/4-data-structures/6-array/array-speed.png diff --git a/1-js/4-data-structures/7-array/array-speed@2x.png b/1-js/4-data-structures/6-array/array-speed@2x.png similarity index 100% rename from 1-js/4-data-structures/7-array/array-speed@2x.png rename to 1-js/4-data-structures/6-array/array-speed@2x.png diff --git a/1-js/4-data-structures/7-array/article.md b/1-js/4-data-structures/6-array/article.md similarity index 95% rename from 1-js/4-data-structures/7-array/article.md rename to 1-js/4-data-structures/6-array/article.md index d1bbc116..c83e6e74 100644 --- a/1-js/4-data-structures/7-array/article.md +++ b/1-js/4-data-structures/6-array/article.md @@ -313,6 +313,24 @@ But that's actually a bad idea. There are potential problems with it: So we should never use `for..in` for arrays. + +````smart header="`for..of` over `arr.entries()`" +For "real" arrays and some array-like structures, there's one more way: + +```js run +let arr = ["Apple", "Orange", "Pear"]; + +*!* +for (let [i, item] of arr.entries()) { +*/!* + alert( i + ':' + item ); // 0:Apple, then 1:Orange, then 2:Pear +} +``` + +Here [arr.entries](mdn:js/Array/entries) is a built-in method, most array-like structures do not support that. +```` + + ## A word about "length" The `length` property automatically updates when we modify the array. It is actually not the *count* of values in the array, but the greatest numeric index plus one. @@ -413,8 +431,9 @@ We can use an array as a deque with the following operations: - `unshift(...items)` adds items to the beginning. To loop over the elements of the array: - - `for(let item of arr)` -- the modern syntax, - `for(let i=0; i взо -киборг, гробик -> бгикор -... -``` - -По такой последовательности будем делать массив уникальным. - -Для этого воспользуемся вспомогательным объектом, в который будем записывать слова по отсортированному ключу: - -```js run -function aclean(arr) { - // этот объект будем использовать для уникальности - var obj = {}; - - for (var i = 0; i < arr.length; i++) { - // разбить строку на буквы, отсортировать и слить обратно -*!* - var sorted = arr[i].toLowerCase().split('').sort().join(''); // (*) -*/!* - - obj[sorted] = arr[i]; // сохраняет только одно значение с таким ключом - } - - var result = []; - - // теперь в obj находится для каждого ключа ровно одно значение - for (var key in obj) result.push(obj[key]); - - return result; -} - -var arr = ["воз", "киборг", "корсет", "ЗОВ", "гробик", "костер", "сектор"]; - -alert( aclean(arr) ); -``` - -Приведение слова к сортированному по буквам виду осуществляется цепочкой вызовов в строке `(*)`. - -Для удобства комментирования разобьём её на несколько строк (JavaScript это позволяет): - -```js -var sorted = arr[i] // ЗОВ - .toLowerCase() // зов - .split('') // ['з','о','в'] - .sort() // ['в','з','о'] - .join(''); // взо -``` - -Получится, что два разных слова `'ЗОВ'` и `'воз'` получат одинаковую отсортированную форму `'взо'`. - -Следующая строка: - -```js -obj[sorted] = arr[i]; -``` - -В объект `obj` будет записано сначала первое из слов `obj['взо'] = "воз"`, а затем `obj['взо'] = 'ЗОВ'`. - -Обратите внимание, ключ -- отсортирован, а само слово -- в исходной форме, чтобы можно было потом получить его из объекта. - -Вторая запись по тому же ключу перезапишет первую, то есть в объекте останется ровно одно слово с таким набором букв. - diff --git a/1-js/4-data-structures/8-array-methods/10-filter-anagrams/task.md b/1-js/4-data-structures/8-array-methods/10-filter-anagrams/task.md deleted file mode 100644 index ac13ca7a..00000000 --- a/1-js/4-data-structures/8-array-methods/10-filter-anagrams/task.md +++ /dev/null @@ -1,27 +0,0 @@ -importance: 3 - ---- - -# Отфильтровать анаграммы - -*Анаграммы* -- слова, состоящие из одинакового количества одинаковых букв, но в разном порядке. -Например: - -``` -воз - зов -киборг - гробик -корсет - костер - сектор -``` - -Напишите функцию `aclean(arr)`, которая возвращает массив слов, очищенный от анаграмм. - -Например: - -```js -var arr = ["воз", "киборг", "корсет", "ЗОВ", "гробик", "костер", "сектор"]; - -alert( aclean(arr) ); // "воз,киборг,корсет" или "ЗОВ,гробик,сектор" -``` - -Из каждой группы анаграмм должно остаться только одно слово, не важно какое. - diff --git a/1-js/4-data-structures/8-iterable/article.md b/1-js/4-data-structures/8-iterable/article.md new file mode 100644 index 00000000..b25247cc --- /dev/null +++ b/1-js/4-data-structures/8-iterable/article.md @@ -0,0 +1,227 @@ + +# Iterables + +*Iterable* objects is a general concept that allows to make any object to be useable in a `for..of` loop. + +Many built-ins are partial cases of this concept. For instance, arrays are iterable. But not only arrays. Strings are iterable too. + +Iterables come from the very core of Javascript and are widely used both in built-in methods and those provided by the environment. For instance, in-browser lists of DOM-nodes are iterable. + +[cut] + +## Symbol.iterator + +We can easily grasp the concept of iterables by making one of our own. + +For instance, we have an object, that is not an array, but looks a suitable for `for..of`. + +Like a `range` object that represents an interval of numbers: + +```js +let range = { + from: 1, + to: 5 +}; + +// We want for..of to work: +// for(let num of range) ... num=1,2,3,4,5 +``` + +To make the `range` iterable (and enable `for..of`) we need to add a method to the object with the name `Symbol.iterator` (a special built-in symbol just for that). + +- When `for..of` starts, it calls that method (or errors if none found). +- The method must return an *iterator* -- an object with the method `next`. +- When `for..of` wants the next value, it calls `next()` on that object. +- The result of `next()` must have the form `{done: Boolean, value: any}`: `done:true` means that the iteration is finished, otherwise `value` must be the new value. + +Let's see how it can work for `range`: + +```js run +let range = { + from: 1, + to: 5 +} + +// call to for..of initially calls this +range[Symbol.iterator] = function() { + + // ...it returns the iterator: + return { + current: this.from, + last: this.to, + + // ...whose next() is called on each iteration of the loop + next() { + if (this.current <= this.last) { + return { done: false, value: this.current++ }; + } else { + return { done: true}; + } + } + }; +}; + +for (let num of range) { + alert(num); // 1, then 2, 3, 4, 5 +} + +alert(Math.max(...range)); // 5 (*) +``` + +There is an important separation of concerns in this code. + +- The `range` itself does not have the `next()` method. +- Instead, another object, a so-called "iterator" is created by the call to `range[Symbol.iterator]()`. +- It keeps the iteration state in its `current` property. That's good, because the original object is not modified by iterations. Also multiple `for..of` loops over the same object can run simultaneously, because they create separate iterators. + +The fact that the object itself does not do the iteration also adds flexibility, because `range[Symbol.iterator]` can create iterators the smart way, depending on other object properties or external conditions. It's a full-fledged function that may be more complex than just a `return {...}`. + +Please note that the internal mechanics is not seen from outside. Here `for..of` calls `range[Symbol.iterator]()` and then the `next()` until `done: false`, but the external code doesn't see that. It only gets values. + +```smart header="Spread operator `...` and iterables" +At the last line `(*)` of the example above, we can see that an iterable object can be expanded to a list through a spread operator. + +The call to `Math.max(...range)` internally does a full `for..of`-like iteration over `range`, and its results are used as a list of arguments for `Math.max`, in our case `Math.max(1,2,3,4,5)`. +``` + +```smart header="Infinite iterators" +Infinite iterators are also doable. For instance, the `range` becomes infinite for `range.to = Infinity`. Or we can make an iterable object that generates an infinite sequence of pseudorandom numbers. Also can be useful. + +There are no limitations on `next`, it can return more and more values, that's normal. + +Of course, the `for..of` loop over such an iterable would be endless, we'll need to stop if, for instance, using `break`. +``` + +````smart header="`Symbol.iterator` in a literal" +We could also write `Symbol.iterator` directly in the object literal, via computed properties syntax: + +```js +let range = { + from: 1, + to: 5, + [Symbol.iterator]() { + return {...}; + } +}; +``` +```` + +## Built-in iterables + +Iterators can also be created explicitly, without `for..of`, with a direct call of `Symbol.iterator`. For built-in objects too. + +For instance, this code gets a string iterator and calls it "manually": + +```js run +let str = "Hello"; + +// does the same as +// for (let char of str) alert(char); + +let iterator = str[Symbol.iterator](); + +while(true) { + let result = iterator.next(); + if (result.done) break; + alert(result.value); // outputs characters one by one +} +``` + +The same works for an array. + +## Iterables VS array-likes + +There are two official terms that are similar, but actually very different. Please take care to avoid the confusion. + +- Iterables are objects that implement the `Symbol.iterator` method, as described above. +- Array-likes are objects that have indexes and `length`, so they look like arrays. + +Sometimes they can both be applied. For instance, strings are both iterable and array-like. + +But an iterable may be not array-like and vise versa. + +For example, the `range` in the example above is not array-like, because it does not have `length`. + +And here's the object that is array-like, but not iterable: + +```js run +let arrayLike = { // has indexes and length => array-like + 0: "Hello", + 1: "World", + length: 2 +}; + +*!* +// Error (not iterable) +for(let item of arrayLike) {} +*/!* +``` + +...But what they share in common -- both iterables and array-likes are usually not arrays, they don't have `join`, `slice` etc. Or maybe have other methods that have same names, but behave differently from their `Array` counterparts. + +Remember the special `arguments` object for a function call? It is both array-like and iterable object that has all function arguments: + +```js run +function f() { + // array-like demo + alert( arguments[0] ); // 1 + alert( arguments.length ); // 2 + + // iterable demo + for(let arg of arguments) alert(arg); // 1, then 2 +} + +f(1, 2); +``` + +...But as it's not an array, the following would fail: + +```js run +function f() { +*!* + alert( arguments.join() ); // Error: arguments.join is not a function +*/!* +} + +f(1, 2); +``` + +There's a universal method [Array.from](mdn:js/Array/from) that brings them together. It takes an iterable *or* an array-like value and makes a "real" `Array` from it. + +For instance: + +```js run +let arrayLike = { + 0: "Hello", + 1: "World", + length: 2 +}; + +alert( Array.from(arrayLike).join() ); // Hello,World +``` + +```js +// assuming range is taken from the example above +alert( Array.from(range) ); // 1,2,3,4,5 +``` + +This method is really handy when we want to use array methods like `map`, `forEach` and others on array-likes or iterables. + + +## Summary + +Objects that can be used in `for..of` are called *iterable*. + +- Technically, iterables must implement the method named `Symbol.iterator`. + - The result of `obj[Symbol.iterator]` is called an *iterator*. It handles the further iteration process. + - An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the iteration end, otherwise the `value` is the next value. +- The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly. +- Built-in iterables like strings or arrays, also implement `Symbol.iterator`. + +The modern specification mostly uses iterables instead of arrays where an ordered collection is required, because they are more abstract. For instance, the spread operator `"..."` does it. + +Objects that have indexed properties and `length` are called *array-like*. Such objects may also have other properties and methods, but lack built-in methods of arrays. + +`Array.from(obj)` makes a real `Array` of an iterable or array-like `obj`, and then we can use array methods on it. + + diff --git a/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/_js.view/solution.js b/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/_js.view/solution.js new file mode 100644 index 00000000..b9f5016f --- /dev/null +++ b/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/_js.view/solution.js @@ -0,0 +1,11 @@ + +function aclean(arr) { + let map = new Map(); + + for(let word of arr) { + let sorted = word.toLowerCase().split("").sort().join(""); + map.set(sorted, word); + } + + return Array.from(map.values()); +} \ No newline at end of file diff --git a/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/_js.view/test.js b/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/_js.view/test.js new file mode 100644 index 00000000..75acb36b --- /dev/null +++ b/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/_js.view/test.js @@ -0,0 +1,24 @@ +function intersection(arr1, arr2) { + return arr1.filter(item => arr2.includes(item)); +} + +describe("aclean", function() { + + it("returns exactly 1 word from each anagram set", function() { + let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; + + let result = aclean(arr); + assert.equal(result.length, 3); + + assert.equal(intersection(result, ["nap", "PAN"]).length, 1); + assert.equal(intersection(result, ["teachers", "cheaters", "hectares"]).length, 1); + assert.equal(intersection(result, ["ear", "era"]).length, 1); + + }); + + it("is case-insensitive", function() { + let arr = ["era", "EAR"]; + assert.equal(aclean(arr).length, 1); + }); + +}); \ No newline at end of file diff --git a/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/solution.md b/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/solution.md new file mode 100644 index 00000000..23e2635d --- /dev/null +++ b/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/solution.md @@ -0,0 +1,81 @@ +To find all anagrams, let's split every word to letters and sort them. When letter-sorted, all anagrams are same. + +For instance: + +``` +nap, pan -> anp +ear, era, are -> aer +cheaters, hectares, teachers -> aceehrst +... +``` + +We'll use the letter-sorted variants as map keys to store only one value per each key: + +```js run +function aclean(arr) { + let map = new Map(); + + for(let word of arr) { + // split the word by letters, sort them and join back +*!* + let sorted = word.toLowerCase().split('').sort().join(''); // (*) +*/!* + map.set(sorted, word); + } + + return Array.from(map.values()); +} + +let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; + +alert( aclean(arr) ); +``` + +Letter-sorting is done by the chain of calls in the line `(*)`. + +For convenience let's split it into multiple lines: + +```js +var sorted = arr[i] // PAN + .toLowerCase() // pan + .split('') // ['p','a','n'] + .sort() // ['a','n','p'] + .join(''); // anp +``` + +Two different words `'PAN'` and `'nap'` receive the same letter-sorted form `'anp'`. + +The next line put the word into the map: + +```js +map.set(sorted, word); +``` + +If we ever meet a word the same letter-sorted form again, then it would overwrite the previous value with the same key in the map. So we'll always have at maximum one word per letter-form. + +At the end `Array.from(map.values())` takes an iterable over map values (we don't need keys in the result) and returns an array of them. + +Here we could also use a plain object instead of the `Map`, because keys are strings. + +That's how the solution can look: + +```js run +function aclean(arr) { + var obj = {}; + + for (var i = 0; i < arr.length; i++) { + var sorted = arr[i].toLowerCase().split("").sort().join(""); + obj[sorted] = arr[i]; + } + + return Array.from(Object.values(obj)); +} + +let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; + +alert( aclean(arr) ); +``` + + + + diff --git a/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/task.md b/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/task.md new file mode 100644 index 00000000..731fd2c2 --- /dev/null +++ b/1-js/4-data-structures/9-map-set-weakmap-weakset/1-filter-anagrams/task.md @@ -0,0 +1,28 @@ +importance: 4 + +--- + +# Filter anagrams + +[Anagrams](https://en.wikipedia.org/wiki/Anagram) are words that have the same number of same letters, but in different order. + +For instance: + +``` +nap - pan +ear - are - era +cheaters - hectares - teachers +``` + +Write a function `aclean(arr)` that returns an array cleaned from anagrams. + +For instance: + +```js +let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; + +alert( aclean(arr) ); // "nap,teachers,ear" or "PAN,cheaters,era" +``` + +From every anagram group should remain only one word, no matter which one. + diff --git a/1-js/4-data-structures/9-map-set-weakmap-weakset/1-recipients-set/solution.md b/1-js/4-data-structures/9-map-set-weakmap-weakset/2-recipients-set/solution.md similarity index 100% rename from 1-js/4-data-structures/9-map-set-weakmap-weakset/1-recipients-set/solution.md rename to 1-js/4-data-structures/9-map-set-weakmap-weakset/2-recipients-set/solution.md diff --git a/1-js/4-data-structures/9-map-set-weakmap-weakset/1-recipients-set/task.md b/1-js/4-data-structures/9-map-set-weakmap-weakset/2-recipients-set/task.md similarity index 100% rename from 1-js/4-data-structures/9-map-set-weakmap-weakset/1-recipients-set/task.md rename to 1-js/4-data-structures/9-map-set-weakmap-weakset/2-recipients-set/task.md diff --git a/1-js/4-data-structures/9-map-set-weakmap-weakset/article.md b/1-js/4-data-structures/9-map-set-weakmap-weakset/article.md index 8f35ccc9..7560f2a9 100644 --- a/1-js/4-data-structures/9-map-set-weakmap-weakset/article.md +++ b/1-js/4-data-structures/9-map-set-weakmap-weakset/article.md @@ -134,9 +134,9 @@ Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"], For looping over a `map`, there are 3 methods: -- `map.keys()` -- returns an array-like object for keys, -- `map.values()` -- returns an array-like object for values, -- `map.entries()` -- returns an array-like object for entries `[key, value]`, it's used by default in `for..of`. +- `map.keys()` -- returns an iterable object for keys, +- `map.values()` -- returns an iterable object for values, +- `map.entries()` -- returns an iterable object for entries `[key, value]`, it's used by default in `for..of`. For instance: @@ -182,7 +182,7 @@ recipeMap.forEach( (value, key, map) => { The main methods are: -- `new Set([values])` -- creates the set, optionally with an array of values (any iterable will do). +- `new Set(iterable)` -- creates the set, optionally from an array of values (any iterable will do). - `set.add(value)` -- adds a value, returns the set itself. - `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. - `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. @@ -236,6 +236,12 @@ Note the funny thing. The `forEach` function in the `Set` has 3 arguments: a val That's made for compatibility with `Map` where `forEach` has three arguments. +The same methods as `Map` has for iterators are also supported: + +- `set.keys()` -- returns an iterable object for values, +- `set.values()` -- same as `set.keys`, for compatibility with `Map`, +- `set.entries()` -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. + ## WeakMap and WeakSet `WeakSet` is a special kind of `Set` that does not prevent JavaScript from memory cleaning. `WeakMap` is the same thing for `Map`. diff --git a/1-js/5-deeper/1-recursion/article.md b/1-js/5-deeper/1-recursion/article.md index b192f5ab..e001fc17 100644 --- a/1-js/5-deeper/1-recursion/article.md +++ b/1-js/5-deeper/1-recursion/article.md @@ -22,7 +22,7 @@ There are two ways to solve it. 1. Iterative thinking -- the `for` loop: - ```js run + ```js run function pow(x, n) { let result = 1; @@ -132,7 +132,8 @@ function pow(x, n) { alert( pow(2, 3) ); ``` -The line changes, so the context is now: + +The variables are same, but the line changes, so the context is now: