From 567995a7c7ad5fc459a5229dbe2edab35c7fc521 Mon Sep 17 00:00:00 2001 From: Nikolay Gagarinov Date: Fri, 29 May 2026 20:59:39 +0500 Subject: [PATCH] feat(50-loops): port full theory from Python and adapt to JS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Расширена теория уроков до объёма Python-оригинала: - 10-while: счётчик, пошаговый разбор, тело цикла {}, цикл в функции; - 23-aggregation-strings: диаграмма наращивания + нейтральный элемент; - 25-iteration-over-string: диаграмма перебора + переворот строки; - 26-conditions-inside-loops: пример с чётными числами, пошаговый разбор, «условие меняет действие, а не движение»; - 30-syntax-sugar: формы +=/-=/*=//=/%=/**=, сахар в циклах, строки; - 55-return-from-loops: пошаговый разбор is_prime, диаграмма, заметка про корень; - 70-for: пошаговый разбор переворота + пример charsCount. Числовой for уже покрыт уроком 70-for (отдельный урок не создавался). 20-aggregation-numbers (полнее), 28-build-strings и 50-mutators (JS-специфика) и все задания не тронуты. Урок 90-debug отложен. Co-Authored-By: Claude Opus 4.8 (1M context) --- modules/50-loops/10-while/ru/README.md | 101 +++++++++++++----- .../23-aggregation-strings/ru/README.md | 24 +++++ .../25-iteration-over-string/ru/README.md | 33 +++++- .../26-conditions-inside-loops/ru/README.md | 56 +++++++--- modules/50-loops/30-syntax-sugar/ru/README.md | 63 +++++++++-- .../55-return-from-loops/ru/README.md | 26 ++++- modules/50-loops/70-for/ru/README.md | 35 ++++++ 7 files changed, 284 insertions(+), 54 deletions(-) diff --git a/modules/50-loops/10-while/ru/README.md b/modules/50-loops/10-while/ru/README.md index 3e2f46c9..396cf4fd 100644 --- a/modules/50-loops/10-while/ru/README.md +++ b/modules/50-loops/10-while/ru/README.md @@ -1,46 +1,89 @@ -Циклы позволяют выполнять одно и то же действие многократно. +Помимо условных конструкций, в программировании невозможно обойтись без циклов. Это специальный механизм, позволяющий выполнять любое действие многократно. На его базе строятся практически любые вычисления — от подсчёта среднего балла в группе до обработки входящих запросов на сайтах. -## while +Цикл хранит повторяющееся действие в одном месте и запускает его снова, пока условие остаётся истинным. -Цикл `while` выполняет тело, пока условие истинно. +## Первый пример + +Пусть программа должна пять раз вывести строку `'Hello!'`. Чтобы остановить повторение в нужный момент, программе нужна переменная, которая хранит номер текущего шага. Такую переменную обычно называют счётчиком. + +В примере счётчик называется `counter`. Перед циклом он равен `0`. После каждого вывода строки мы увеличиваем его на единицу. ```javascript -let i = 0; -while (i < 3) { - console.log(i); - i += 1; +let counter = 0; +while (counter < 5) { + console.log('Hello!'); + counter += 1; } -// 0 -// 1 -// 2 + +// => Hello! +// => Hello! +// => Hello! +// => Hello! +// => Hello! ``` -## Счётчик и обратный отсчёт +После `while` в круглых скобках записывается условие, а тело цикла — в фигурных скобках. Пока `counter < 5`, выполняется тело цикла. После выполнения тела движок возвращается к условию и проверяет его заново. Когда условие становится ложным (`false`), программа выходит из цикла и продолжает выполнять код дальше. + +Без изменения счётчика условие никогда не станет ложным, и цикл превратится в **бесконечный**. Со стороны это выглядит так, как будто программа зависла. + +## Работа цикла по шагам + +Перед первым повтором `counter` равен `0`. + +**Шаг 1.** Проверяется `counter < 5`. Значение `0` меньше `5`, поэтому выполняется тело цикла. На экран выводится `Hello!`, а `counter` увеличивается до `1`. + +**Шаг 2.** Условие проверяется снова. Значение `1` всё ещё меньше `5`, тело цикла выполняется ещё раз. На экран снова выводится `Hello!`, а `counter` увеличивается до `2`. + +Так продолжается, пока `counter` не станет равен `5`. При следующей проверке условие `counter < 5` будет ложным, поэтому цикл завершится. + +```text +counter = 0 +┌──→ counter < 5? +│ true │ +│ ↓ +│ console.log('Hello!') +│ counter += 1 +└──────────┘ + false → выход из цикла +``` + +## Тело цикла и продолжение программы + +К телу цикла относится всё, что находится внутри фигурных скобок. Код после цикла выполняется один раз: ```javascript -function printCountdown(seconds) { - while (seconds > 0) { - console.log(seconds); - seconds -= 1; - } - console.log('Go!'); +let counter = 0; +while (counter < 2) { + console.log('Hello!'); + counter += 1; } -printCountdown(3); -// 3 -// 2 -// 1 -// Go! +console.log('End of loop'); ``` -## Бесконечный цикл +Здесь `console.log('Hello!')` и `counter += 1` находятся внутри цикла, а `console.log('End of loop')` стоит за его пределами, поэтому выполнится один раз после завершения цикла. + +## Цикл внутри функции -Если условие никогда не станет `false`, цикл будет работать вечно. Всегда убедитесь, что переменная изменяется внутри тела цикла. +Теперь перенесём цикл в функцию. Она напечатает числа от `1` до переданного значения: ```javascript -// Бесконечный цикл — никогда не останавливается! -// let i = 0; -// while (i < 3) { -// console.log(i); // i не изменяется -// } +const printNumbers = (n) => { + let i = 1; + while (i <= n) { + console.log(i); + i += 1; + } + console.log('Finished!'); +}; + +printNumbers(3); +// => 1 +// => 2 +// => 3 +// => Finished! ``` + +Цикл `while` печатает числа, пока `i` не станет больше `n`. После этого программа выходит из цикла и выполняет `console.log('Finished!')`. + +Условие и изменение счётчика зависят от задачи. Счётчик можно увеличивать на `1`, на `2` или сразу на `10`. Его можно уменьшать, если цикл идёт от большего значения к меньшему. Главное, чтобы условие когда-нибудь стало ложным, иначе цикл будет работать бесконечно. diff --git a/modules/50-loops/23-aggregation-strings/ru/README.md b/modules/50-loops/23-aggregation-strings/ru/README.md index 052f8b6a..6366e5e7 100644 --- a/modules/50-loops/23-aggregation-strings/ru/README.md +++ b/modules/50-loops/23-aggregation-strings/ru/README.md @@ -34,3 +34,27 @@ result = `${result}hexlet`; // hexlet result = `${result}hexlet`; // hexlethexlet result = `${result}hexlet`; // hexlethexlethexlet ``` + +Наглядно процесс наращивания строки выглядит так: + +```text +repeat('hexlet', 3): + +i=1: result = '' + 'hexlet' = 'hexlet' +i=2: result = 'hexlet' + 'hexlet' = 'hexlethexlet' +i=3: result = 'hexlethexlet' + 'hexlet' = 'hexlethexlethexlet' + └── результат +``` + +## Нейтральный элемент + +Чтобы наращивание работало, нужно стартовое значение. Для строк таким значением является **пустая строка** `''`. + +Она называется нейтральным элементом, потому что при конкатенации ничего не меняет: + +```javascript +console.log('' + 'abc'); // => abc +console.log('abc' + ''); // => abc +``` + +Поэтому именно пустая строка всегда используется как начальное значение при агрегации строк. diff --git a/modules/50-loops/25-iteration-over-string/ru/README.md b/modules/50-loops/25-iteration-over-string/ru/README.md index 6218bf1f..fc4736b5 100644 --- a/modules/50-loops/25-iteration-over-string/ru/README.md +++ b/modules/50-loops/25-iteration-over-string/ru/README.md @@ -21,4 +21,35 @@ printNameBySymbol(name); // => a ``` -Самое главное в этом коде, поставить правильное условие в `while`. Это можно сделать сразу двумя способами: `i < name.length` или `i <= name.length - 1`. Оба способа приводят к одному результату. +Самое главное в этом коде — поставить правильное условие в `while`. Это можно сделать сразу двумя способами: `i < name.length` или `i <= name.length - 1`. Оба способа приводят к одному результату. + +Цикл проходит по каждому символу строки по очереди: + +```text +'Arya' + │ │ │ │ + A r y a + ↓ ↓ ↓ ↓ +каждый символ обрабатывается по очереди +``` + +## Переворот строки + +Вместо печати можно собирать новую строку. Например, напишем функцию, которая переворачивает строку: + +```javascript +const reverseString = (text) => { + let result = ''; + let i = text.length - 1; + while (i >= 0) { + result = `${result}${text[i]}`; + i -= 1; + } + return result; +}; + +console.log(reverseString('Arya')); // => ayrA +console.log(reverseString('hexlet')); // => telxeh +``` + +Переменная `result` инициализируется пустой строкой как нейтральным элементом для конкатенации. Цикл начинается с последнего индекса (`text.length - 1`), двигается к нулю и завершается, когда индекс становится меньше нуля. На каждом шаге к результату добавляется текущий символ — и строка собирается в обратном порядке. diff --git a/modules/50-loops/26-conditions-inside-loops/ru/README.md b/modules/50-loops/26-conditions-inside-loops/ru/README.md index 985c3756..25f41e7a 100644 --- a/modules/50-loops/26-conditions-inside-loops/ru/README.md +++ b/modules/50-loops/26-conditions-inside-loops/ru/README.md @@ -1,18 +1,48 @@ +Тело цикла, как и тело функции, — это место выполнения инструкций. Значит, внутри него можно использовать всё изученное ранее, например условные конструкции. Так программа повторяет одно действие несколько раз, но на каждом повторе принимает решение. -Тело цикла, как и тело функции — это место выполнения инструкций. Значит, мы можем использовать внутри него всё изученное ранее, например — условные конструкции. +Пусть нужно пройти числа от `1` до `10` и напечатать только чётные. Цикл перебирает все числа подряд, а условие внутри решает, какие из них попадут на экран: -Представьте себе функцию, которая считает, сколько раз входит буква в предложение. Пример её работы: +```javascript +let number = 1; +while (number <= 10) { + if (number % 2 === 0) { + console.log(number); + } + number += 1; +} + +// => 2 +// => 4 +// => 6 +// => 8 +// => 10 +``` + +Счётчик увеличивается после проверки **в любом случае**. Это важно: если увеличивать `number` только внутри `if`, цикл остановится на первом нечётном числе и будет работать бесконечно. + +## Работа по шагам + +Перед первым повтором `number` равен `1`. + +**Шаг 1.** Условие цикла `number <= 10` истинно, программа входит в тело. Число `1` нечётное, блок `if` не выполняется. Затем `number` увеличивается до `2`. + +**Шаг 2.** Условие снова истинно. Число `2` чётное, поэтому печатается `2`. Затем `number` увеличивается до `3`. + +Дальше цикл проверяет каждое число: нечётные пропускает, чётные выводит. Когда `number` станет равен `11`, условие `number <= 10` станет ложным, и цикл завершится. + +## Условие меняет действие, а не движение + +В таких циклах удобно разделять две части: счётчик переводит программу к следующему значению, а `if` решает, что делать с текущим значением. + +Рассмотрим функцию, которая считает, сколько раз буква входит в предложение: ```javascript countChars('Fear cuts deeper than swords.', 'e'); // 4 -// Если вы ничего не нашли, то результат — 0 совпадений +// Если ничего не нашли, результат — 0 совпадений countChars('Sansa', 'y'); // 0 ``` -Перед тем как посмотреть её содержимое, попробуйте ответить на вопросы: - -* Является ли эта операция агрегацией? -* Какой будет проверка на вхождение символа? +Реализация: ```javascript const countChars = (str, char) => { @@ -21,18 +51,14 @@ const countChars = (str, char) => { while (i < str.length) { if (str[i] === char) { // Считаем только подходящие символы - count = count + 1; + count += 1; } - // Счетчик увеличивается в любом случае - i = i + 1; + // Счётчик увеличивается в любом случае + i += 1; } return count; }; ``` -Эта задача является агрегирующей. Несмотря на то, что она считает не все символы, для подсчета самой суммы все равно приходится анализировать каждый символ. - -Ключевое отличие этого цикла от рассмотренных в наличии условия внутри тела. Переменная `count` увеличивается только в том случае, когда текущий рассматриваемый символ совпадает с ожидаемым. - -В остальном — это типичная агрегатная функция, которая возвращает количество нужных символов вызываемому коду. +Эта задача является агрегирующей. Несмотря на то что она считает не все символы, для подсчёта суммы всё равно приходится анализировать каждый символ. Ключевое отличие от обычной агрегации — наличие условия внутри тела: переменная `count` увеличивается только тогда, когда текущий символ совпадает с ожидаемым. Условие внутри цикла может проверять что угодно — чётность числа, совпадение символа, длину строки. Главное, чтобы счётчик продолжал меняться и цикл мог завершиться. diff --git a/modules/50-loops/30-syntax-sugar/ru/README.md b/modules/50-loops/30-syntax-sugar/ru/README.md index 4017e6e3..465f02fc 100644 --- a/modules/50-loops/30-syntax-sugar/ru/README.md +++ b/modules/50-loops/30-syntax-sugar/ru/README.md @@ -1,10 +1,59 @@ +В программировании часто встречаются повторяющиеся конструкции. В JavaScript, как и во многих других языках, есть возможность сокращать их запись. Такие упрощения называют **синтаксическим сахаром** — они делают код короче и удобнее, сохраняя тот же результат. -Подобные конструкции `index = index + 1` в JavaScript используются довольно часто, поэтому создатели языка добавили сокращённый вариант записи: `index += 1`. Такие сокращения принято называть **синтаксическим сахаром**, потому что они делают процесс написания кода немного проще и приятнее, «подслащивая» его :) +## Сокращённые формы присваивания -Существуют сокращённые формы для всех арифметических операций и для конкатенации строк: +Часто требуется изменить значение переменной: что-то прибавить, вычесть, умножить или разделить. Базовый вариант выглядит так: -- `a = a + 1` → `a += 1` -- `a = a - 1` → `a -= 1` -- `a = a * 2` → `a *= 2` -- `a = a / 1` → `a /= 1` -- `a = a + 'foo'` → `a += 'foo'` +```javascript +index = index + 1; +count = count * 2; +total = total - 5; +price = price / 3; +``` + +JavaScript позволяет записать это короче, с помощью комбинированных операторов: + +```javascript +index += 1; // то же самое, что index = index + 1 +count *= 2; // то же самое, что count = count * 2 +total -= 5; // то же самое, что total = total - 5 +price /= 3; // то же самое, что price = price / 3 +``` + +## Сахар в циклах + +В циклах такие сокращения встречаются особенно часто: обычно мы меняем счётчик и накапливаем результат. + +```javascript +let sum = 0; +let index = 1; + +while (index <= 5) { + sum += index; // то же самое, что sum = sum + index + index += 1; // то же самое, что index = index + 1 +} + +console.log(sum); // => 15 +``` + +Без сокращений тело цикла было бы длиннее: + +```javascript +while (index <= 5) { + sum = sum + index; + index = index + 1; +} +``` + +## Другие операции + +Такая запись работает не только с числами. Для строк используется оператор конкатенации: + +```javascript +let text = 'Hello'; +text += ' World'; // то же самое, что text = text + ' World' +``` + +## Поддерживаемые сокращения + +Сокращённая форма существует почти для всех операторов: `+=`, `-=`, `*=`, `/=`, `%=`, `**=`. Все они работают по одному принципу: берут текущее значение переменной, применяют операцию и сохраняют результат в ту же переменную. diff --git a/modules/50-loops/55-return-from-loops/ru/README.md b/modules/50-loops/55-return-from-loops/ru/README.md index 1e72c81f..441f8e27 100644 --- a/modules/50-loops/55-return-from-loops/ru/README.md +++ b/modules/50-loops/55-return-from-loops/ru/README.md @@ -6,6 +6,15 @@ Рассмотрим простой алгоритм проверки простоты числа. Будем делить искомое число `x` на все числа из диапазона от двух до `x - 1` и смотреть остаток от деления. Если в этом диапазоне не найден делитель, который делит число `x` без остатка, значит перед нами простое число. +## Проверка простоты числа 5: пошаговый разбор + +1. Берём число `x = 5`. Возможные делители ищем в диапазоне от 2 до `x - 1`, то есть от 2 до 4. +2. Делим 5 на 2. Остаток равен 1 — делителя не нашли, продолжаем. +3. Делим 5 на 3. Остаток равен 2 — делителя не нашли, продолжаем. +4. Делим 5 на 4. Остаток равен 1 — делителя не нашли, завершаем перебор. + +Итог: в диапазоне 2…4 не нашлось ни одного числа, на которое 5 делится без остатка. Следовательно, 5 — простое число. + Если задуматься, то можно заметить, что достаточно проверять числа не до `x - 1`, а до половины числа. Например, 11 не делится на 2, 3, 4, 5. Но и дальше гарантированно не будет делиться на числа больше своей половины. Значит, можно провести небольшую оптимизацию и проверять деление только до `x / 2`. ```javascript @@ -33,6 +42,19 @@ isPrime(3); // true isPrime(4); // false ``` -Алгоритм построен таким образом, что если во время последовательного деления на числа до `x / 2` находится хоть одно, которое делит без остатка, то переданный аргумент — не простое число, а значит дальнейшие вычисления не имеют смысла. В этом месте стоит возврат `false`. +```text +while (...) { + if (условие) { + return значение; ← выход из функции (и из цикла) + } + ... +} +───────────────────────── +Без return цикл продолжается до конца +``` + +Алгоритм построен так: если во время последовательного деления на числа до `x / 2` находится хоть одно, которое делит без остатка, то переданный аргумент — не простое число, и дальнейшие вычисления не имеют смысла. В этом месте стоит возврат `false`. + +И только если цикл отработал целиком и не нашлось числа, которое делит без остатка, можно сделать вывод, что число простое. -И только если цикл отработал целиком, можно сделать вывод, что число — простое, так как не было найдено ни одного числа, которое делит число без остатка. +*Если быть до конца честными, для решения задачи хватит проверки чисел до квадратного корня из `number`. Но здесь нам важно сосредоточиться на понимании работы с условиями и возвратом внутри цикла.* diff --git a/modules/50-loops/70-for/ru/README.md b/modules/50-loops/70-for/ru/README.md index 6f0e61b2..055cfec9 100644 --- a/modules/50-loops/70-for/ru/README.md +++ b/modules/50-loops/70-for/ru/README.md @@ -16,6 +16,18 @@ const reverseString = (str) => { Можно читать так: *цикл с индексом `i` повторяется пока `i < str.length` и после каждого шага увеличивает `i` на 1*. +Разберём работу цикла по шагам для вызова `reverseString('go!')`: + +```text +до цикла: result = '' + +i=0: result = `${str[0]}${result}` = 'g' + '' = 'g' +i=1: result = `${str[1]}${result}` = 'o' + 'g' = 'og' +i=2: result = `${str[2]}${result}` = '!' + 'og' = '!og' +``` + +Каждый новый символ приписывается слева от накопленного результата, поэтому строка собирается в обратном порядке. + В определении цикла `for` в круглых скобках есть три выражения, разделённые точкой с запятой: 1. Начальное значение счётчика. Этот код выполняется ровно один раз перед первой итерацией. @@ -24,6 +36,29 @@ const reverseString = (str) => { В остальном принцип работы точно такой же, как у цикла `while`. +## Ещё пример: подсчёт символов + +Посчитаем, сколько раз символ встречается в строке без учёта регистра: + +```javascript +const charsCount = (text, char) => { + let result = 0; + for (let i = 0; i < text.length; i += 1) { + // приводим к нижнему регистру, чтобы не зависеть от регистра + if (text[i].toLowerCase() === char.toLowerCase()) { + result += 1; + } + } + return result; +}; + +charsCount('hexlet!', 'e'); // 2 +charsCount('hExlet!', 'E'); // 2 +charsCount('hexlet!', 'a'); // 0 +``` + +Здесь `for` управляет перебором символов, а условие внутри решает, увеличивать ли счётчик. + В определении `for` не обязательно указывать все три выражения. Если не указать условие повторения цикла, то цикл будет выполняться бесконечно: ```javascript