renovations

This commit is contained in:
Ilya Kantor 2015-01-11 01:54:57 +03:00
parent 4b8b168fd2
commit c7d4c7e3ff
172 changed files with 869 additions and 244 deletions

View file

@ -0,0 +1,11 @@
function addClass(obj, cls) {
var classes = obj.className ? obj.className.split(' ') : [];
for(var i=0; i<classes.length; i++) {
if (classes[i] == cls) return; // класс уже есть
}
classes.push(cls); // добавить
obj.className = classes.join(' '); // и обновить свойство
}

View file

@ -0,0 +1,27 @@
describe("addClass", function() {
it("добавляет класс, которого нет", function() {
var obj = { className: 'open menu' };
addClass(obj, 'new');
assert.deepEqual(obj, {
className: 'open menu new'
});
});
it("не добавляет класс, который уже есть", function() {
var obj = { className: 'open menu' };
addClass(obj, 'open');
assert.deepEqual(obj, {
className: 'open menu'
});
});
it("не добавляет лишних пробелов, который уже есть", function() {
var obj = { className: '' };
addClass(obj, 'open');
assert.deepEqual(obj, {
className: 'open'
});
});
});

View file

@ -0,0 +1,28 @@
Решение заключается в превращении `obj.className` в массив при помощи `split`.
После этого в нем можно проверить наличие класса, и если нет - добавить.
```js
//+ run
function addClass(obj, cls) {
var classes = obj.className ? obj.className.split(' ') : [];
for(var i=0; i<classes.length; i++) {
if (classes[i] == cls) return; // класс уже есть
}
classes.push(cls); // добавить
obj.className = classes.join(' '); // и обновить свойство
}
var obj = { className: 'open menu' };
addClass(obj, 'new');
addClass(obj, 'open');
addClass(obj, 'me');
alert(obj.className) // open menu new me
```
P.S. "Альтернативный" подход к проверке наличия класса вызовом `obj.className.indexOf(cls)` был бы неверным. В частности, он найдёт `cls = "menu"` в строке классов `obj.className = "open mymenu"`.
P.P.S. Проверьте, нет ли в вашем решении присвоения `obj.className += " " + cls`. Не добавляет ли оно лишний пробел в случае, если изначально `obj.className = ""`?

View file

@ -0,0 +1,23 @@
# Добавить класс в строку
[importance 5]
В объекте есть свойство `className`, которое содержит список "классов" - слов, разделенных пробелом:
```js
var obj = {
className: 'open menu'
}
```
Создайте функцию `addClass(obj, cls)`, которая добавляет в список класс `cls`, но только если его там еще нет:
```js
addClass(obj, 'new'); // obj.className='open menu new'
addClass(obj, 'open'); // без изменений (класс уже существует)
addClass(obj, 'me'); // obj.className='open menu new me'
alert(obj.className); // "open menu new me"
```
P.S. Ваша функция не должна добавлять лишних пробелов.

View file

@ -0,0 +1,16 @@
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 = [];
for(var key in obj) {
result.push(obj[key]);
}
return result;
}

View file

@ -0,0 +1,13 @@
describe("aclean", function() {
it("удаляет анаграммы", function() {
var arr = ["воз", "киборг", "корсет", "зов", "гробик", "костер", "сектор"];
assert.sameMembers(aclean(arr), ["гробик", "зов", "сектор"]);
});
it("не различает регистр символов", function() {
var arr = ["воз", "ЗОВ"];
assert.equal( aclean(arr).length, 1 );
});
});

View file

@ -0,0 +1,70 @@
# Решение
Чтобы обнаружить анаграммы, разобьём каждое слово на буквы и отсортируем их. В отсортированном по буквам виде все анаграммы одинаковы.
Например:
```
воз, зов -> взо
киборг, гробик -> бгикор
...
```
По такой последовательности будем делать массив уникальным.
Для этого воспользуемся вспомогательным объектом, в который будем записывать слова по отсортированному ключу:
```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['взо'] = 'ЗОВ'`.
Обратите внимание, ключ -- отсортирован, а само слово -- в исходной форме, чтобы можно было потом получить его из объекта.
Вторая запись по тому же ключу перезапишет первую, то есть в объекте останется ровно одно слово с таким набором букв.

View file

@ -0,0 +1,27 @@
# Отфильтровать анаграммы
[importance 3]
*Анаграммы* -- слова, состоящие из одинакового количества одинаковых букв, но в разном порядке.
Например:
```
воз - зов
киборг - гробик
корсет - костер - сектор
```
Напишите функцию `aclean(arr)`, которая возвращает массив слов, очищенный от анаграмм.
Например:
```js
var arr = ["воз", "киборг", "корсет", "ЗОВ", "гробик", "костер", "сектор"];
alert( aclean(arr) ); // "воз,киборг,корсет" или "ЗОВ,гробик,сектор"
```
Из каждой группы анаграмм должно остаться только одно слово, не важно какое.

View file

@ -0,0 +1,10 @@
function unique(arr) {
var obj = {};
for(var i=0; i<arr.length; i++) {
var str = arr[i];
obj[str] = true; // запомнить строку в виде свойства объекта
}
return Object.keys(obj); // или собрать ключи перебором для IE<9
}

View file

@ -0,0 +1,14 @@
describe("unique", function() {
it("убирает неуникальные элементы из массива", function() {
var strings = ["кришна", "кришна", "харе", "харе",
"харе", "харе", "кришна", "кришна", "8-()"];
assert.deepEqual( unique(strings), ["кришна", "харе", "8-()"] );
});
it("не изменяет исходный массив", function() {
var strings = ["кришна", "кришна", "харе", "харе"];
unique(strings);
assert.deepEqual( strings, ["кришна", "кришна", "харе", "харе"] );
});
});

View file

@ -0,0 +1,81 @@
# Решение перебором (медленное)
Пройдём по массиву вложенным циклом.
Для каждого элемента мы будем искать, был ли такой уже. Если был -- игнорировать:
```js
//+ run
function unique(arr) {
var obj = {};
var result = [];
nextInput:
for(var i=0; i<arr.length; i++) {
var str = arr[i]; // для каждого элемента
for(var j=0; j<result.length; j++) { // ищем, был ли он уже?
if (result[j] == str) continue nextInput; // если да, то следующий
}
result.push(str);
}
return result;
}
var strings = ["кришна", "кришна", "харе", "харе",
"харе", "харе", "кришна", "кришна", "8-()"];
alert( unique(strings) ); // кришна, харе, 8-()
```
Давайте посмотрим, насколько быстро он будет работать.
Предположим, в массиве `100` элементов. Если все они одинаковые, то `result` будет состоять из одного элемента и вложенный цикл будет выполняться сразу. В этом случае всё хорошо.
А если все, или почти все элементы разные?
В этом случае для каждого элемента понадобится обойти весь текущий массив результатов, после чего -- добавить в этот массив.
<ol>
<li>Для первого элемента -- это обойдётся в `0` операций доступа к элементам `result` (он пока пустой).</li>
<li>Для второго элемента -- это обойдётся в `1` операцию доступа к элементам `result`.</li>
<li>Для третьего элемента -- это обойдётся в `2` операции доступа к элементам `result`.</li>
<li>...Для n-го элемента -- это обойдётся в `n-1` операций доступа к элементам `result`.</li>
</ul>
Всего <code>0 + 1 + 2 + ... + n-1 = (n-1)*n/2 = n<sup>2</sup>/2 - n/2</code> (как сумма арифметической прогрессии), то есть количество операций растёт примерно как квадрат от `n`.
Это очень быстрый рост. Для `100` элементов -- `4950` операций, для `1000` -- `499500` (по формуле выше).
Поэтому такое решение подойдёт только для небольших массивов. Вместо вложенного `for` можно использовать и `arr.indexOf`, ситуация от этого не поменяется, так как `indexOf` тоже ищет перебором.
# Решение с объектом (быстрое)
Наилучшая техника для выбора уникальных строк -- использование вспомогательного объекта. Ведь название свойства в объекте, с одной стороны -- строка, а с другой -- всегда уникально. Повторная запись в свойство с тем же именем перезапишет его.
Например, если `"харе"` попало в объект один раз (`obj["харе"] = true`), то второе такое же присваивание ничего не изменит.
Решение ниже создаёт объект `obj = {}` и записывает в него все строки как имена свойств. А затем собирает свойства из объекта в массив через `for..in`. Дубликатов уже не будет.
```js
//+ run
function unique(arr) {
var obj = {};
for(var i=0; i<arr.length; i++) {
var str = arr[i];
*!*
obj[str] = true; // запомнить строку в виде свойства объекта
*/!*
}
return Object.keys(obj); // или собрать ключи перебором для IE8-
}
var strings = ["кришна", "кришна", "харе", "харе",
"харе", "харе", "кришна", "кришна", "8-()"];
alert( unique(strings) ); // кришна, харе, 8-()
```
Так что можно положить все значения как ключи в объект, а потом достать.

View file

@ -0,0 +1,21 @@
# Оставить уникальные элементы массива
[importance 3]
Пусть `arr` -- массив строк.
Напишите функцию `unique(arr)`, которая возвращает массив, содержащий только уникальные элементы `arr`.
Например:
```js
function unique(arr) {
/* ваш код */
}
var strings = ["кришна", "кришна", "харе", "харе",
"харе", "харе", "кришна", "кришна", "8-()"];
alert( unique(strings) ); // кришна, харе, 8-()
```

View file

@ -0,0 +1,10 @@
function camelize(str) {
var arr = str.split('-');
for(var i=1; i<arr.length; i++) {
// преобразовать: первый символ с большой буквы
arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1);
}
return arr.join('');
}

View file

@ -0,0 +1,22 @@
describe("camelize", function() {
it("оставляет пустую строку \"как есть\"", function() {
assert.equal( camelize(""), "");
});
describe("делает заглавным первый символ после дефиса", function() {
it("превращает background-color в backgroundColor", function() {
assert.equal( camelize("background-color"), "backgroundColor");
});
it("превращает list-style-image в listStyleImage", function() {
assert.equal( camelize("list-style-image"), "listStyleImage");
});
it("превращает -webkit-transition в WebkitTransition", function() {
assert.equal( camelize("-webkit-transition"), "WebkitTransition");
});
});
});

View file

@ -0,0 +1,26 @@
# Идея
Задача может быть решена несколькими способами. Один из них -- разбить строку по дефису `str.split('-')`, затем последовательно сконструировать новую.
# Решение
Разобьем строку в массив, а затем преобразуем его элементы и сольём обратно:
```js
//+ run
function camelize(str) {
var arr = str.split('-');
for(var i=1; i<arr.length; i++) {
// преобразовать: первый символ с большой буквы
arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1);
}
return arr.join('');
}
alert( camelize("background-color")); // backgroundColor
alert( camelize("list-style-image") ); // listStyleImage
alert( camelize("-webkit-transition") ); // WebkitTransition
```

View file

@ -0,0 +1,20 @@
# Перевести текст вида border-left-width в borderLeftWidth
[importance 3]
Напишите функцию `camelize(str)`, которая преобразует строки вида "my-short-string" в "myShortString".
То есть, дефисы удаляются, а все слова после них получают заглавную букву.
Например:
```js
camelize("background-color") == 'backgroundColor';
camelize("list-style-image") == 'listStyleImage';
camelize("-webkit-transition") == 'WebkitTransition';
```
Такая функция полезна при работе с CSS.
P.S. Вам пригодятся методы строк `charAt`, `split` и `toUpperCase`.

View file

@ -0,0 +1,11 @@
function removeClass(obj, cls) {
var classes = obj.className.split(' ');
for(i=0; i<classes.length; i++) {
if (classes[i] == cls) {
classes.splice(i, 1); // удалить класс
i--;
}
}
obj.className = classes.join(' ');
}

View file

@ -0,0 +1,35 @@
describe("removeClass", function() {
it("ничего не делает, если класса нет", function() {
var obj = { className: 'open menu' };
removeClass(obj, 'new');
assert.deepEqual(obj, {
className: 'open menu'
});
});
it("не меняет пустое свойство", function() {
var obj = { className: '' };
removeClass(obj, 'new');
assert.deepEqual(obj, {
className: ""
});
});
it("удаляет класс, не оставляя лишних пробелов", function() {
var obj = { className: 'open menu' };
removeClass(obj, 'open');
assert.deepEqual(obj, {
className: "menu"
});
});
it("если класс один и он удалён, то результат - пустая строка", function() {
var obj = { className: "menu" };
removeClass(obj, 'menu');
assert.deepEqual(obj, {
className: ""
});
});
});

View file

@ -0,0 +1,31 @@
Решение заключается в том, чтобы разбить `className` в массив классов, а затем пройтись по нему циклом. Если класс есть - удаляем его `splice`, заново объединяем массив в строку и присваиваем объекту.
```js
//+ run
function removeClass(obj, cls) {
var classes = obj.className.split(' ');
for(i=0; i<classes.length; i++) {
if (classes[i] == cls) {
classes.splice(i, 1); // удалить класс
*!*
i--; // (*)
*/!*
}
}
obj.className = classes.join(' ');
}
var obj = { className: 'open menu menu' }
removeClass(obj, 'blabla');
removeClass(obj, 'menu')
alert(obj.className) // open
```
В примере выше есть тонкий момент. Элементы массива проверяются один за другим. При вызове `splice` удаляется текущий, `i-й` элемент, и те элементы, которые идут дальше, сдвигаются на его место.
Таким образом, **на месте `i` оказывается новый, непроверенный элемент**.
Чтобы это учесть, строчка `(*)` уменьшает `i`, чтобы следующая итерация цикла заново проверила элемент с номером `i`. Без нее функция будет работать с ошибками.

View file

@ -0,0 +1,28 @@
# Функция removeClass
[importance 5]
У объекта есть свойство `className`, которое хранит список "классов" - слов, разделенных пробелами:
```js
var obj = {
className: 'open menu'
};
```
Напишите функцию `removeClass(obj, cls)`, которая удаляет класс `cls`, если он есть:
```js
removeClass(obj, 'open'); // obj.className='menu'
removeClass(obj, 'blabla'); // без изменений (нет такого класса)
```
P.S. Дополнительное усложнение. Функция должна корректно обрабатывать дублирование класса в строке:
```js
obj = { className: 'my menu menu' };
removeClass(obj, 'menu');
alert(obj.className); // 'my'
```
Лишних пробелов после функции образовываться не должно.

View file

@ -0,0 +1,10 @@
function filterRangeInPlace(arr, a, b) {
for(var i = 0; i<arr.length; i++) {
var val = arr[i];
if (val < a || val > b) {
arr.splice(i--, 1);
}
}
}

View file

@ -0,0 +1,9 @@
describe("filterRangeInPlace", function() {
it("меняет массив, оставляя только значения из диапазона", function() {
var arr = [5, 3, 8, 1];
filterRangeInPlace(arr, 1, 4);
assert.deepEqual(arr, [3, 1]);
});
});

View file

@ -0,0 +1,21 @@
```js
//+ run
function filterRangeInPlace(arr, a, b) {
for(var i = 0; i<arr.length; i++) {
var val = arr[i];
if (val < a || val > b) {
arr.splice(i--, 1);
}
}
}
var arr = [5, 3, 8, 1];
filterRangeInPlace(arr, 1, 4);
alert(arr); // [3, 1]
```

View file

@ -0,0 +1,17 @@
# Фильтрация массива "на месте"
[importance 4]
Создайте функцию `filterRangeInPlace(arr, a, b)`, которая получает массив с числами `arr` и удаляет из него все числа вне диапазона `a..b`.
То есть, проверка имеет вид `a ≤ arr[i] ≤ b`. Функция должна менять сам массив и ничего не возвращать.
Например:
```js
arr = [5, 3, 8, 1];
filterRangeInPlace(arr, 1, 4); // удалены числа вне диапазона 1..4
alert(arr); // массив изменился: остались [3, 1]
```

View file

@ -0,0 +1,15 @@
```js
//+ run
var arr = [ 5, 2, 1, -10, 8];
function compareReversed(a, b) {
return b - a;
}
arr.sort(compareReversed);
alert(arr);
```

View file

@ -0,0 +1,14 @@
# Сортировать в обратном порядке
[importance 5]
Как отсортировать массив чисел в обратном порядке?
```js
var arr = [ 5, 2, 1, -10, 8];
// отсортируйте?
alert(arr); // 8, 5, 2, 1, -10
```

View file

@ -0,0 +1,14 @@
Для копирования массива используем `slice()`, и тут же -- сортировку:
```js
//+ run
var arr = [ "HTML", "JavaScript", "CSS" ];
*!*
var arrSorted = arr.slice().sort();
*/!*
alert(arrSorted);
alert(arr);
```

View file

@ -0,0 +1,18 @@
# Скопировать и отсортировать массив
[importance 5]
Есть массив строк `arr`. Создайте массив `arrSorted` -- из тех же элементов, но отсортированный.
Исходный массив не должен меняться.
```js
var arr = [ "HTML", "JavaScript", "CSS" ];
// ... ваш код ...
alert(arrSorted); // CSS, HTML, JavaScript
alert(arr); // HTML, JavaScript, CSS (без изменений)
```
Постарайтесь сделать код как можно короче.

View file

@ -0,0 +1,23 @@
# Подсказка
Функция сортировки должна возвращать случайный результат сравнения. Используйте для этого [Math.random](http://javascript.ru/Math.random).
# Решение
Обычно `Math.random()` возвращает результат от `0` до `1`. Вычтем `0.5`, чтобы область значений стала `[-0.5 ... 0.5)`.
```js
//+ run
var arr = [1, 2, 3, 4, 5];
*!*
function compareRandom(a, b) {
return Math.random() - 0.5;
}
arr.sort(compareRandom);
*/!*
alert(arr); // элементы в случайном порядке, например [3,5,1,2,4]
```

View file

@ -0,0 +1,14 @@
# Случайный порядок в массиве
[importance 3]
Используйте функцию `sort` для того, чтобы "перетрясти" элементы массива в случайном порядке.
```js
var arr = [1, 2, 3, 4, 5];
arr.sort(ваша функция);
alert(arr); // элементы в случайном порядке, например [3,5,1,2,4]
```

View file

@ -0,0 +1,26 @@
Для сортировки объявим и передадим в `sort` анонимную функцию, которая сравнивает объекты по полю `age`:
```js
//+ run
*!*
// Наша функция сравнения
function compareAge(personA, personB) {
return personA.age - personB.age;
}
*/!*
// проверка
var vasya = { name: "Вася", age: 23 };
var masha = { name: "Маша", age: 18 };
var vovochka = { name: "Вовочка", age: 6 };
var people = [ vasya , masha , vovochka ];
people.sort(compareAge);
// вывести
for(var i=0; i<people.length; i++) {
alert(people[i].name); // Вовочка Маша Вася
}
```

View file

@ -0,0 +1,22 @@
# Сортировка объектов
[importance 5]
Напишите код, который отсортирует массив объектов `people` по полю `age`.
Например:
```js
var vasya = { name: "Вася", age: 23 };
var masha = { name: "Маша", age: 18 };
var vovochka = { name: "Вовочка", age: 6 };
var people = [ vasya , masha , vovochka ];
... ваш код ...
// теперь people: [vovochka, masha, vasya]
alert(people[0].age) // 6
```
Выведите список имён в массиве после сортировки.

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="594px" height="53px" viewBox="0 0 594 53" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
<title>Slice 1</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="linked-list" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<rect id="Rectangle-1" stroke="#979797" stroke-width="2" sketch:type="MSShapeGroup" x="0" y="19" width="80" height="34"></rect>
<text id="value" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="3" y="10">value</tspan>
</text>
<text id="1" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="35" y="40">1</tspan>
</text>
<path d="M65.73,35 L137.27,35" id="Line" stroke="#979797" stroke-width="2" stroke-linecap="square" fill="#979797" sketch:type="MSShapeGroup"></path>
<path id="Line-decoration-1" d="M136.73,35 C132.95,33.95 129.71,33.05 125.93,32 C125.93,34.1 125.93,35.9 125.93,38 C129.71,36.95 132.95,36.05 136.73,35 C136.73,35 136.73,35 136.73,35 Z" stroke="#979797" stroke-width="2" stroke-linecap="square" fill="#979797"></path>
<text id="next" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="89" y="24">next</tspan>
</text>
<rect id="Rectangle-2" stroke="#979797" stroke-width="2" sketch:type="MSShapeGroup" x="140" y="19" width="80" height="34"></rect>
<text id="value-2" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="143" y="10">value</tspan>
</text>
<text id="2" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="175" y="40">2</tspan>
</text>
<path d="M205.73,35 L277.27,35" id="Line-2" stroke="#979797" stroke-width="2" stroke-linecap="square" fill="#979797" sketch:type="MSShapeGroup"></path>
<path id="Line-2-decoration-1" d="M276.73,35 C272.95,33.95 269.71,33.05 265.93,32 C265.93,34.1 265.93,35.9 265.93,38 C269.71,36.95 272.95,36.05 276.73,35 C276.73,35 276.73,35 276.73,35 Z" stroke="#979797" stroke-width="2" stroke-linecap="square" fill="#979797"></path>
<text id="next-2" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="229" y="24">next</tspan>
</text>
<rect id="Rectangle-3" stroke="#979797" stroke-width="2" sketch:type="MSShapeGroup" x="279" y="19" width="80" height="34"></rect>
<text id="value-3" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="282" y="10">value</tspan>
</text>
<text id="3" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="314" y="40">3</tspan>
</text>
<path d="M344.73,35 L416.27,35" id="Line-3" stroke="#979797" stroke-width="2" stroke-linecap="square" fill="#979797" sketch:type="MSShapeGroup"></path>
<path id="Line-3-decoration-1" d="M415.73,35 C411.95,33.95 408.71,33.05 404.93,32 C404.93,34.1 404.93,35.9 404.93,38 C408.71,36.95 411.95,36.05 415.73,35 C415.73,35 415.73,35 415.73,35 Z" stroke="#979797" stroke-width="2" stroke-linecap="square" fill="#979797"></path>
<text id="next-3" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="368" y="24">next</tspan>
</text>
<rect id="Rectangle-4" stroke="#979797" stroke-width="2" sketch:type="MSShapeGroup" x="419" y="19" width="80" height="34"></rect>
<text id="value-4" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="422" y="10">value</tspan>
</text>
<text id="4" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="454" y="40">4</tspan>
</text>
<path d="M484.73,35 L556.27,35" id="Line-4" stroke="#979797" stroke-width="2" stroke-linecap="square" fill="#979797" sketch:type="MSShapeGroup"></path>
<path id="Line-4-decoration-1" d="M555.73,35 C551.95,33.95 548.71,33.05 544.93,32 C544.93,34.1 544.93,35.9 544.93,38 C548.71,36.95 551.95,36.05 555.73,35 C555.73,35 555.73,35 555.73,35 Z" stroke="#979797" stroke-width="2" stroke-linecap="square" fill="#979797"></path>
<text id="next-4" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="508" y="24">next</tspan>
</text>
<text id="null" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-style="italic" font-weight="normal" fill="#000000">
<tspan x="564" y="39">null</tspan>
</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

@ -0,0 +1,142 @@
# Вывод списка в цикле
```js
//+ run
var list = {
value: 1, next: {
value: 2, next: {
value: 3, next: {
value: 4, next: null
}
}
}
};
function printList(list) {
var tmp = list;
while(tmp) {
alert( tmp.value );
tmp = tmp.next;
}
}
printList(list);
```
Обратите внимание, что для прохода по списку используется временная переменная `tmp`, а не `list`. Можно было бы и бегать по списку, используя входной параметр функции:
```js
function printList(list) {
while(*!*list*/!*) {
alert( list.value );
list = list.next;
}
}
```
...Но при этом мы в будущем не сможем расширить функцию и сделать со списком что-то ещё, ведь после окончания цикла начало списка уже нигде не хранится.
Поэтому и используется временная переменная -- чтобы сделать код расширяемым, и, кстати, более понятным, ведь роль `tmp` -- исключительно обход списка, как `i` в цикле `for`.
# Вывод списка с рекурсией
Рекурсивный вариант `printList(list)` следует простой логике: вывести текущее значение `(1)`, а затем пропустить через себя следующее `(2)`:
```js
//+ run
var list = {
value: 1, next: {
value: 2, next: {
value: 3, next: {
value: 4, next: null
}
}
}
};
function printList(list) {
alert(list.value); // (1)
if (list.next) {
printList(list.next); // (2)
}
}
printList(list);
```
# Обратный вывод с рекурсией
Обратный вывод -- почти то же самое, что прямой, просто сначала мы обрабатываем следующее значение, а потом -- текущее:
```js
//+ run
var list = {
value: 1, next: {
value: 2, next: {
value: 3, next: {
value: 4, next: null
}
}
}
};
function printReverseList(list) {
if (list.next) {
printReverseList(list.next);
}
alert(list.value);
}
printReverseList(list);
```
# Обратный вывод без рекурсии
```js
//+ run
var list = {
value: 1, next: {
value: 2, next: {
value: 3, next: {
value: 4, next: null
}
}
}
};
function printReverseList(list) {
var arr = [];
var tmp = list;
while(tmp) {
arr.push(tmp.value);
tmp = tmp.next;
}
for( var i = arr.length-1; i>=0; i-- ) {
alert( arr[i] );
}
}
printReverseList(list);
```
**Обратный вывод без рекурсии быстрее.**
По сути, рекурсивный вариант и нерекурсивный работают одинаково: они проходят список и запоминают его элементы, а потом выводят в обратном порядке.
В случае с массивом это очевидно, а для рекурсии запоминание происходит в стеке (внутренней специальной структуре данных): когда вызывается вложенная функция, то интерпретатор сохраняет в стек текущие параметры. Вложенные вызовы заполняют стек, а потом он выводится в обратном порядке.
При этом, при рекурсии в стеке сохраняется не только элемент списка, а другая вспомогательная информация, необходимая для возвращения из вложенного вызова. Поэтому тратится больше памяти. Все эти расходы отсутствуют во варианте без рекурсии, так как в массиве хранится именно то, что нужно.
Преимущество рекурсии, с другой стороны -- более короткий и, зачастую, более простой код.

View file

@ -0,0 +1,49 @@
# Вывести односвязный список
[importance 5]
[Односвязный список](http://ru.wikipedia.org/wiki/Связный_список) -- это структура данных, которая состоит из *элементов*, каждый из которых хранит ссылку на следующий. Последний элемент может не иметь ссылки, либо она равна `null`.
Например, объект ниже задаёт односвязный список, в `next` хранится ссылка на следующий элемент:
```js
var list = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: {
value: 4,
next: null
}
}
}
};
```
Графическое представление этого списка:
<img src="linked-list.svg">
Альтернативный способ создания:
```js
var list = { value: 1 };
list.next = { value: 2 };
list.next.next = { value: 3 };
list.next.next.next = { value: 4 };
```
Такая структура данных интересна тем, что можно очень быстро разбить список на части, объединить списки, удалить или добавить элемент в любое место, включая начало. При использовании массива такие действия требуют обширных перенумерований.
Задачи:
<ol>
<li>Напишите функцию `printList(list)`, которая выводит элементы списка по очереди, при помощи цикла.</li>
<li>Напишите функцию `printList(list)` при помощи рекурсии.</li>
<li>Напишите функцию `printReverseList(list)`, которая выводит элементы списка в обратном порядке, при помощи рекурсии.
Для списка выше она должна выводить `4`,`3`,`2`,`1`</li>
<li>Сделайте вариант `printReverseList(list)`, использующий не рекурсию, а цикл.</li>
</ol>
Как лучше -- с рекурсией или без?

View file

@ -0,0 +1,452 @@
# Массивы: методы
В этой главе мы рассмотрим встроенные методы массивов JavaScript.
[cut]
## Метод split
Ситуация из реальной жизни. Мы пишем сервис отсылки сообщений и посетитель вводит имена тех, кому его отправить: `Маша, Петя, Марина, Василий...`. Но нам-то гораздо удобнее работать с массивом имен, чем с одной строкой.
К счастью, есть метод `split(s)`, который позволяет превратить строку в массив, разбив ее по разделителю `s`. В примере ниже таким разделителем является строка из запятой и пробела.
```js
//+ run
var names = 'Маша, Петя, Марина, Василий';
var arr = names.split(', ');
for (var i=0; i<arr.length; i++) {
alert('Вам сообщение ' + arr[i]);
}
```
[smart header="Второй аргумент `split`"]
У метода `split` есть необязательный второй аргумент -- ограничение на количество элементов в массиве. Если их больше, чем указано -- остаток массива будет отброшен:
```js
//+ run
alert( "a,b,c,d".split(',', *!*2*/!*) ); // a,b
```
[/smart]
[smart header="Разбивка по буквам"]
Вызов `split` с пустой строкой разобьёт по буквам:
```js
//+ run
var str = "тест";
alert( str.split('') ); // т,е,с
```
[/smart]
## Метод join
Вызов `arr.join(str)` делает в точности противоположное `split`. Он берет массив и склеивает его в строку, используя `str` как разделитель.
Например:
```js
//+ run
var arr = ['Маша', 'Петя', 'Марина', 'Василий'];
var str = arr.join(';');
alert(str); // Маша;Петя;Марина;Василий
```
[smart header="new Array + join = Повторение строки"]
Код для повторения строки `3` раза:
```js
//+ run
alert( new Array(4).join("ля") ); // ляляля
```
Как видно, `new Array(4)` делает массив без элементов длины 4, который `join` объединяет в строку, вставляя *между его элементами* строку `"ля"`.
В результате, так как элементы пусты, получается повторение строки. Такой вот небольшой трюк.
[/smart]
## Удаление из массива
Так как массивы являются объектами, то для удаления ключа можно воспользоваться обычным `delete`:
```js
//+ run
var arr = ["Я", "иду", "домой"];
delete arr[1]; // значение с индексом 1 удалено
// теперь arr = ["Я", undefined, "домой"];
alert(arr[1]); // undefined
```
Да, элемент удален из массива, но не так, как нам этого хочется. Образовалась "дырка".
Это потому, что оператор `delete` удаляет пару "ключ-значение". Это -- все, что он делает. Обычно же при удалении из массива мы хотим, чтобы оставшиеся элементы сдвинулись и заполнили образовавшийся промежуток.
Поэтому для удаления используются специальные методы: из начала -- `shift`, с конца -- `pop`, а из середины -- `splice`, с которым мы сейчас познакомимся.
## Метод splice
Метод `splice` -- это универсальный раскладной нож для работы с массивами. Умеет все: удалять элементы, вставлять элементы, заменять элементы -- по очереди и одновременно.
Его синтаксис:
<dl>
<dt>`arr.splice(index[, deleteCount, elem1, ..., elemN])`</dt>
<dd>Удалить `deleteCount` элементов, начиная с номера `index`, а затем вставить `elem1, ..., elemN` на их место.</dd>
</dl>
Посмотрим примеры.
```js
//+ run
var arr = ["Я", "изучаю", "JavaScript"];
*!*
arr.splice(1, 1); // начиная с позиции 1, удалить 1 элемент
*/!*
alert(arr); // осталось ["Я", "JavaScript"]
```
Ниже продемонстрировано, как использовать `splice` для удаления одного элемента. Следующие за удаленным элементы сдвигаются, чтобы заполнить его место.
```js
//+ run
var arr = ["Я", "изучаю", "JavaScript"];
*!*
arr.splice(0, 1); // удалить 1 элемент, начиная с позиции 0
*/!*
alert( arr[0] ); // "изучаю" стал первым элементом
```
Следующий пример показывает, как *заменять элементы*:
```js
//+ run
var arr = [*!*"Я", "сейчас", "изучаю",*/!* "JavaScript"];
// удалить 3 первых элемента и добавить другие вместо них
arr.splice(0, 3, "Мы", "изучаем")
alert( arr ) // теперь [*!*"Мы", "изучаем"*/!*, "JavaScript"]
```
Метод `splice` возвращает массив из удаленных элементов:
```js
//+ run
var arr = [*!*"Я", "сейчас",*/!* "изучаю", "JavaScript"];
// удалить 2 первых элемента
var removed = arr.splice(0, 2);
alert( removed ); // "Я", "сейчас" <-- array of removed elements
```
Метод `splice` также может вставлять элементы без удаления, для этого достаточно установить `deleteCount` в `0`:
```js
//+ run
var arr = ["Я", "изучаю", "JavaScript"];
// с позиции 2
// удалить 0
// вставить "сложный", "язык"
arr.splice(2, 0, "сложный", "язык");
alert(arr); // "Я", "изучаю", "сложный", "язык", "JavaScript"
```
Допускается использование отрицательного номера позиции, которая в этом случае отсчитывается с конца:
```js
//+ run
var arr = [1, 2, 5]
// начиная с позиции индексом -1 (предпоследний элемент)
// удалить 0 элементов,
// затем вставить числа 3 и 4
arr.splice(-1, 0, 3, 4);
alert(arr); // результат: 1,2,3,4,5
```
## Метод slice
Метод `slice(begin, end)` копирует участок массива от `begin` до `end`, не включая `end`. Исходный массив при этом не меняется.
Например:
```js
//+ run
var arr = ["Почему", "надо", "учить", "JavaScript"];
var arr2 = arr.slice(1,3); // элементы 1, 2 (не включая 3)
alert(arr2); // надо, учить
```
Аргументы ведут себя так же, как и в строковом `slice`:
<ul>
<li>Если не указать `end` -- копирование будет до конца массива:
```js
//+ run
var arr = ["Почему", "надо", "учить", "JavaScript"];
alert( arr.slice(1) ); // взять все элементы, начиная с номера 1
```
</li>
<li>Можно использовать отрицательные индексы, они отсчитываются с конца:
```js
var arr2 = arr.slice(-2); // копировать от 2го элемента с конца и дальше
```
</li>
<li>Если вообще не указать аргументов -- скопируется весь массив:
```js
var fullCopy = arr.slice();
```
</li>
</ul>
[smart header="Совсем как в строках"]
Синтаксис метода `slice` одинаков для строк и для массивов. Тем проще его запомнить.
[/smart]
## Сортировка, метод sort(fn)
Метод `sort()` сортирует массив *на месте*. Например:
```js
//+ run
var arr = [ 1, 2, 15 ];
arr.sort();
alert( arr ); // *!*1, 15, 2*/!*
```
Не заметили ничего странного в этом примере?
Порядок стал `1, 15, 2`, это точно не сортировка чисел. Почему?
**Это произошло потому, что по умолчанию `sort` сортирует, преобразуя элементы к строке.**
Поэтому и порядок у них строковый, ведь `"2" > "15"`.
### Свой порядок сортировки
Для указания своего порядка сортировки в метод `arr.sort(fn)` нужно передать функцию `fn` от двух элементов, которая умеет сравнивать их.
Внутренний алгоритм функции сортировки умеет сортировать любые массивы -- апельсинов, яблок, пользователей, и тех и других и третьих -- чего угодно. Но для этого ему нужно знать, как их сравнивать. Эту роль и выполняет `fn`.
Если эту функцию не указать, то элементы сортируются как строки.
Например, укажем эту функцию явно, отсортируем элементы массива как числа:
```js
//+ run
function compareNumeric(a, b) {
if (a > b) return 1;
if (a < b) return -1;
}
var arr = [ 1, 2, 15 ];
*!*
arr.sort(compareNumeric);
*/!*
alert(arr); // *!*1, 2, 15*/!*
```
Обратите внимание, мы передаём в `sort()` именно саму функцию `compareNumeric`, без вызова через скобки. Был бы ошибкой следующий код:
```js
arr.sort( compareNumeric*!*()*/!* ); // не сработает
```
Как видно из примера выше, функция, передаваемая `sort`, должна иметь два аргумента.
Алгоритм сортировки, встроенный в JavaScript, будет передавать ей для сравнения элементы массива. Она должна возвращать:
<ul>
<li>Положительное значение, если `a > b`,</li>
<li>Отрицательное значение, если `a < b`,</li>
<li>Если равны -- можно `0`, но вообще -- не важно, что возвращать, их взаимный порядок не имеет значения.</li>
</ul>
[smart header="Алгоритм сортировки"]
В методе `sort`, внутри самого интерпретатора JavaScript, реализован универсальный алгоритм сортировки. Как правило, это ["\"быстрая сортировка\""](http://algolist.manual.ru/sort/quick_sort.php), дополнительно оптимизированная для небольших массивов.
Он решает, какие пары элементов и когда сравнивать, чтобы отсортировать побыстрее. Мы даём ему функцию -- способ сравнения, дальше он вызывает её сам.
Кстати, те значения, с которыми `sort` вызывает функцию сравнения, можно увидеть, если вставить в неё `alert`:
```js
//+ run
[1, -2, 15, 2, 0, 8].sort(function(a, b) {
alert(a + " <> " + b);
});
```
[/smart]
[smart header="Сравнение `compareNumeric` в одну строку"]
Функцию `compareNumeric` для сравнения элементов-чисел можно упростить до одной строчки.
```js
function compareNumeric(a, b) {
return a - b;
}
```
Эта функция вполне подходит для `sort`, так как возвращает положительное число, если `a > b`, отрицательное, если наоборот, и `0`, если числа равны.
[/smart]
## reverse
Метод [arr.reverse()](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/reverse) меняет порядок элементов в массиве на обратный.
```js
//+ run
var arr = [1,2,3];
arr.reverse();
alert(arr); // 3,2,1
```
## concat
Метод [arr.concat(value1, value2, ... valueN)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/concat) создаёт новый массив, в который копируются элементы из `arr`, а также `value1, value2, ... valueN`.
Например:
```js
//+ run
var arr = [1,2];
*!*
var newArr = arr.concat(3,4);
*/!*
alert(newArr); // 1,2,3,4
```
У `concat` есть одна забавная особенность.
Если аргумент `concat` -- массив, то `concat` добавляет элементы из него.
Например:
```js
//+ run
var arr = [1,2];
*!*
var newArr = arr.concat( [3,4], 5);// то же самое, что arr.concat(3,4,5)
*/!*
alert(newArr); // 1,2,3,4,5
```
## indexOf/lastIndexOf
Эти методы не поддерживаются в IE<9. Для их поддержки подключите библиотеку [ES5-shim](https://github.com/kriskowal/es5-shim).
Метод ["arr.indexOf(searchElement[, fromIndex])"](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf) возвращает номер элемента `searchElement` в массиве `arr` или `-1`, если его нет.
Поиск начинается с номера `fromIndex`, если он указан. Если нет -- с начала массива.
**Для поиска используется строгое сравнение `===`.**
Например:
```js
//+ run
var arr = [ 1, 0, false ];
alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1
```
Как вы могли заметить, по синтаксису он полностью аналогичен методу [indexOf для строк](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/indexOf).
Метод ["arr.lastIndexOf(searchElement[, fromIndex])"](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf) ищет справа-налево: с конца массива или с номера `fromIndex`, если он указан.
[warn header="Методы `indexOf/lastIndexOf` осуществляют поиск перебором"]
Если нужно проверить, существует ли значение в массиве -- его нужно перебрать. Только так. Внутренняя реализация `indexOf/lastIndexOf` осуществляет полный перебор, аналогичный циклу `for` по массиву. Чем длиннее массив, тем дольше он будет работать.
[/warn]
[smart header="Коллекция уникальных элементов"]
Рассмотрим задачу -- есть коллекция строк, и нужно быстро проверять: есть ли в ней какой-то элемент. Массив для этого не подходит из-за медленного `indexOf`. Но подходит объект! Доступ к свойству объекта осуществляется очень быстро, так что можно сделать все элементы ключами объекта и проверять, есть ли уже такой ключ.
Например, организуем такую проверку для коллекции строк `"div"`, `"a"` и `"form"`:
```js
var store = { }; // объект для коллекции
var items = ["div", "a", "form"];
for(var i=0; i<items.length; i++) {
var key = items[i]; // для каждого элемента создаём свойство
store[ key ] = true; // значение здесь не важно
}
```
Теперь для проверки, есть ли ключ `key`, достаточно выполнить `if (store[key])`. Если есть -- можно использовать значение, если нет -- добавить.
Такое решение работает только со строками, но применимо к любым элементам, для которых можно вычислить строковый "уникальный ключ".
[/smart]
## Object.keys(obj)
Ранее мы говорили о том, что свойства объекта можно перебрать в цикле `for..in`.
Если мы хотим работать с ними в виде массива, то к нашим услугам -- замечательный метод [Object.keys(obj)](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys). Он поддерживается везде, кроме IE8-:
```js
//+ run
var user = {
name: "Петя",
age: 30
}
var keys = Object.keys(user);
alert(keys); // name, age
```
## Итого
Методы:
<ul>
<li>`push/pop`, `shift/unshift`, `splice` -- для добавления и удаления элементов.</li>
<li>`join/split` -- для преобразования строки в массив и обратно.</li>
<li>`sort` -- для сортировки массива. Если не передать функцию сравнения -- сортирует элементы как строки.</li>
<li>`reverse` -- меняет порядок элементов на обратный.</li>
<li>`concat` -- объединяет массивы.</li>
<li>`indexOf/lastIndexOf` -- возвращают позицию элемента в массиве (не поддерживается в IE<9).</li>
</ul>
Изученных нами методов достаточно в 95% случаях, но существуют и другие. Для знакомства с ними рекомендуется заглянуть в справочник <a href="http://javascript.ru/Array">Array</a> и [Array в Mozilla Developer Network](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array).