Статистика использования. Отключена, пока вы её не разрешите.
Реклама и измерение рекламы. Отключены, пока вы их не разрешите.
О САЙТЕ
Все открытые вкладки сайта
JavaScript · Синтаксис · Продвинутый уровень
Итераторы и генераторы
10 задач
Создавайте собственные итерируемые объекты через `Symbol.iterator`, пишите функции-генераторы с `function*` и `yield`, используйте `yield*` для делегирования.
Краткое введение в тему и пояснения перед упражнениями (упражнения ниже):
Протокол итератора, ленивые вычисления и функции-генераторы
**Зачем существуют итераторы**
Массивы нетерпеливы — они хранят все значения в памяти сразу. Итераторы ленивы — они производят одно значение за раз, по требованию. Это важно для больших наборов данных, бесконечных последовательностей или дорогих вычислений где нужна лишь часть значений:
```js
// Массив: все 1 000 000 чисел сразу в памяти
const arr = Array.from({ length: 1_000_000 }, (_, i) => i);
// Генератор: одно число на вызов .next(), почти не использует память
function* range(n) {
for (let i = 0; i < n; i++) yield i;
}
for (const x of range(1_000_000)) { /* ... */ }
```
**Протокол итератора**
Объект является *iterable* если имеет метод `[Symbol.iterator]()` возвращающий *iterator*. Итератор — объект с методом `next()` возвращающим `{ value, done }`:
```js
const counter = {
[Symbol.iterator]() {
let n = 1;
return {
next() {
return n <= 3
? { value: n++, done: false }
: { value: undefined, done: true };
}
};
}
};
for (const x of counter) console.log(x); // 1 2 3
console.log([...counter]); // [1, 2, 3]
```
Встроенные iterables: `Array`, `String`, `Map`, `Set`, `arguments`, `NodeList`.
**Функции-генераторы — более простой способ**
Писать итераторы вручную повторяемо. Функции-генераторы (`function*`) позволяют записать ту же логику с гораздо меньшим кодом. Каждый `yield` приостанавливает выполнение и отправляет значение наружу:
```js
function* counter(start, end) {
for (let i = start; i <= end; i++) {
yield i; // пауза здесь, отправить i наружу
}
}
for (const n of counter(1, 3)) console.log(n); // 1 2 3
console.log([...counter(1, 5)]); // [1, 2, 3, 4, 5]
const g = counter(1, 2);
g.next(); // { value: 1, done: false }
g.next(); // { value: 2, done: false }
g.next(); // { value: undefined, done: true }
```
yield*, передача значений через .next(), async генераторы
Создайте объект `Counter`, реализующий протокол итератора вручную. Он должен считать от `start` до `end` (включительно). Добавьте метод `[Symbol.iterator]()`, возвращающий объект с методом `next()`. Используйте в цикле `for...of`.
function makeCounter(start, end) {
return {
[Symbol.iterator]() {
// верните объект итератора с next()
}
};
}
for (const n of makeCounter(1, 5)) {
console.log(n); // 1 2 3 4 5
}
Решение
function makeCounter(start, end) {
return {
[Symbol.iterator]() {
let current = start;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
}
for (const n of makeCounter(1, 5)) {
console.log(n); // 1 2 3 4 5
}
Напишите функцию-генератор `range(start, end, step = 1)`, выдающую числа от `start` до `end` (не включая), с шагом `step`. Используйте для вывода чисел 0, 2, 4, 6, 8.
function* range(start, end, step = 1) {
// выдавайте числа здесь
}
for (const n of range(0, 10, 2)) {
console.log(n); // 0 2 4 6 8
}
Решение
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
for (const n of range(0, 10, 2)) {
console.log(n); // 0 2 4 6 8
}
Напишите бесконечный генератор `fibonacci()`, выдающий последовательность Фибоначчи бесконечно (0, 1, 1, 2, 3, 5, 8, ...). Используйте его для вывода первых 8 чисел Фибоначчи через `.next()` или выхода из цикла `for...of` со счётчиком.
function* fibonacci() {
// бесконечная последовательность
}
const gen = fibonacci();
for (let i = 0; i < 8; i++) {
console.log(gen.next().value);
}
// 0 1 1 2 3 5 8 13
Решение
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const gen = fibonacci();
for (let i = 0; i < 8; i++) {
console.log(gen.next().value);
}
// 0 1 1 2 3 5 8 13
Напишите два генератора: `odds(n)` выдаёт нечётные числа 1, 3, 5, ..., до n; `evens(n)` выдаёт чётные числа 2, 4, 6, ..., до n. Напишите третий генератор `oddsAndEvens(n)`, использующий `yield*` сначала для делегирования `odds(n)`, затем `evens(n)`.
function* odds(n) {
for (let i = 1; i <= n; i += 2) yield i;
}
function* evens(n) {
for (let i = 2; i <= n; i += 2) yield i;
}
function* oddsAndEvens(n) {
// используйте yield* здесь
}
console.log([...oddsAndEvens(6)]);
// [1, 3, 5, 2, 4, 6]
Решение
function* odds(n) {
for (let i = 1; i <= n; i += 2) yield i;
}
function* evens(n) {
for (let i = 2; i <= n; i += 2) yield i;
}
function* oddsAndEvens(n) {
yield* odds(n);
yield* evens(n);
}
console.log([...oddsAndEvens(6)]);
// [1, 3, 5, 2, 4, 6]
Создайте класс `NumberRange` с `constructor(start, end)`. Добавьте `[Symbol.iterator]()` как метод-генератор, чтобы экземпляры работали с `for...of`, spread и деструктуризацией.
class NumberRange {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
// выдавайте значения здесь
}
}
const r = new NumberRange(1, 5);
console.log([...r]); // [1, 2, 3, 4, 5]
const [a, b] = r;
console.log(a, b); // 1 2
for (const n of r) console.log(n);
Решение
class NumberRange {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
const r = new NumberRange(1, 5);
console.log([...r]); // [1, 2, 3, 4, 5]
const [a, b] = r;
console.log(a, b); // 1 2
for (const n of r) console.log(n);
Постройте конвейер из трёх генераторов: `naturals()` — бесконечный: 1, 2, 3, ...; `take(gen, n)` — первые n значений из генератора; `squares(gen)` — отображает каждое значение в его квадрат. Выведите первые 5 квадратов через `take(squares(naturals()), 5)`.
function* naturals() {
let n = 1;
while (true) yield n++;
}
function* take(gen, n) {
// выдайте первые n значений из gen
}
function* squares(gen) {
// выдайте x*x для каждого x из gen
}
console.log([...take(squares(naturals()), 5)]);
// [1, 4, 9, 16, 25]
Решение
function* naturals() {
let n = 1;
while (true) yield n++;
}
function* take(gen, n) {
let count = 0;
for (const val of gen) {
if (count++ >= n) break;
yield val;
}
}
function* squares(gen) {
for (const x of gen) yield x * x;
}
console.log([...take(squares(naturals()), 5)]);
// [1, 4, 9, 16, 25]
Напишите генератор `countdown(n)`, выдающий n, n-1, ..., 1, а затем возвращающий `'Done!'`. Покажите, что значение return появляется в последнем вызове `.next()` как `{ value: 'Done!', done: true }`, но НЕ выдаётся `for...of`.
Создайте класс `LinkedList`, где каждый узел имеет `value` и `next`. Добавьте `[Symbol.iterator]()`, чтобы список можно было развернуть в массив и деструктурировать. Постройте список 1 → 2 → 3 и убедитесь, что `[...list]` даёт `[1, 2, 3]`.
class LinkedList {
constructor() { this.head = null; }
push(value) {
this.head = { value, next: this.head };
return this;
}
*[Symbol.iterator]() {
// обход от head
}
}
const list = new LinkedList();
list.push(3).push(2).push(1);
console.log([...list]); // [1, 2, 3]
const [first, second] = list;
console.log(first, second); // 1 2
Напишите асинхронный генератор `asyncRange(start, end, delay)`, выдающий числа от `start` до `end`, ожидая `delay` мс между каждым. Потребляйте его через `for await...of` внутри асинхронной функции `main()`.
async function* asyncRange(start, end, delay) {
for (let i = start; i <= end; i++) {
await new Promise(r => setTimeout(r, delay));
yield i;
}
}
async function main() {
// используйте for await...of здесь
}
main();
Решение
async function* asyncRange(start, end, delay) {
for (let i = start; i <= end; i++) {
await new Promise(r => setTimeout(r, delay));
yield i;
}
}
async function main() {
for await (const n of asyncRange(1, 4, 50)) {
console.log(n); // 1 2 3 4
}
}
main();
const deck = {
suits: ['♠', '♥', '♦', '♣'],
values: ['A', '2', '3'],
*[Symbol.iterator]() {
for (const value of this.values) {
for (const suit of this.suits) {
yield `${value}${suit}`;
}
}
}
};
console.log([...deck].length); // 12
console.log([...deck][0]); // 'A♠'
console.log([...deck][11]); // '3♣'
No split tab
Настройки cookies
Мы используем необходимые cookies для работы сайта. С вашего разрешения мы также можем сохранять настройки сайта и использовать аналитические и рекламные cookies, чтобы понимать использование сайта и поддерживать развитие проекта.
* Вы всегда можете изменить свой выбор в настройках сайта.
Выберите категории cookies
Настройки аналитики
Можно отключить аналитику использования платформы. Также можно отправить в Google Analytics запрос на удаление данных об использовании этого сайта, связанных с этим браузером.
Учебный workspace
Учитесь, читая, запуская код и решая задачи.
Практикуйте программирование с пояснениями тем, упражнениями, инструментами browser IDE, проверкой regex и тренировкой печати кода в одном workspace.
Открывайте инструменты во вкладках.Упражнения, IDE-инструменты и тренажеры остаются доступными как вкладки сайта.
Переключайтесь без потери контекста.Переходите между пояснениями, кодом и утилитами, сохраняя свое место.
Используйте sidebar как карту.Левые панели содержат навигацию, настройки, файлы, libraries и управление инструментами.
PythonJavaScriptSQLite
Одна IDE, три практичных режима
Python в браузере.Запускайте небольшие скрипты, пробуйте библиотеки и тренируйте API-запросы без установки.
JavaScript для быстрых экспериментов.Проверяйте код для браузера и сравнивайте идеи рядом с учебными материалами.
SQLite для практики с данными.Открывайте обозреватель базы данных, изучайте таблицы, пишите запросы и учитесь SQL локально.
ТемаIDE
Работайте рядом в split tabs
Держите инструкции перед глазами.Откройте упражнение или справочную страницу рядом с IDE, вместо постоянных переключений.
Сравнивайте инструменты во время обучения.Размещайте проверки regex, пояснения и эксперименты с кодом рядом, когда это нужно для задачи.
Закройте split, когда закончите.Workspace вернётся к одной сфокусированной вкладке, а открытые вкладки сайта останутся доступны.
Тренажер слепой печати кода
Или просто текста
Тренажер рассчитан на физическую клавиатуру.Откройте этот раздел на ноутбуке или компьютере с широким экраном. На телефоне тренировка слепой печати не будет корректной.
Скорость: 0 зн/мин
0 слов/мин
Лучшая скорость (60с): 0 зн/мин
0 слов/мин
Ошибки: 0
Общее время: 0.0 с
Для активации режима слепого набора не подсматривайте на физическую клавиатуру.