Тема, шрифт, мова та інші налаштування інтерфейсу.
Статистика використання. Вимкнена, доки ви її не дозволите.
Реклама та вимірювання реклами. Вимкнені, доки ви їх не дозволите.
ПРО САЙТ
Усі відкриті вкладки сайту
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]`, що видає всі комбінації карт (`'A♠'`, `'A♥'`, ..., `'3♣'`). Перевірте через `[...deck]`.
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 с
Щоб тренувати сліпий друк, не підглядайте на фізичну клавіатуру.