Dotychczas omawialiśmy tablice jednowymiarowe, przyszedł jednak czas na wizytę w wyższych wymiarach ;-)
Ta strona jest częścią materiałów do kursu “Programowanie w Javie z elementami bioinformatyki dla poczatkujących”. Pozostałe materiały znajdziesz tutaj
Tablice dwuwymiarowe
O ile tablicę jednowymiarową możemy sobie wyobrazić jako półkę z ponumerowanymi przegródkami, to tablicę dwuwymiarową można przedstawić jako regał z ponumerowanymi półkami na których znajdują się ponumerowane przegródki. Jeśli chcielibyśmy określić położenie, dajmy na to, książki znajdującej się w konkretnej przegródce należałoby podać dwie liczby: numer półki i numer przegródki. Dwie liczby określają dwa wymiary.
Tworzenie tablic
Tablicę dwuwymiarową można zadeklarować na przykład w ten sposób:
int[][] tablica2D
a następnie zainicjalizować:
tablica2D = new int[10][10]
Oczywiście obie operacje można połączyć:
int[][] tablica2D = new int[10][10]
W ten sposób otrzymujemy tablicę, która ma formę „prostokątną” – każdy rząd ma tyle samo kolumn.
Liczba rzędów i kolumn nie musi być taka sama:
int [][] tablica= new int[6][10]
Jak widać w tym przypadku stworzyliśmy prostokątną tablicę, która ma 6 rzędów i 10 kolumn. Oczywiście to co jest rzędem a co kolumną jest sprawą umowną, tak naprawdę w pamięci komputera nie ma żadnych rzędów i kolumn (ani tym bardziej półek i przegródek). Niemniej kolejność liczb podanych w nawiasach ma znaczenie, ponieważ określa liczbę możliwych do zapamiętania elementów w każdym z wymiarów. Tablica dwuwymiarowa jest tak naprawdę tablicą, która przechowuje tablice jednowymiarowe jako elementy. Liczba podana jako pierwsza przy tworzeniu tablicy określa liczbę tablic jednowymiarowych, które mogą być przechowywane.
Wprowadzanie i pobieranie elementów w tablicach
Praca z tablicami dwuwymiarowymi w zasadzie przebiega podobnie jak z tablicami jednowymiarowymi, z tym oczywistym zastrzeżeniem, że odwołując się do konkretnej komórki tablicy należy podać dwie liczby, zamiast jednej. Na przykład, jeśli chcemy wprowadzić wartość do komórki znajdującej się w pierwszym rzędzie i drugiej kolumnie, można to zrobić tak:
tablica[0][1] = 4;
Czyli pierwsza liczba będzie oznaczała rząd a druga kolumnę.
Podobnie jak w przypadku tablic wymiarowych można od razu tablicę wymiarową wypełnić wartościami:
int[][] tablica2 = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
Choć zdecydowanie bardziej czytelna będzie taka forma:
int[][] tablica2 = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};
Teraz od razu widać, które dane mieszczą się w kolejnych rzędach, na kolejnych pozycjach.
Sposób pobierania poszczególnych danych, znów nie powinien być zaskoczeniem:
System.out.println("tablica2[1][3]: "+tablica2[1][3]);
Powyższy kod wyświetli zawartość czwartej komórki w drugim rzędzie.
Tablice nieprostokątne
Wyobrażanie sobie tablicy jako tabeli z rzędami i równą liczbą kolumn może być nieco zwodnicze w niektórych przypadkach, ponieważ liczba komórek w każdym z rzędów wcale nie musi być taka sama. Spróbuj wykonać taki kod:
int[][] tablica3 = {{1,2,3,4},
{5,6,7},
{9,10,11,12}};
System.out.println("tablica3[1][3]: "+tablica3[1][3]);
Tym razem otrzymujemy komunikat:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
…
który jasno (tak, choć na początku to nie musi być oczywiste, ale przeczytaj go uważnie) informuje nas, że próbowaliśmy odwołać się do nieistniejącego elementu tablicy, inaczej przekroczyliśmy zakres indeksów komórek tablicy. W drugim rzędzie znajduje się tablica o tylko trzech elementach, próba odwołania się do komórki czwartej kończy więc niepowodzeniem.
Powyższy przypadek ukazuje naturę tablic dwuwymiarowych, a więc to, że jest to zbiór tablic jednowymiarowych, które znajdują się w strukturze wyższego rzędu.
Taka budowa tablic pozwala na stopniowe tworzenie tablic, to znaczy na definiowanie najpierw wyższego wymiaru a później w miarę potrzeb, dopiero niższego. Czyli najpierw można określić, ze tablica będzie przechowywała konkretną liczbę tablic jednowymiarowych a dopiero później określać jakiej długości to będą tablice. Na przykład może to wyglądać tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// tablica4 będzie przechowywać 4 tablice jednowymiarowe, // na razie nie jest określone jakiej będą długości int[][] tablica4 = new int[4][]; // tablica4 w pierwszej komórce będzie przechowywać 5-elementową tablicę tablica4[0] = new int[5]; // tablica 4 będzie przechowywać komórce pod indeksem 3 // tablicę o 3 elementach tablica4[3] = new int[3]; // umieszczenie elementu tablica4[3][2] = 2; // pobranie elementu System.out.println("tablica4[3][2]: "+tablica4[3][2]); |
Pamiętaj jednak, że trzeba zachować odpowiednią kolejność w inicjalizacji tablic wielowymiarowych, od lewej do prawej. Nie można napisać na przykład tak:
int[][] tablica = new int[][3];
Tablice dwuwymiarowe i pętle
Pętle współdziałają z tablicami dwuwymiarowymi podobnie jak z jednowymiarowymi, oczywiście biorąc pod uwagę ich dwuwymiarowy charakter i strukturę (tablica jednowymiarowych tablic).
Na początek stwórzmy zmodyfikowaną wersję zadania z pierwszej części dotyczącej tablic. Wtedy tworzyliśmy jednowymiarową tablicę o 100 elementach, która najpierw była wypełniana liczbami od 0 do 10 a później jej zawartość była drukowana po 10 liczb w rzędzie, przy czym liczby jednocyfrowe miały dodawane z przodu 0 (w ten sposób wszystkie liczby miały postać dwucyfrową). Przedstawienie 100 liczb w 10 rzędach po 10 liczb odpowiada 100-elementowej tablicy „kwadratowej” o 10 kolumnach i 10 rzędach. Aby wypełnij ją liczbami a później uzyskać jej zawartość, będziemy posługiwać się dwoma zagnieżdżonymi pętlami.
Zacznijmy od stworzenia tablicy i wypełnienia jej liczbami.
1 2 3 4 5 6 7 8 9 10 11 |
// Tworzenie tablicy int[][] tablica2D = new int[10][10]; // pętla zewnętrzna generuje indeksy rzędów for (int i = 0; i < 10; i++) { // pętla wewnętrzna generuje indeksy kolumn for (int j = 0; j < 10; j++) { // możemy tak wyliczyć kolejną liczbę, ponieważ // każdy rząd odpowiada kolejnej dziesiątce tablica2D[i][j]= i*10 + j; } } |
Jak wynika z komentarzy przy kodzie, pętla zewnętrzna odpowiedzialna jest za generowanie kolejnych indeksów odpowiadających rzędom, można więc powiedzieć, że „pracuje na rzędach”. Natomiast pętla wewnętrzna jest odpowiedzialna za kolumny, pozwala więc dotrzeć do kolejnych komórek. Mówiąc bardziej ściśle, pętla zewnętrzna wskazuje na kolejne tablice jednowymiarowe a pętla wewnętrzna na elementy w obrębie tablic jednowymiarowych.
Przy drukowaniu zawartości tablicy moglibyśmy wykorzystać identyczne pętle jak wyżej. Jednak tym razem zastosujemy pętle foreach
co pozwoli przy okazji znów zilustrować naturę tablicy dwuwymiarowej:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// ponieważ tablica dwuwymiarowa jest zbiorem tablic jednowymiarowych // pętla foreach pobiera tablice jednowymiarowe for (int[] tablica1D : tablica2D) { // dopiero pętla wewnętrzna pobiera liczby for (int liczba : tablica1D) { // dla liczb < 10 dodajemy 0 przed cyfrą if (liczba < 10) System.out.print("0"+liczba + ","); else System.out.print(liczba + ","); } // nowa linia dla każdej 10-tki liczb System.out.println(""); } |
Jak, mam nadzieję, pamiętasz z poprzednich lekcji, pętla foreach
pobiera po kolei elementy z tablicy i przekazuje je do zmiennej. Zauważ, że pętla zewnętrzna w powyższym przykładzie pobiera tablice jednowymiarowe. Dopiero pętla wewnętrzna uzyskuje dostęp do komórek tworzących tablice jednowymiarowe.
Trzeci wymiar, czwarty wymiar…
Jeśli tablicę dwuwymiarową wyobrażaliśmy sobie jako regał z półkami to tablice trójwymiarową można przedstawić jako rząd ponumerowanych regałów z półkami, tablice czterowymiarową jako wiele ponumerowanych rzędów regałów z półkami itd… Jak widać każdy dodatkowy wymiar tablicy można rozumieć jako kolekcję tablic niższego wymiaru.
Deklaracja i tworzenie tablicy przebiega podobnie jak w przypadku tablic, które już poznaliśmy, z tym, że każdy dodatkowy wymiar jest oznaczany dodatkową parą nawiasów kwadratowych. Tak na przykład może wyglądać program wypełniający kolejnymi liczbami tablicę trójwymiarową:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int[][][] tablica3D = new int[3][4][5]; // "licznik" int l = 0; // trzeci wymiar for (int i = 0; i < 3; i++) { // drugi wymiar for (int j = 0; j < 4; j++) { // pierwszy wymiar for (int k = 0; k < 5; k++) { tablica3D[i][j][k] = l; l++; } } } |
Pętle odpowiedzialne za wypełnianie tablicy można napisać w inny, bardzie odporny na błędy sposób wykorzystując możliwość bezpośredniego pobrania informacji o długości tablicy, poznaną w pierwszej części poświęconej tablicom (tablica.length
).
1 2 3 4 5 6 7 8 9 10 11 |
// trzeci wymiar for (int i = 0; i < tablica3D.length; i++) { // drugi wymiar for (int j = 0; j < tablica3D[i].length; j++) { // pierwszy wymiar for (int k = 0; k < tablica3D[i][j].length; k++) { tablica3D[i][j][k] = l; l++; } } } |
Ten sposób wykorzystuje hierarchiczną budowę tablic wielowymiarowych i ma jeszcze tą zaletę, że umożliwia obsługę tablic wielowymiarowych, nawet gdy poszczególne tablice składowe na danym poziomie różnią się wielkością.
Skoro tablica została wypełniona, czas wyświetlić jej zawartość Wykorzystamy do tego celu pętle foreach
. Poszczególne tablice dwuwymiarowe wyświetlimy oddzielnie, przedzielając liniami z gwiazdkami, co pozwoli je oddzielić wizualnie od siebie.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// pobieramy tablice 2D z tablicy 3D for (int[][] tablica2D : tablica3D) { // pobieramy tablicę 1D z tablicy 2D for (int[] tablica1D : tablica2D) { // pobieramy liczby z tablicy 1D for (int liczba : tablica1D) { // dla liczb < 10 dodajemy 0 przed cyfrą if (liczba < 10) { System.out.print("0" + liczba + ", "); } else { System.out.print(liczba + ", "); } } System.out.println(""); } // pomiędzy zawartoscią tablic dwuwymiarowych wyświetlają się gwiazdki System.out.println("*******************"); } |
Wynik powinien wyglądać tak:
00, 01, 02, 03, 04,
05, 06, 07, 08, 09,
10, 11, 12, 13, 14,
15, 16, 17, 18, 19,
*******************
20, 21, 22, 23, 24,
25, 26, 27, 28, 29,
30, 31, 32, 33, 34,
35, 36, 37, 38, 39,
*******************
40, 41, 42, 43, 44,
45, 46, 47, 48, 49,
50, 51, 52, 53, 54,
55, 56, 57, 58, 59,
*******************
Zadanie
Trójkąt Pascala
Poniżej znajduje się trójkąt Pascala o siedmiu rzędach.
Algorytm tworzenia trójkąta Pascala jest bardzo prosty. Pierwszy rząd składa się z jedynki, drugi z dwu jedynek ułożonych po bokach jedynki z pierwszego rzędu. Kolejne rzędy powstają w ten sposób, że po bokach stawia się jedynki a kolejne liczby stanowią sumę dwu sąsiednich liczb znajdujących się powyżej.
Napisz program, który utworzy trójkąt Pascala o zadanej przez użytkownika liczbie rzędów, umieszczając liczby w tablicy a następnie wydrukuje zawartość tablicy w ten sposób, że wszystkie liczby nieparzyste zostaną wydrukowane jako gwiazdki a puste miejsca i liczby parzyste jako spacje.
Podpowiedzi - kliknij aby zobaczyć
Odpowiedź - kliknij aby zobaczyć
Ta strona jest częścią materiałów do kursu “Programowanie w Javie z elementami bioinformatyki dla poczatkujących”. Pozostałe materiały znajdziesz tutaj
System.out.println(„tablica2[1][3]: „+tablica2[1][3]);
Powyższy kod wyświetli zawartość czwartej komórki w czwartym rzędzie.
?
Czy jesteś pewien?
@javawatch
Oczywiście był błąd. Poprawiłem.
Dziękuję za zwrócenie uwagi!
Rewelacyjny kurs! Bardzo bardzo bardzo dziękuje:-)
W na prawdę przejrzysty i prosty sposób (trafiający do kompletnego laika) wyjaśniasz poszczególne zagadnienia!
Na prawdę wielkie dzięki i pozdrowienia!
Bardzo się cieszę, że się podoba.
Pozdrawiam serdecznie
Grzegorz Góralski
Dziękuję bardzo za świetny kurs:) Zapewnia szybką „przesiadkę” z C++ :D
Miło mi, że się przydaje :-)
Pozdrawiam serdecznie
Grzegorz Góralski
Coś z podstawą trójkąta jest nie tak, innymi słowy jest niemalże pusta.
Jeżeli chodzi o sam kurs to ten trójkąt aktualnie to czarna magia :p
Hmm… dla niektórych wartości, np. dla 9 rzędów, w podstawie trójkąta powinny wyświetlać się tylko skrajne gwiazdki bo tylko tam znajdują się liczby nieparzyste (1).
Hm… Mam problem z trójkątem. Znaczy niby wszystko ok, ale program nie wykonuje się. Wyświetla się tylko komunikat „Podaj ilość rzędów”, a potem działa w nieskończoność. Program nie wyświetla żadnych komunikatów o błędzie, jakichkolwiek danych.
Kod wygląda tak:
import java.util.Arrays;
import java.util.Scanner;
public class TablicaV {
public static void main(String[] args){
Scanner skaner = new Scanner(System.in);
System.out.println(„Podaj ilość rzędów: „);
int rzedy = skaner.nextInt();
int kolumny = 2*rzedy + 1;
int[][] tab = new int [rzedy][kolumny];
tab [0][rzedy] = 1;
for (int i = 1; i < rzedy; i++) {
for (int j = rzedy – i; j <= rzedy + i; j++){
tab [i][j] = tab[i-1][j=1]+tab[i-1][j+1];
}
}
for(int[] tab1 : tab){
System.out.println(Arrays.toString(tab1));
}
for (int [] tab1 : tab) {
for (int x : tab1) {
if(x%2 != 0 ) {
System.out.print("*");
} else {
System.out.print(" ");
}
}
System.out.println("");
}
}
}
Nie umiem znaleźć błędu :/ Proszę o jakąś podpowiedź :)
fajne…przyda sie w moich badaniach