Twitter: @grzegg
Kategoria: java, Tagi: - - - - .

Java [16] – Tablice cz. II: parę sztuczek

Zanim przejdziemy do tablic wielowymiarowych pokażę parę sztuczek związanych z tablicami.

Ta strona jest częścią materiałów do kursu “Programowanie w Javie z elementami bioinformatyki dla poczatkujących”. Pozostałe materiały znajdziesz tutaj

Kopiowanie tablic

Próba skopiowania tablicy przy pomocy zwykłego operatora przypisania = przynosi efekty nieco inne od oczekiwanych. Jeśli uruchomimy kod:


int[] tablicaOryginalna = {1, 2, 3, 4};
int[] tablicaSkopiowana = tablicaOryginalna;
System.out.println("Oryginalna, 4 element: " + tablicaOryginalna[3]);
System.out.println("Kopia, 4 element: " + tablicaSkopiowana[3]);
tablicaSkopiowana[3] = 10;
System.out.println("Oryginalna, 4 element: " + tablicaOryginalna[3]);
System.out.println("Kopia, 4 element: " + tablicaSkopiowana[3]);

to otrzymamy dość dziwny rezultat:


Oryginalna, 4 element: 4
Kopia, 4 element: 4
Oryginalna, 4 element: 10
Kopia, 4 element: 10

Jak widać, zmiana wartości przeprowadzona na kopii tablicy, spowodowała także zmianę w tablicy oryginalnej. Dlaczego tak się dzieje? Przypominam, że tablice przechowują jedynie odnośniki do elementów, skopiowanie za pomocą operatora = tablicy powoduje, że kopia przechowuje adresy do tych samych elementów co oryginał. Skoro obie tablice wskazują na ten sam element nie ma znaczenia na której przeprowadzimy operacje, efekty będą widoczne na obu.

Zwykle jednak kopiujemy tablice po to, żeby na jednej z nich przeprowadzać pewne operacje związane ze zmianą przechowywanych przez nie wartości, a druga powinna przechować pierwotną zawartość bądź zmieniać ją w inny sposób. Jak zatem otrzymać rzeczywistą kopię tablicy?

Kopiowane po kolei elementów

Pierwszą metodą jest stworzenie tablicy o takiej samej długości (i typie rzecz jasna) co tablica, którą chcemy skopiować a następnie kopiowanie po kolei wszystkich elementów z jednej tablicy do drugiej:

Teraz wynik wygląda lepiej.

Klonowanie metodą… clone()

Tablice, które są obiektami, udostępniają pewne metody a jedną z nich jest clone(). Bez wątpienia jest to sposób prostszy niż poprzedni:

int[] kopia = (int[])oryginal.clone();

Zauważ, że zastosowaliśmy tu rzutowanie. Bez tego też kod by zadziałał, ale stosowanie takiej praktyki pozwala uchronić się przed pewnego rodzaju błędami.

Wykorzystanie java.util.Arrays

Klasa java.util.Arrays udostępnia zestaw pożytecznych metod do obsługi tablic. Zanim przejdziemy do kopiowania tablic poznamy metodę, która pozwala w łatwy sposób wyświetlić wartości przechowywane przez tablicę:

Arrays.toString(tablica)

Na przykład po wykonaniu kodu:


int[] liczby = {1, 3, 4, 5, 8};
System.out.println(Arrays.toString(liczby));

Otrzymamy:


[1, 3, 4, 5, 8]

Teraz czas na kopiowanie:


Arrays.copyOf(tablica, długośćKopii)

Na przykład:


int[] liczby = {1, 3, 4, 5, 8};
System.out.println(Arrays.toString(liczby));
int[] kopiaLiczb = Arrays.copyOf(liczby, liczby.length);
System.out.println("Oryginał: "+ Arrays.toString(liczby));
System.out.println("Kopia: "+ Arrays.toString(kopiaLiczb));
kopiaLiczb[2]=0;
System.out.println("Oryginał: "+ Arrays.toString(liczby));
System.out.println("Kopia: "+ Arrays.toString(kopiaLiczb));

Rezultat:


Oryginał: [1, 3, 4, 5, 8]
Kopia: [1, 3, 4, 5, 8]
Oryginał: [1, 3, 4, 5, 8]
Kopia: [1, 3, 0, 5, 8]

Jak wynika z powyższego, można też skopiować część tablicy:


int[] czescLiczb = Arrays.copyOf(liczby, 3);
System.out.println("Oryginał: "+ Arrays.toString(liczby));
System.out.println("Kopia: "+ Arrays.toString(czescLiczb));

Po uruchomieniu kodu otrzymamy:


Oryginał: [1, 3, 4, 5, 8]
Kopia: [1, 3, 4]

Uwaga, jeśli tablica do której kopiujemy jest dłuższa niż liczba elementów kopiowanych, to zostanie ona skrócona, jeśli przekażemy jako argument długość tablicy kopiowanej:

Rezultat:


tab1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Długośc tab1: 10
tab1: [1, 2, 3, 4]
Długośc tab1: 4

Jak widać tablica tab1 została skrócona do czterech elementów. Aby tego uniknąć, wystarczy podać jako argument długość tablicy dłuższej:


tab1 = Arrays.copyOf(tab2, tab1.length);

Teraz wynik będzie taki:


tab1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Długość tab1: 10
tab1: [1, 2, 3, 4, 0, 0, 0, 0, 0, 0]
Długość tab1: 10

Poniżej opiszę jeszcze kilka użytecznych metod udostępnianych przez klasę java.util.Arrays

Metoda System.arraycopy()

Ten sposób pozwala na częściowe skopiowanie tablic. Format polecenia wygląda tak:


System.arraycopy(tablica1, indexStart1, tablica2, indexStart2, liczbaElementow)

Jak nietrudno się domyśleć zawartość tablicy1 jest począwszy od indeksu index1 kopiowania do tablicy2, przy czym elementy są umieszczane począwszy od indeksu indexStart2, kopiowana jest liczba elementów równa wartości liczbaElementow.

Kilka innych możliwości klasy java.util.Arrays

Klasa java.util.Arrays, poza wspomnianą wcześniej możliwością łatwego kopiowania tablic(copyOf()) udostępnia inne, użyteczne metody. Poniżej wymieniłem niektóre z nich.

  • Arrays.toString – wydrukowanie zawartości tablicy, zwracany jest łańcuch znaków
  • Arrays.copyOf(tablica, długośćKopii) – kopiowanie (patrz wyżej)
  • Arrays.fill(tablica, element) – wypełnianie tablicy podanym elementem
  • Arrays.equals(tablica1, tablica2) – porównanie tablic, zwracana jest wartość false lub true
  • Arrays.sort(tablica) – sortowana jest zawartość tablicy, nic nie jest zwracane, zmieniana jest kolejność elementów w tablicy. Jeśli chcemy mieć posortowaną kopię, najpierw trzeba tablicę skopiować a później posortować kopię
  • Arrays.binarySearch(tablica, szukanyElement) – szukanie w posortowanej tablicy, zwracany jest indeks, w którym znajduje się szukany element

Zastosowanie powyższych metod można pokazać na przykładzie:

Zauważ, że w powyższych kodach odwoływaliśmy się bezpośrednio do klasy Arrays, nie tworzyliśmy obiektu jak na przykład w przypadku java.util.Scanner. Jest tak, ponieważ klasa java.util.Arrays udostępnia metody statyczne. Na razie niewiele to wyjaśnia, ale wrócimy jeszcze to tego tematu.

Pełny wykaz dostępnych metod można znaleźć w oficjalnej dokumentacji klasy pod adresem: https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html. Przy okazji warto zwrócić uwagę na to, jakie typy danych metody obsługują oraz które z nich pozwalają na pracę z tablicami wielowymiarowymi, którym poświęcę następną lekcję.

Czy można zmienić długość tablicy?

Dotychczas poznane właściwości tablic wskazują na to, że jeśli raz określimy wielkość tablicy, to nie można już jej zmienić. W zasadzie jest to prawda, ale istnieją pewne triki, które umożliwiają obejście tego ograniczenia. Powyżej, przy okazji kopiowania tablicy za pomocą Arrays.copyOf() zmniejszyliśmy tablicę, teraz spróbujemy ją zwiększyć. Pokażę to na przykładzie. Warto jednak zauważyć, że jeśli chcemy mieć możliwość przechowywania serii elementów w strukturze o zmiennej długości to warto zainteresować się listami tablicowymi (ang. ArrayLists) o których oczywiście także napiszę w przyszłości.

Wyobraźmy sobie, że piszemy program, w którym będzie przechowywana sekwencja nukleotydów w postaci tablicy zmiennych typu char (w końcu do czegoś się przydadzą). Na początku została ona ustalona, poprzez wpisanie pewnej początkowej sekwencji, na 6 znaków. Potem jednak chcemy przedłużyć sekwencje o trzy dodatkowe nukleotydy. Jak to zrobić?

Sposób 1

Na początku tego wpisu pokazałem, jak nie kopiować tablic, tłumacząc, że wykorzystanie operatora = kopiuje tylko wskaźniki do elementów z jednej tablicy do drugiej, w efekcie obie tablice wskazują na te same elementy. Teraz wykorzystamy tą możliwość. Przepisz, wykonaj a potem przeanalizujmy poniższy kod.

Rezultat wygląda tak, przy czym niewypełnione komórki mogą być wydrukowane np. jako kwadraty:


seq: [A, C, T, A, A, G]
tpm: [ , , , , , , , , ]
tmp: [A, C, T, A, A, G, , , ]
seq: [A, C, T, A, A, G, , , ]
seq: [A, C, T, A, A, G, C, A, T]

Kluczowe w tym programie było wykonanie operacji: seq = tmp. Od tego miejsca tablica seq wskazuje na te same elementy, które wskazuje tablica tmp, a ponieważ jest ich 9, to wygląda to tak, jakby z tablicy pojawiło się więcej miejsca.

Sposób 2

Drugi sposób wykorzystuje metodę Arrays.copyOf(), którą omawialiśmy powyżej. Być może, kiedy pisałem o tym, że jeśli liczba, podana jako argument jest mniejsza niż długość tablicy do której odbywa się kopiowanie, to jest ona zmniejszana, przyszło Ci na myśl, że może to też działać w drugą stronę. Tak, działa. Poniżej znajduje się nieco zmodyfikowana wersja przykładu ze „Sposobu 1”, jest ona przy tym nieco krótsza, nie zawiera też tymczasowej tablicy.

Komenda seq = Arrays.copyOf(seq, seq.length+3) wykonuje operację kopiowania do samej siebie ale ponieważ argument określający długość kopii jest o 3 większy niż tablica, po kopiowaniu tablica seq posiada o trzy elementy więcej niż poprzednio.

Ćwiczenie – sortowanie tablicy

Znanych jest wiele algorytmów pozwalających posortować elementy w tablicy. W praktyce można oczywiście użyć klasy java.util.Arrays ale ten sposób nie przybliży nas do zrozumienia jak taki proces może przebiegać. Poniżej pokazałem dwa, należące do najprostszych, algorytmów ale zanim przejdziesz dalej spróbuj samodzielnie wymyślić w jaki sposób takie sortowanie mogłoby przebiegać i napisać odpowiedni kod.


<

p class=”uwaga”>Ta strona jest częścią materiałów do kursu “Programowanie w Javie z elementami bioinformatyki dla poczatkujących”. Pozostałe materiały znajdziesz tutaj

8 komentarzy Java [16] – Tablice cz. II: parę sztuczek

Leave a Reply