Zaawansowane typy danych
Teraz, gdy znasz już proste typy danych oraz konstrukcje dające pełną programistyczną moc, czas zapoznać się z bardziej złożonymi typami: tablicą i słownikiem.
Tablica (Array)
Dotychczas, w naszych programach mieliśmy, co najwyżej kilka zmiennych. W prawdziwym życiu programiści muszą borykać się z przypadkami, kiedy ilości danych "idą w terabajty". Wyobraź sobie sytuację, w której chcesz wpisać do programu średnią, dobową temperaturę wszystkich dni w minionym roku. Tworzenie osobnej zmiennej dla każdego dnia nie jest dobrym pomysłem (choćby z powodu czytelności kodu). Właśnie w takich sytuacjach przydają się tablice. Tablica to obiekt, który w uporządkowany sposób “przechowuje” inne obiekty. Przykład:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> a = [1, "word", 10, nil] => [1, "word", 10, nil] irb(main):002:0>
Składnia jest bardzo intuicyjna. Tablicę tworzymy poprzez wpisanie oddzielonych przecinkami elementów w nawiasy kwadratowe. Aby otrzymać element (i go zmodyfikować) o indeksie n, z tablicy a, należy wpisać a[n] (pamiętaj, że programiści numerują od zera, a nie od jedynki ;)):
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> a = [1, "word", 10, nil] => [1, "word", 10, nil] irb(main):002:0> a[2] => 10 irb(main):003:0>a[0]+=100 => 101
Aby otrzymać zakres elementów możemy użyć… zakresu ;) W ten sam sposób możemy uzyskać część napisu:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> a = [1, "word", 10, nil] => [1, "word", 10, nil] irb(main):002:0> a[2] => 10 irb(main):003:0>a[0]+=100 => 101 irb(main):004:0> a[1..3] => ["word", 10, nil] irb(main):005:0>"Dog, cat and hamster"[0..7] => "Dog, cat"
Aby dodać element na końcu tablicy powinniśmy użyć metody push, lub operatora <<, który de facto jest skróconą formą zapisu tej metody. Jeśli chcesz umieścić element w konkretnym miejscu tablicy - użyj metody insert:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> a = [1, "word", 10, nil] => [1, "word", 10, nil] irb(main):002:0> a[2] => 10 irb(main):003:0>a[0]+=100 => 101 irb(main):004:0> a[1..3] => ["word", 10, nil] irb(main):005:0>"Dog, cat and hamster"[0..7] => "Dog, cat" irb(main):006:0> a.push(96) => [101, "word", 10, nil, 96] irb(main):007:0> a << "a" => [101, "word", 10, nil, 96, "a"] irb(main):008:0> a.insert(2, nil) => [101, "word", nil, 10, nil, 96, "a"]
Analogicznie (przy usuwaniu) do metod push i insert działają pop oraz delete_at:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> arr = [1,2,3,4,5,6,7,8,9] => [1, 2, 3, 4, 5, 6, 7, 8, 9] irb(main):002:0> arr.pop => 9 irb(main):003:0> arr.delete_at(3) => 4
Zauważ, że metody push, << oraz insert zwracają całą liste, natomiast pop oraz delete_at tylko usunięty element.
Słownik (Hash)
Słowniki, nazywane są też tablicami asocjacyjnymi. Działają bardzo podobnie do zwykłych tablic, ale wartości mogą być indeksowane dowolnym typem obiektu (w przypadku tablic są to liczby całkowite). Innymi słowy, słowniki to zbiory par klucz=>wartość. Zauważ, że o ile wartości mogą się powtarzać, klucz musi być jednoznaczny. Poniżej kilka równoważnych sposobów inicjalizacji słownika:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> h1 = {"a"=>1, "b"=>2, "c"=>4, "d"=>4} => {"a"=>1, "b"=>2, "c"=>4, "d"=>4} irb(main):002:0> h2 = Hash["a"=>1, "b"=>2, "c"=>4, "d"=>4] => {"a"=>1, "b"=>2, "c"=>4, "d"=>4} irb(main):003:0> h3 = Hash["a", 1, "b", 2, "c", 4, "d", 4] => {"a"=>1, "b"=>2, "c"=>4, "d"=>4}
Aby uzyskać element zapisany pod danym kluczem (i go zmodyfikować), należy użyć składni analogicznej do tej z tablic:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> h = {"a"=>1, "b"=>nil, "c"=>123} => {"a"=>1, "b"=>nil, "c"=>123} irb(main):002:0> h["a"] => 1 irb(main):003:0> h["b"]=1000 => 1000 irb(main):004:0> h["d"] => nil irb(main):005:0> h => {"a"=>1, "b"=>1000, "c"=>123}
Zauważ, że, gdy “poprosimy” słownik o wartość pod kluczem, którego w nim nie ma, zwrócony będzie nil (analogiczne zachowanie występuje w tablicach).
Aby dodać parę klucz=>wartość do słownika należy użyć poniższej składni:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> h = {"a"=>1, "b"=>2, "c"=>3 => {"a"=>1, "b"=>2, "c"=>3} irb(main):002:0>h["z"] = 1.11 => 1.11 irb(main):003:0> h => {"a"=>1, "b"=>2, "c"=>3, "z"=>1.11}
Jeśli nie należysz do strachliwych spróbuj zastosować/zaadoptować tą składnię dla tablic ;)
Do usuwania elementów ze słownika służy metoda delete:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> h = {"a"=>1, "b"=>2, "c"=>3 => {"a"=>1, "b"=>2, "c"=>3} irb(main):002:0>h["z"] = 1.11 => 1.11 irb(main):003:0> h => {"a"=>1, "b"=>2, "c"=>3, "z"=>1.11} irb(main):004:0> h.delete("a") => 1 irb(main):005:0> h => {"a"=>1, "c"=>123, "z"=>1.11}
Symbol
Symbol to typ służący do reprezentowania nazw. Najczęściej wykorzystuje się je jako klucze w słownikach, gdyż są bardziej wydajne (dzięki nim programy działają szybciej) niż napisy.
Tworzymy je za pomocą dwukropka i następujących po nim liter i cyfr:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> s1 = :dog => :dog irb(main):002:0> h1 = {:one => 1, :two => 2} => {:one=>1, :two=>2} irb(main):003:0> h2 = {one: 1, two: 2} => {:one=>1, :two=>2}
Zauważ, że jeśli używamy symboli jako kluczy słownika możemy użyć specjalnej skróconej formy inicjalizacji słownika, gdzie dwukropek należący do symbolu przestawiamy na jego koniec. Pozwala to pomijać znak => (tzw. hash rocket).
Iterowanie po tablicach i słownikach
Tablice i słowniki pozwalają na wykonywanie pętli po ich kolejnych elementach (tak jak zakresy). Poniżej przykładowy program z użyciem pętli for:
Iterating.rb a = [1,3,7,"end"] h = {a: 1, b:"B", c:8} for i in a puts i end for i,j in h puts i puts j end
jan@Stacja-Linux ~/Pulpit $ ruby Iterating.rb 1 3 7 end a 1 b B c 8
Zauważ, że iterując po słowniku potrzebne są dwie zmienne (w naszym wypadku i oraz j) na które przypisywane będą kolejne elementy, gdyż element słownika to para klucz=>wartość.
Tablice tablic
Elementem tablicy może być dowolny obiekt, w tym... tablica. Zagnieżdżone tablice do dosyć naturalna reprezentacja macierzy (dwu- i więcej wymiarowych). Poniżej przykład inicjalizacji takiej macierzy oraz wypisanie jej elementu (zauważ jakie to proste i eleganckie ;)):
jan@Stacja-Linux ~ $ irb irb(main):001:0> matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] => [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] irb(main):002:0> matrix[2][0] => 9
Przegląd najważniejszych metod (i operatorów) dla list i słowników.
Metody wymienione wcześniej w rozdziale nie są wypisane poniżej.
Tablice
Metoda | Opis |
---|---|
count(n) | Zwraca ilość elementów w tablicy (jeśli nie podamy argumentu n) lub ilość elementów równych n w tablicy. |
+ | Zwraca konkatenację podanych tablic. |
& | Zwraca część wspólną podanych tablic. |
clear | Usuwa wszystkie elementy z listy. |
index(obj) | Zwraca indeks pierwszego elementu równego obj w tablicy lub nil jeśli takowego w niej nie ma. |
reverse | Zwraca tablicę z odwróconą kolejnością elementów. |
sort | Zwraca tablicę z posortowanymi elementami. |
uniq | Zwraca tablicę z usuniętymi powtórzeniami elementów. |
Poniżej praktyczne spojrzenie na powyższe metody:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> a = [1,2,3,4,5,6,7,8,9] => [1, 2, 3, 4, 5, 6, 7, 8, 9] irb(main):002:0> a.count 8 => 1 irb(main):003:0> a.count 11 => 0 irb(main):004:0> a+a => [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9] irb(main):005:0> a&[0,5,10] => [5] irb(main):006:0> a.index 2 => 1 irb(main):007:0> a.index 11 => nil irb(main):008:0> a.reverse => [9, 8, 7, 6, 5, 4, 3, 2, 1] irb(main):009:0> a.sort => [1, 2, 3, 4, 5, 6, 7, 8, 9] irb(main):010:0> [9,3,2,9,2,3,4,5,1,2,3,1].sort => [1, 1, 2, 2, 2, 3, 3, 3, 4, 5, 9, 9] irb(main):011:0> ["aba","aca","a","b"].sort => ["a", "aba", "aca", "b"] irb(main):012:0> [1,1,1,1,1,2,2,2,2,1,1,1,2,2,2,3,4,5,6,5,4,3,4,5,6].uniq => [1, 2, 3, 4, 5, 6] irb(main):013:0> a.clear => [] irb(main):014:0> a => []
Słowniki
Metoda | Opis |
---|---|
length | Zwraca liczbę elementów w słowniku. |
has_key? | Zwraca true, jeśli słownik zawiera podany klucz i false w przeciwnym przypadku. |
has_value? | Zraca true, jeśli słownik zawiera podaną wartość i false w przeciwnym przypadku. |
clear | Usuwa wszystkie elementy ze słownika. |
invert | Zwraca słownik, w którym wartości podanego słownika są kluczami i odwrotnie. |
merge(other_hash) | Zwraca słownik będący połączeniem dwu podanych słowników. |
Poniżej powyższe metody w przykładach:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> h = {a: 1, b: 2, c: 3} => {:a=>1, :b=>2, :c=>3} irb(main):002:0> h.length => 3 irb(main):003:0> h.has_key? :a => true irb(main):004:0> h.has_key? :q => false irb(main):005:0> h.has_value? :a => false irb(main):006:0> h.has_value? 1 => true irb(main):007:0> h.merge({d: 4, e: 5}) => {:a=>1, :b=>2, :c=>3, :d=>4, :e=>5} irb(main):008:0> h.invert => {1=>:a, 2=>:b, 3=>:c} irb(main):009:0> h.clear => {} irb(main):010:0>
Po co komu podłoga?
Kilka rozdziałów temu mówiliśmy sobie o dziwnym zachowaniu Ruby’iego przy dzieleniu liczb całkowitych:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> 9/5 => 2
Poniżej krótki przykład pokazujący, że czasem jest to bardzo użyteczny feature:
jan@Stacja-Linux ~/Pulpit $ irb irb(main):001:0> a = [1,2,3,4,5,6,7,8,9] => [1, 2, 3, 4, 5, 6, 7, 8, 9] irb(main):002:0> a[a.length/2] => 5