« poprzedni punkt  następny punkt »

3. Tablice

Często dane w programie będziemy chcieli grupować w pewne zestawy, które stanowią logiczną całość, choć składają się z poszczególnych elementów. Taki zestaw powinien mieć jedną nazwę (bo przecież oznacza logiczną całość), a poszczególne jego elementy powinny być dostępne przez tę nazwę oraz jakąś dodatkową informację, pozwalającą odnaleźć element w zestawie. 
Szczególnym przypadkiem takich zestawów danych są tablice.

Tablice są zestawami elementów danych, ułożonych na określonych pozycjach. Do każdego z tych elementów mamy bezpośredni ( swobodny - nie wymagający przeglądania innych elementów zestawu) dostęp poprzez nazwę tablicy i pozycję elementu w zestawie, określaną przez indeks lub indeksy.

Wyobraźmy sobie np., że chcemy posługiwać się zestawem dni tygodnia.
Zestaw ten - tablicę - nazwiemy dni.
Kolejne jego elementy (kolejne dni tygodnia) ponumerujemy za pomocą indeksów (jedynka będzie oznaczać poniedziałek, dwójka - wtorek itd.).
Musimy mieć jeszcze jakąś konstrukcję składniową, która pozwoli oznaczać kolejne elementy tablicy.
W naszym uproszczonym języku (bazującym na REXXie) taką konstrukcją - podobnie jak w Javie (C i C++) - będą nawiasy kwadratowe, w których umieszczać będziemy indeksy (uwaga: jest to zmiana składniowa w stosunku do klasycznego REXXa, zastosowana w uproszczonej, zmodyfikowanej wersji tego języka, którą się posługujemy w naszych ćwiczeniach).

Możemy zatem zapisać:

dni[1] = "poniedziałek";
dni[2] = "wtorek";
dni[3] = "środa";
dni[4] = "czwartek";
dni[5] = "piątek";
dni[6] =  "sobota";
dni[7] =  "niedziela";

Jak widać elementy tablic możemy traktować jako zmienne. Zmiennym tym (tak jak każdym innym zmiennym) poprzez przypisanie nadawane są podane wartości.
Nie jest to jedyny sposób ustalenia wartości elementów tablicy.

Uwaga! W uproszczonym Rexx-ie inicjacja tablicy za pomocą nawiasów klamrowych musi znajdować się w jednym wierszu programu i może dotyczyć tylko tablic jednowymiarowych.
Nie wolno przy tym zapomnieć o umieszczeniu pustych nawiasów [ ] po nazwie tablicy.

Tablice możemy inicjować w szybszy sposób za pomocą specjalnych konstrukcji syntaktycznych. W naszym uproszczonym, zmodyfikowanym REXXie, inicjacja tablic  będzie dokonywana (podobnie jak w Javie) za pomoca nawiasów klamrowych:

dni[] = {"poniedziałek", "wtorek", "środa", "czwartek", "piątek", "sobota", "niedziela" };

Ten zapis oznacza dokładnie to samo, co poprzedni: elementowi z indeksem 1 tablicy dni nadawana jest wartość "poniedziałek", elementowi z indeksem 2 - wartość "wtorek" itd.

Oczywiście, możemy nie tylko ustalać, ale  również pobierać wartości elementów tablicy.
W poniższym programie wypisujemy aktualny dzień tygodnia, korzystając przy tym z wbudowanej funkcji date().

/* Podaje aktualny dzień tygodnia */

dni[]={"poniedziałek", "wtorek", "środa", "czwartek", "piątek", "sobota", "niedziela"};
ldni = date('B'); /* liczba dni od 1 stycznia 0001 */
nrDnia = ldni // 7 + 1;
say 'Dzisiaj jest :' dni[nrDnia];

Wbudowana funkcja date wywołana z argumentem 'B' zwraca liczbę dni, które minęły od 1 stycznia 0001 roku (kalendarza gregoriańskiego) do wczoraj włącznie. Ponieważ wiadomo, że wtedy była niedziela, to reszta z dzielenia tej liczby dni przez 7 da: dla poniedziałku 0, dla wtorku 1,... dla niedzieli 6. Dodając do tego wyniku 1 uzyskamy odpowiedni indeks naszej tablicy dni.

Proszę zapisać i uruchomić omawiany wyżej program na własnym komputerze.

Inny przykład: znów trochę lepsza wersja liczenia ceny komputera. Tym razem nazwy składowych komputera i odpowiadające im ceny będziemy przechowywać w tablicach. Dzięki temu modyfikacje programu (np. wyszczególnienie większej liczby składników) będą bardzo łatwe. Ponadto uzyskamy łatwą (przy poprzednim sposobie oprogramowania nieosiagalną ) możliwość interakcyjnej zmiany ceny dowolnego wybranego składnika.

/* Obliczenie ceny komputera */

N = 6 /* liczba składników */

/* Tablica nazw składników */
elt[] = { "Procesor", "Płyta", "Pamięć", "Dysk", "Monitor", "Inne" };

/*
   Pobieramy ceny każdego składnika
   i liczymy ich sumę
*/
suma = 0
do i = 1 to N
   say elt[i] "- podaj cenę: ";
   cena[i] = linein();
   suma = suma + cena[i];
end

say "Cena komputera wynosi :" suma;
say "Udział ceny procesora :" cena[1]/suma;

/*
   Dajemy użytkownikowi możliwość
   zmiany ceny dowolnego składnika
*/
say "Którą cenę chcesz zmienić ? Wybierz";
do i = 1 to n
   say i '-' elt[i];
end
say "Podaj wybrany numer:"
nr = linein();

/*
   Podany numer musi zawierać się
   w zakresie od 1 do N - liczby składników
*/

if (nr < 1 | nr > N) then do
   say "Zły wybór";
   exit;
end

/* zachowujemy starą cenę wybranego składnika */
stara_cena = cena[nr];

/*
   Pobieramy nową cenę wybranego skladnika
   i obliczamy nową cenę komputera
*/

say elt[nr] " - wprowadz nową cenę";
cena[nr] = linein();
suma = suma - stara_cena + cena[nr];

say "Cena komputera wynosi :" suma;
say "Udział ceny procesora :" cena[1]/suma;

Przykładowy wydruk działania programu
Procesor - podaj cenę:
500
Płyta - podaj cenę:
500
Pamięć - podaj cenę:
500
Dysk - podaj cenę:
500
Monitor - podaj cenę:
1000
Inne - podaj cenę:
500
Cena komputera wynosi : 3500
Udział ceny procesora : 0.142857143
Którą cenę chcesz zmienić ? Wybierz
1 - Procesor
2 - Płyta
3 - Pamięć
4 - Dysk
5 - Monitor
6 - Inne
Podaj wybrany numer:
1
Procesor  - wprowadz nową cenę
700
Cena komputera wynosi : 3700
Udział ceny procesora : 0.189189189

W tym programie warto zwrócić uwagę na zastosowanie zmiennej o nazwie N dla oznaczenia liczby skladników. Moglibyśmy jej nie używać, pisząc do i = 1 to 6. Ale wtedy zmiana liczby składników pociągałaby za sobą konieczność zmian kodu w wielu miejscach programu. Mając N i pisząc zawsze do i =1 to N, możemy zmienić wartość N tylko raz, w jednym miejscu i w ten sposób mamy zarówno mniej pracy jak i w mniejszym stopniu narażeni jesteśmy na błędy.

Proszę zapisać i uruchomić omawiany wyżej program na własnym komputerze.

Dotąd posługiwaliśmy się tablicami jednowymiarowymi. Nic nie stoi na przeszkodzie, by tworzyć i używać tablic wielowymiarowych. W naszym uproszczonym REXXie odwołania do tablic wielowymiarowych będę miały postać:

                a[i][j][k]...

gdzie kolejne nawiasy kwadratowe służą indeksowaniu w każdym z wymiarów.

Typowym przykładem tablicy dwuwymiarowej jest macierz (czyli tablica liczb, ułożonych w wierszach i kolumnach).
Poniższy program realizuje algorytm mnożenia macierzy (dla uproszczenia: kwadratowych). Element i-go wiersza i j-ej kolumny macierzy C, będącej iloczynem macierzy A i macierzy B,  jest sumą iloczynów elementów i-go wiersza macierzy A przez elementy j-ej kolumny macierzy B.

/* Mnożenie macierzy kwadratowych */

N = 2;

a[1][1] = 1;  b[1][1] = 2;
a[1][2] = 2;  b[1][2] = 2;
a[2][1] = 3;  b[2][1] = 3;
a[2][2] = 7;  b[2][2] = 2;

do i = 1 to N;
  do j = 1 to N;
    c[i][j] = 0;
    do k = 1 to N;
       c[i][j] = c[i][j] + a[i][k] * b[k][j];
    end
  end
end

say 'Wynik'
do i = 1 to N
  out = '';
  do j = 1 to N
    out = out right(c[i][j], 8);
  end
  say out;
end

Wynik:
        8        6
       27       20


Wynik podajemy "wierszami", składając elementy każdego wiersza w zmiennej out. Funkcja right umieszcza swój pierwszy argument (w tym przypadku c[i][j] w polu o rozmiarach, podanych jako drugi argument (8) z wyrównaniem do prawej strony. Dzięki temu uzyskujemy "ładny" wydruk.

W tym intuicyjnym wprowadzeniu do tablic pominęliśmy sporo istotnych szczegółów:

  • jakie wartości mogą przybierać indeksy?
  • czy pierwszy element tablicy zawsze ma ustalony indeks i - jeśli tak - to jaki?
  • czy rozmiary tablicy mogą być ustalane w trakcie wykonania programu?
  • czy rozmiary te mogą w trakcie wykonania programu zmieniać się?
  • w jaki sposób tablice są lokowane w pamięci komputera?

Każdy język programowania odpowiada nieco inaczej na te pytania.
W szczególności w REXXie (również w naszej uproszczonej i zmodyfikowanej wersji tego języka) indeksy mogą przybierać dowolne wartości, a tablice są tworzone dynamicznie w trakcie działania programu i mogą dynamicznie się rozszerzać.

Natomiast w Javie:

  • indeksy mogą przybierać tylko nieujemne wartości całkowitoliczbowe,
  • tablice zawsze są indeksowane od 0 - pierwszy element tablicy ma indeks 0 (jest to kolejne świadectwo niskopoziomowej składni Javy, przejętej od C: nienaturalne zero odzwierciedla techniczne właściwości implementacyjne - jest wartością, którą trzeba dodać do adresu początku tablicy by uzyskać dostęp do pierwszego jej elementu),
  • rozmiary tablicy mogą być dynamicznie podane przy jej tworzeniua,
  • ale potem nie mogą się już zmieniać.

Należy dodać, że do odzwierciedlania bardziej zaawansowanych przypadków - np. dynamicznie rozszerzalnych tablic - służą w Javie  inne niż tablice struktury danych, (o czym będziemy mówić w drugim semestrze).


« poprzedni punkt  następny punkt »