JavaScript / 39 минут
Циклы и перебор данных
Стройте завершающиеся циклы и выбирайте правильный способ перебора.
- Стройте завершающиеся циклы и выбирайте правильный способ перебора.
- Применить тему в задаче «Циклы и перебор данных» и объяснить граничные случаи.
Около 9 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.
Повторение с понятным условием остановки
Цикл описывает повторяющееся действие: сложить числа, обработать строки заказа, найти первый подходящий элемент. До выбора синтаксиса определите три вещи: начальное состояние, условие продолжения и изменение состояния. Если ни одна операция не приближает цикл к остановке, программа может зависнуть. Особенно внимательно смотрите на ветви с continue: они тоже должны сохранять возможность завершения.
Для суммирования от 1 до n удобно хранить индекс текущего числа и аккумулятор суммы. До первой итерации индекс равен 1, сумма равна 0. Пока индекс не превышает n, число добавляется к сумме, после чего индекс растёт. При n=0 тело не выполнится ни разу. Правильный начальный аккумулятор позволяет обработать пустой диапазон без отдельного случайного исключения.
let total = 0;
for (let value = 1; value <= 4; value += 1) {
total += value;
}
console.log(total); // 10for: инициализация, проверка, шаг
Заголовок for содержит три части. Инициализация выполняется один раз, условие — перед каждой итерацией, шаг — после тела. В примере value += 1 сработает после добавления текущего числа. Это объясняет, почему границы нужно проверять отдельно: < и <= задают разные наборы чисел. Ошибка на единицу часто появляется на последнем элементе и остаётся незаметной на случайных больших входах.
Блочная переменная let существует в области цикла. Для обычного счётчика выбирайте имя, которое соответствует смыслу: index для позиции, day для номера дня, attempt для попытки. Короткое i нормально в небольшом локальном цикле, но несколько вложенных i, j и k без объяснений быстро мешают понять, какая координата изменяется. При вложенных циклах отдельно оцените количество итераций, а не только число строк.
while и do...while
while проверяет условие до выполнения тела. Он подходит, когда число повторений определяется состоянием: пока есть следующая порция данных или пока не найден ответ. do...while проверяет условие после тела и потому выполняется минимум один раз. Эта разница существенна для пустых данных: иногда попытка прочитать первый элемент до проверки уже является ошибкой.
let remaining = 3;
while (remaining > 0) {
console.log(remaining);
remaining -= 1;
}
let attempts = 0;
do {
attempts += 1;
} while (attempts < 1);Не используйте бесконечный while как способ ожидать сетевой ответ или событие браузера. Синхронное ожидание занимает поток и мешает обработке событий. Асинхронные операции строят через Promise, callbacks и соответствующие API среды. Пустое тело цикла не делает ожидание бесплатным и не позволяет интерфейсу продолжать работать.
for...of и for...in
for...of перебирает значения итерируемого объекта: элементы массива, code points строки, записи Map. Это хороший выбор, когда индекс не нужен. Если нужны и индекс, и значение массива, можно использовать entries(). for...in перебирает строковые имена перечислимых свойств, включая унаследованные перечислимые свойства. Он не является обычной заменой обхода массива.
const names = ['Аня', 'Олег'];
for (const name of names) console.log(name);
for (const [index, name] of names.entries()) {
console.log(index, name);
}
for (const key of Object.keys({ js: 1, ts: 2 })) {
console.log(key);
}Выбор обхода должен отражать контракт данных. Если работаете с объектом-словарём, Object.keys, Object.values и Object.entries делают набор собственных данных явным. Если обрабатываете массив, обычно нужны его элементы, а не имена всех возможных свойств. Изменение массива в процессе обхода требует отдельного рассуждения: длина, порядок и ещё не посещённые элементы могут поменяться.
break, continue и ранний выход
break завершает текущий цикл, continue пропускает остаток текущей итерации, return завершает всю функцию. Эти команды не взаимозаменяемы. При поиске первого подходящего значения ранний return часто делает код понятнее: нашли результат — сразу передали его вызывающему коду. Если ни один элемент не подошёл, после цикла нужно явно вернуть согласованный результат отсутствия.
function firstPositive(values) {
for (const value of values) {
if (value <= 0) continue;
return value;
}
return null;
}У вложенных циклов break по умолчанию относится только к ближайшему циклу. Не решайте сложность управления неочевидными переходами, если можно выделить поиск в отдельную функцию. Понятный локальный контракт обычно полезнее, чем попытка сохранить всё вычисление в одном большом блоке.
Практика и доказательство корректности
В задаче требуется сумма от 1 до n. Проверьте нулевой диапазон, один элемент и несколько элементов, для которых легко посчитать ответ вручную. Полезно записать таблицу: индекс до шага, сумма до шага, сумма после шага. Для n=3 она показывает последовательность 0→1→3→6 и помогает найти момент неправильного обновления.
Объясните инвариант: перед добавлением очередного числа аккумулятор содержит сумму уже пройденных чисел. Инициализация делает утверждение верным до первого шага, тело сохраняет его, а остановка означает, что все нужные числа учтены. Это простая форма рассуждения о программе, которую затем можно перенести на фильтрацию заказов, обход дерева и более сложные алгоритмы.