Към съдържанието
wannacode

Таблици с числа

Проблем

Три седмици, три масива

Кафенето записва колко кафета е продало всеки работен ден. За една седмица масивът от миналия урок е точно каквото трябва. За три седмици правиш три масива:

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} дни`);
Изход
45
3 седмици, 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]);
Отговор

Идеята

Сума по редове и по колони

Колко кафета са продадени всяка седмица? Това е сумата на всеки ред. Сумата започва от 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: 239

const 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}`);
}
Стъпка по стъпка, с думи
  1. Таблицата: 3 седмици (редове) по 5 дни (колони).
  2. week = 0: total започва отначало, от 0.
  3. Вътрешният цикъл минава през дните: 42 + 38 + 51 + 47 + 60 = 238.
  4. Отпечатва „Седмица 1: 238“.
  5. week = 1: total започва отначало, от 0.
  6. Вътрешният цикъл минава през дните: 45 + 40 + 49 + 52 + 63 = 249.
  7. Отпечатва „Седмица 2: 249“.
  8. week = 2: total започва отначало, от 0.
  9. Вътрешният цикъл минава през дните: 39 + 44 + 50 + 48 + 58 = 239.
  10. Отпечатва „Седмица 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}`);
}

Пробвай

Намери грешката

Залата има 3 реда по 5 места: 1 е заето, 0 е свободно. Свободните места са 7, а програмата казва 5. На кой ред е грешката?

Избери реда с грешката.

Идеята

Резервация: хората броят от 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}`);
}
Вход
3
2
Изход
Запазено: ред 3, място 2
Вход
1
2
Изход
Мястото е заето.

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 2
3 4
Изход
Главен диагонал: 5
Втори диагонал: 5
Първи ред: 3
Втори ред: 7

2. Обърни таблицата. Продажбите са записани по седмици (редове) и дни (колони). Счетоводството ги иска по дни и седмици. Направи нова таблица, в която редовете стават колони. Това се казва транспониране. Новата таблица е с размери колони × редове, а клетката [r][c] отива на [c][r].

Таблицата
42 38 51
45 40 49
Изход
42 45
38 40
51 49

Упражнение

Лабиринти и съкровище

3. Колко пътя има? Лабиринтът е таблица: 0 е проход, 1 е стена. Тръгваш от горния ляв ъгъл и стигаш до долния десен, като се движиш само надясно или надолу. Колко различни пътя има?

Лабиринтът
0 0 0 0
0 1 0 0
0 0 0 1
1 0 0 0
Изход
Пътища: 3
Подсказка

Направи втора таблица ways със същия размер. В нея пиши по колко начина се стига до всяка клетка. До началото се стига по 1 начин. До стена по 0. До всяка друга клетка се стига или отгоре, или отляво, затова броят е сборът от клетката над нея и клетката вляво от нея (ако ги има). Попълни таблицата ред по ред, отгоре надолу. Отговорът е в последната клетка.

4. Най-краткият път. Трудна задача. Сега може да се движиш и в четирите посоки: нагоре, надолу, наляво и надясно. С колко стъпки най-малко се стига от горния ляв до долния десен ъгъл? Ако няма път, отпечатай „Няма път“.

Лабиринтът
0 0 0 0 0
1 1 1 1 0
0 0 0 0 0
0 1 1 1 1
0 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 свободни места едно до друго, и отпечатай номера на реда и на първото място.

Тест

Провери се

1. Какво е sales[1] в таблицата с продажбите?
2. Таблицата sales има 3 седмици (редове) и 5 дни (колони). Какво дава sales[0].length?
3. Коя е последната клетка на таблица с 3 реда и 5 колони?
4. Искаш сумата на една колона (един ден за всички седмици). Кой индекс се мени във вътрешния цикъл?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·