Таблици с числа
Проблем
Три седмици, три масива
Кафенето записва колко кафета е продало всеки работен ден. За една седмица масивът от миналия урок е точно каквото трябва. За три седмици правиш три масива:
const week1 = [42, 38, 51, 47, 60];const week2 = [45, 40, 49, 52, 63];const week3 = [39, 44, 50, 48, 58];
const tuesday = week1[1] + week2[1] + week3[1];console.log(`Вторник: ${tuesday}`);Вторник: 122Сега управителят пита колко е продадено във всички вторници за последната година. Това са 52 седмици, значи 52 масива и сбор с 52 члена. А цикъл не може да мине през week1, week2, week3: това са различни имена, не индекси.
Данните са таблица: седмиците са редове, дните са колони. Трябва ти масив, който е таблица.
Идеята
Масив от масиви: ред и колона
Таблицата в JavaScript е масив от масиви: всеки елемент на външния масив е цял ред, а редът е обикновен масив с числа. Такъв масив се казва двумерен, а често и матрица.
const sales = [ [42, 38, 51, 47, 60], [45, 40, 49, 52, 63], [39, 44, 50, 48, 58],];console.log(sales[1][0]);console.log(`${sales.length} седмици, ${sales[0].length} дни`);453 седмици, 5 дни- Всеки вътрешен
[ … ]е един ред. Пишем ги един под друг, за да се вижда таблицата. sales[1][0]е редът с индекс 1 (втората седмица) и колоната с индекс 0 (понеделник). Първо е редът, после колоната. Броенето и тук е от 0.sales.lengthе броят на редовете.sales[0].lengthе дължината на първия ред, тоест броят на колоните.
sales[1] е цял ред, обикновен масив с 5 числа. Затова пишеш две отделни скоби [1][0]: първо взимаш реда, после числото в него.
const sales = [ [42, 38, 51, 47, 60], [45, 40, 49, 52, 63], [39, 44, 50, 48, 58],];console.log(sales[1]);console.log(sales);[ 45, 40, 49, 52, 63 ][ [ 42, 38, 51, 47, 60 ], [ 45, 40, 49, 52, 63 ], [ 39, 44, 50, 48, 58 ]]Празна таблица 52 × 5, пълна с нули, правиш ред по ред: 52 пъти добавяш нов ред от 5 нули.
const table = [];for (let week = 0; week < 52; week++) { table.push(new Array(5).fill(0));}table[0][1] = 7;console.log(table.length, table[0].length, table[0][1], table[1][1]);52 5 7 0По-точно, за любопитните
- Понеже редовете са отделни масиви, могат да са с различна дължина. Такава таблица се казва назъбена (jagged):
const shifts = [ [8, 8], [6, 6, 6], [12],];console.log(shifts[1].length, shifts[2].length);3 1При нея sales[0].length не е дължината на всеки ред. Затова във вложения цикъл е по-сигурно да пишеш sales[row].length: дължината на реда, през който минаваш в момента.
- Не прави празната таблица с
new Array(3).fill(new Array(5).fill(0)). Така и трите места сочат един и същ ред, както двете имена на един масив от миналия урок. Промяна в единия ред се вижда във всички:
const table = new Array(3).fill(new Array(5).fill(0));table[0][1] = 7;console.log(table);[ [ 0, 7, 0, 0, 0 ], [ 0, 7, 0, 0, 0 ], [ 0, 7, 0, 0, 0 ] ]Виж го
Цялата таблица на екрана
За да минеш през всяка клетка, ти трябват два цикъла един в друг. Вложените цикли вече ги знаеш от звездичките: външният избира реда, вътрешният минава през колоните на този ред.
for (let week = 0; week < sales.length; week++) { let line = ""; for (let day = 0; day < sales[0].length; day++) { line += String(sales[week][day]).padStart(5); } console.log(line);} 42 38 51 47 60 45 40 49 52 63 39 44 50 48 58Външният цикъл се завърта 3 пъти, по веднъж за всяка седмица. При всяко негово завъртане вътрешният се завърта 5 пъти. Общо 15 клетки, в реда, в който четеш таблица: отляво надясно, отгоре надолу.
String(x).padStart(5) прави числото текст и го допълва с интервали отляво до 5 знака. Така колоните застават една под друга. Редът се събира в line и console.log(line) го печата след вътрешния цикъл.
Пробвай
Коя клетка?
В дневника всеки ред е ученик, а всяка колона е предмет.
Какво ще отпечата програмата?
const grades = [ [5, 3, 4, 6], [3, 4, 6, 5], [6, 2, 5, 4],];console.log(grades[1][2]);Първият индекс е редът: 1 е вторият ученик, [3, 4, 6, 5]. Вторият индекс е колоната: 2 е третият предмет. Оценката е 6. Числото 2 излиза, ако объркаш реда с колоната, а 3, ако броиш от 1.
Идеята
Сума по редове и по колони
Колко кафета са продадени всяка седмица? Това е сумата на всеки ред. Сумата започва от 0 за всеки нов ред, затова total е вътре във външния цикъл:
for (let week = 0; week < sales.length; week++) { let total = 0; for (let day = 0; day < sales[0].length; day++) { total += sales[week][day]; } console.log(`Седмица ${week + 1}: ${total}`);}Седмица 1: 238Седмица 2: 249Седмица 3: 239const sales = [ [42, 38, 51, 47, 60], [45, 40, 49, 52, 63], [39, 44, 50, 48, 58],];for (let week = 0; week < sales.length; week++) { let total = 0; for (let day = 0; day < sales[0].length; day++) { total += sales[week][day]; } console.log(`Седмица ${week + 1}: ${total}`);}Стъпка по стъпка, с думи
- Таблицата: 3 седмици (редове) по 5 дни (колони).
- week = 0: total започва отначало, от 0.
- Вътрешният цикъл минава през дните: 42 + 38 + 51 + 47 + 60 = 238.
- Отпечатва „Седмица 1: 238“.
- week = 1: total започва отначало, от 0.
- Вътрешният цикъл минава през дните: 45 + 40 + 49 + 52 + 63 = 249.
- Отпечатва „Седмица 2: 249“.
- week = 2: total започва отначало, от 0.
- Вътрешният цикъл минава през дните: 39 + 44 + 50 + 48 + 58 = 239.
- Отпечатва „Седмица 3: 239“.
Колко е продадено в понеделник, във вторник и т.н.? Това е сумата на всяка колона. Таблицата е същата, сменя се само редът на циклите: отвън е денят, отвътре са седмиците. Ще го допишеш в следващата карта.
Кой цикъл е отвън, решава какво сумираш. Индексите в sales[week][day] не се местят: първо е винаги редът.
Пробвай
Сума по дни
Външният цикъл минава през дните, вътрешният през седмиците. Какво трябва да пише на празното място?
const days = ["Пн", "Вт", "Ср", "Чт", "Пт"];
for (let day = 0; day < sales[0].length; day++) {
let total = 0;
for (let week = 0; week < sales.length; week++) {
total += sales[][day];
}
console.log(`${days[day]}: ${total}`);
}week. Редът е седмицата, затова на първо място е week. Денят стои на място, докато вътрешният цикъл минава през трите седмици. Във вторник излиза 38 + 40 + 44 = 122, същото като в първата карта, но без 52 имена.
Пробвай
Намери грешката
Залата има 3 реда по 5 места: 1 е заето, 0 е свободно. Свободните места са 7, а програмата казва 5. На кой ред е грешката?
Избери реда с грешката.
Вътрешният цикъл е за местата на реда, а броят им е hall[0].length (5). hall.length е броят на редовете (3), затова последните две места на всеки ред не се проверяват. JavaScript не вижда такава грешка: и двете са числа, а програмата върви.
Идеята
Резервация: хората броят от 1
Зрителят иска ред 3, място 2. За него първият ред е ред 1. За масива той е ред 0. Затова от числата, които въвежда човек, вадиш 1, преди да ги ползваш като индекси:
const row = Number(await ask(""));const seat = Number(await ask(""));if (hall[row - 1][seat - 1] === 1) { console.log("Мястото е заето.");} else { hall[row - 1][seat - 1] = 1; console.log(`Запазено: ред ${row}, място ${seat}`);}32Запазено: ред 3, място 212Мястото е заето.hall е залата от предната карта, а редовете за ask() и rl.close() са както в урока „Вход и изход“. Клетката се променя като обикновена променлива: hall[row - 1][seat - 1] = 1;.
По-точно, за любопитните
- Ако някой поиска ред 7,
hall[6]еundefined, аundefined[1]спира програмата сTypeError. Ако поиска място 10 на съществуващ ред, грешка няма:hall[2][9]еundefined, а записът добавя ново място в края на реда, и редът става с 10 места:
const hall = [ [1, 1, 0, 0, 1], [0, 1, 1, 1, 0], [0, 0, 0, 1, 1],];try { console.log(hall[7 - 1][2 - 1]);} catch (error) { console.log(`${error.name}: ${error.message}`);}hall[3 - 1][10 - 1] = 1;console.log(hall[2].length);TypeError: Cannot read properties of undefined (reading '1')10Затова провери числата, преди да ги ползваш: row >= 1 && row <= hall.length, и същото за мястото.
- Тук всяко число е на отделен ред. Как да прочетеш цял ред числа наведнъж (
0 1 1 0), ще видиш в следващия урок за текста.
Упражнение
Сметки по таблица
Реши задачите в редактора си. Таблиците напиши направо в кода.
1. Диагонали и редове. За таблицата 2 × 2 по-долу намери сумата по главния диагонал (отгоре вляво надолу вдясно), по другия диагонал и сумите на първия и на втория ред. На главния диагонал индексът на реда и на колоната са равни. На другия диагонал колоната е n - 1 - ред.
1 23 4Главен диагонал: 5Втори диагонал: 5Първи ред: 3Втори ред: 72. Обърни таблицата. Продажбите са записани по седмици (редове) и дни (колони). Счетоводството ги иска по дни и седмици. Направи нова таблица, в която редовете стават колони. Това се казва транспониране. Новата таблица е с размери колони × редове, а клетката [r][c] отива на [c][r].
42 38 51 45 40 49 42 45 38 40 51 49Упражнение
Лабиринти и съкровище
3. Колко пътя има? Лабиринтът е таблица: 0 е проход, 1 е стена. Тръгваш от горния ляв ъгъл и стигаш до долния десен, като се движиш само надясно или надолу. Колко различни пътя има?
0 0 0 00 1 0 00 0 0 11 0 0 0Пътища: 3Подсказка
Направи втора таблица ways със същия размер. В нея пиши по колко начина се стига до всяка клетка. До началото се стига по 1 начин. До стена по 0. До всяка друга клетка се стига или отгоре, или отляво, затова броят е сборът от клетката над нея и клетката вляво от нея (ако ги има). Попълни таблицата ред по ред, отгоре надолу. Отговорът е в последната клетка.
4. Най-краткият път. Трудна задача. Сега може да се движиш и в четирите посоки: нагоре, надолу, наляво и надясно. С колко стъпки най-малко се стига от горния ляв до долния десен ъгъл? Ако няма път, отпечатай „Няма път“.
0 0 0 0 01 1 1 1 00 0 0 0 00 1 1 1 10 0 0 0 0Най-кратък път: 16 стъпкиПодсказка
Пусни „вълна“ от началото. Направи таблица dist, пълна с -1 (още не сме стигнали дотам), и сложи 0 в началото. После повтаряй: намери всички клетки с dist равно на текущата стъпка и на свободните им съседи, до които още не сме стигнали, запиши стъпка + 1. Вълната расте с една стъпка на всяко завъртане. Спри, когато стигне целта или когато нищо не се е променило (тогава няма път). Четирите посоки са удобни като два масива: const dr = [-1, 1, 0, 0]; и const dc = [0, 0, -1, 1];. Съседът в посока k е на ред r + dr[k] и колона c + dc[k].
Още задачи
- Намери съкровището. Игра на карта, която е таблица: 0 е път, 1 е препятствие, 2 е съкровището. Играчът започва в горния ляв ъгъл. На всеки ход отпечатай картата (
Pза играча,#за препятствие,.за път) и прочети команда като число: 8 нагоре, 2 надолу, 4 наляво, 6 надясно (както на цифровата клавиатура). Ако ходът води извън картата или в препятствие, играчът остава на място. Когато стъпи на съкровището, отпечатай „Намери съкровището с … хода!“. - Ред с най-много продажби. За таблицата
salesнамери коя седмица е била най-силна и кой ден от седмицата е най-силен средно. - Свободни места подред. За залата
hallнамери ред, в който има поне 3 свободни места едно до друго, и отпечатай номера на реда и на първото място.
Тест