Таблици с числа
Проблем
Три седмици, три масива
Кафенето записва колко кафета е продало всеки работен ден. За една седмица масивът от миналия урок е точно каквото трябва. За три седмици правиш три масива:
int[] week1 = { 42, 38, 51, 47, 60 };int[] week2 = { 45, 40, 49, 52, 63 };int[] week3 = { 39, 44, 50, 48, 58 };
int tuesday = week1[1] + week2[1] + week3[1];System.out.println("Вторник: " + tuesday);Вторник: 122Сега управителят пита колко е продадено във всички вторници за последната година. Това са 52 седмици, значи 52 масива и сбор с 52 члена. А цикъл не може да мине през week1, week2, week3: това са различни имена, не индекси.
Данните са таблица: седмиците са редове, дните са колони. Трябва ти масив, който е таблица.
Идеята
Двумерен масив: ред и колона
Масив, в който всяка стойност има ред и колона, се казва двумерен масив. Често го наричат и матрица.
int[][] sales = { { 42, 38, 51, 47, 60 }, { 45, 40, 49, 52, 63 }, { 39, 44, 50, 48, 58 }};System.out.println(sales[1][0]);System.out.println(sales.length + " седмици, " + sales[0].length + " дни");453 седмици, 5 дниint[][]значи таблица от цели числа. Всеки вътрешен{ … }е един ред.sales[1][0]е редът с индекс 1 (втората седмица) и колоната с индекс 0 (понеделник). Първо е редът, после колоната. Броенето и тук е от 0.sales.lengthе броят на редовете.sales[0].lengthе дължината на първия ред, тоест броят на колоните.
В Java двумерният масив всъщност е масив от масиви: sales[1] е цял ред, обикновен int[] с 5 числа. Затова пишеш две отделни скоби [1][0]: първо взимаш реда, после числото в него.
Празна таблица създаваш с размерите ѝ: new int[52][5] е 52 реда по 5 числа, всичките 0.
По-точно, за любопитните
Понеже редовете са отделни масиви, могат да са с различна дължина. Тогава оставяш втората скоба празна и създаваш всеки ред сам. Такъв масив се казва назъбен (jagged):
int[][] shifts = new int[3][];shifts[0] = new int[] { 8, 8 };shifts[1] = new int[] { 6, 6, 6 };shifts[2] = new int[] { 12 };System.out.println(shifts[1].length + " " + shifts[2].length);3 1При назъбен масив sales[0].length не е дължината на всеки ред. Затова във вложения цикъл е по-сигурно да пишеш sales[row].length: дължината на реда, през който минаваш в момента.
java.util.Arrays.deepToString(sales) връща цялата таблица като текст, например [[42, 38, …], [45, 40, …]]. Удобно е, когато търсиш грешка.
Виж го
Цялата таблица на екрана
За да минеш през всяка клетка, ти трябват два цикъла един в друг. Вложените цикли вече ги знаеш от звездичките: външният избира реда, вътрешният минава през колоните на този ред.
for (int week = 0; week < sales.length; week++) { for (int day = 0; day < sales[0].length; day++) { System.out.printf("%5d", sales[week][day]); } System.out.println();} 42 38 51 47 60 45 40 49 52 63 39 44 50 48 58Външният цикъл се завърта 3 пъти, по веднъж за всяка седмица. При всяко негово завъртане вътрешният се завърта 5 пъти. Общо 15 клетки, в реда, в който четеш таблица: отляво надясно, отгоре надолу.
%5d печата числото в поле от 5 знака, подравнено вдясно. Така колоните застават една под друга. След всеки ред println() минава на нов ред.
Пробвай
Коя клетка?
В дневника всеки ред е ученик, а всяка колона е предмет.
Какво ще отпечата програмата?
int[][] grades = { { 5, 3, 4, 6 }, { 3, 4, 6, 5 }, { 6, 2, 5, 4 }};System.out.println(grades[1][2]);Първият индекс е редът: 1 е вторият ученик, { 3, 4, 6, 5 }. Вторият индекс е колоната: 2 е третият предмет. Оценката е 6. Числото 2 излиза, ако объркаш реда с колоната, а 3, ако броиш от 1.
Идеята
Сума по редове и по колони
Колко кафета са продадени всяка седмица? Това е сумата на всеки ред. Сумата започва от 0 за всеки нов ред, затова total е вътре във външния цикъл:
for (int week = 0; week < sales.length; week++) { int total = 0; for (int day = 0; day < sales[0].length; day++) { total += sales[week][day]; } System.out.println("Седмица " + (week + 1) + ": " + total);}Седмица 1: 238Седмица 2: 249Седмица 3: 239int[][] sales = { { 42, 38, 51, 47, 60 }, { 45, 40, 49, 52, 63 }, { 39, 44, 50, 48, 58 }};for (int week = 0; week < sales.length; week++) { int total = 0; for (int day = 0; day < sales[0].length; day++) { total += sales[week][day]; } System.out.println("Седмица " + (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] не се местят: първо е винаги редът.
Пробвай
Сума по дни
Външният цикъл минава през дните, вътрешният през седмиците. Какво трябва да пише на празното място?
String[] days = { "Пн", "Вт", "Ср", "Чт", "Пт" };
for (int day = 0; day < sales[0].length; day++) {
int total = 0;
for (int week = 0; week < sales.length; week++) {
total += sales[][day];
}
System.out.println(days[day] + ": " + total);
}week. Редът е седмицата, затова на първо място е week. Денят стои на място, докато вътрешният цикъл минава през трите седмици. Във вторник излиза 38 + 40 + 44 = 122, същото като в първата карта, но без 52 имена.
Пробвай
Намери грешката
Залата има 3 реда по 5 места: 1 е заето, 0 е свободно. Свободните места са 7, а програмата казва 5. На кой ред е грешката?
Избери реда с грешката.
Вътрешният цикъл е за местата на реда, а броят им е hall[0].length (5). hall.length е броят на редовете (3), затова последните две места на всеки ред не се проверяват. Компилаторът не вижда такава грешка: и двете числа са int.
Идеята
Резервация: хората броят от 1
Зрителят иска ред 3, място 2. За него първият ред е ред 1. За масива той е ред 0. Затова от числата, които въвежда човек, вадиш 1, преди да ги ползваш като индекси:
Scanner in = new Scanner(System.in);int row = in.nextInt();int seat = in.nextInt();if (hall[row - 1][seat - 1] == 1) { System.out.println("Мястото е заето.");} else { hall[row - 1][seat - 1] = 1; System.out.println("Запазено: ред " + row + ", място " + seat);}32Запазено: ред 3, място 212Мястото е заето.hall е залата от предната карта. Клетката се променя като обикновена променлива: hall[row - 1][seat - 1] = 1;.
По-точно, за любопитните
- Ако някой поиска ред 7, индексът 6 е извън таблицата и програмата спира с
ArrayIndexOutOfBoundsException, също като при обикновен масив. Провери числата, преди да ги ползваш: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. Вълната расте с една стъпка на всяко завъртане. Спри, когато стигне целта или когато нищо не се е променило (тогава няма път). Четирите посоки са удобни като два масива: int[] dr = { -1, 1, 0, 0 }; и int[] dc = { 0, 0, -1, 1 };. Съседът в посока k е на ред r + dr[k] и колона c + dc[k].
Още задачи
- Намери съкровището. Игра на карта, която е таблица: 0 е път, 1 е препятствие, 2 е съкровището. Играчът започва в горния ляв ъгъл. На всеки ход отпечатай картата (
Pза играча,#за препятствие,.за път) и прочети команда като число: 8 нагоре, 2 надолу, 4 наляво, 6 надясно (както на цифровата клавиатура). Ако ходът води извън картата или в препятствие, играчът остава на място. Когато стъпи на съкровището, отпечатай „Намери съкровището с … хода!“. - Ред с най-много продажби. За таблицата
salesнамери коя седмица е била най-силна и кой ден от седмицата е най-силен средно. - Свободни места подред. За залата
hallнамери ред, в който има поне 3 свободни места едно до друго, и отпечатай номера на реда и на първото място.
Тест