Nowości i zmiany w Ruby 1.9 #1 - ordered Hash
16 komentarzy | Kategorie: Ruby, Techblog | trackbackTagi: 1.9 ruby ruby1.9
Wpis ten jest jedną z części cyklu pt "Nowości i zmiany w Ruby 1.9". Pełną listę wpisów znajdziesz pod adresem http://radarek.jogger.pl/2008/11/30/nowosci-i-zmiany-w-ruby-1-9/.
Od wersji 1.9 Ruby zachowuje kolejność elementów w Hashu ("ordered Hash", czyli Hash zachowujący kolejność). Oznacza to, że iteracja po elementach hasha następuje dokładnie w takiej samej kolejności w jakiej były wstawiane elementy. Podkreślam słowo "wstawiane", co nie ma związku z sortowaniem kluczy (np. leksykograficznie).
Poniższy kod pokazuje różnicę między 1.8 i 1.9.
h = {"c" => 3, "a" => 1, "b" => 2}
h.each do |k, v|
p [k, v]
end
1.8
$ ruby1.8 -v && ruby1.8 ordered_hash01.rb ruby 1.8.7 (2008-08-11 patchlevel 72) [x86_64-linux] ["a", 1] ["b", 2] ["c", 3]
1.9
$ ruby1.9.1 -v && ruby1.9.1 ordered_hash01.rb ruby 1.9.1 (2008-11-30 revision 20428) [x86_64-linux] ["c", 3] ["a", 1] ["b", 2]
Kolejność kluczy nie zmienia się gdy nadpisujemy istniejący element. Jeśli tego chcemy musimy najpierw usunąć stary klucz i dodać go jeszcze raz. Obrazuje to poniższy przykład.
h = {}
h["a"] = "foo"
h["b"] = "bar"
p h
# {"a"=>"foo", "b"=>"bar"}
h["a"] = nil
p h
# {"a"=>nil, "b"=>"bar"}
h.delete("a")
h["a"] = "foo"
p h
# {"b"=>"bar", "a"=>"foo"}
Jeśli chodzi o kwestię wydajności to zapamiętywanie kolejności kluczy ponosi za sobą dosyć niewielki koszt. W kwestii pamięci są to jedynie dwa wskaźniki przypadające na jeden element hasha (by pamiętać następny i poprzedni). Koszt operacji dodawania/usuwania wzrósł o koszt dodania/usunięcia takiego elementu z listy dwukierunkowej mając już wskaźnik do niego. Jest to zatem kwestia kilku "przepięć" wskaźników (złożoność O(1)).
Praktyczne użycie
Przykładowo implementując model w RoR/Merb chcemy definiować tłumaczenie dla nazw jakiegoś wyliczeniowego atrybutu (np. status). Mogłoby to wyglądać tak:
# model
class User
STATUS_LABELS = {
"active" => "aktywny",
"deleted" => "usunięty"
}
end
# widok
<%= select :user, :status, User::STATUS_LABELS %>
Dzięki temu, że kolejność kluczy jest zapamiętywana lista wyboru wyświetli elementy w takiej samej kolejności.
To jest jeden z tych „ficzerow” ktorych mi czasem bardzo brakowalo w ruby. Mozna bedzie sortowac taki hash?
„kilku przepięć wskaźników” jest chyba bardziej po polsku niż „kilku przepiąć wskaźników”
O, zaczyna się robić jak array() w PHP ;)
teamon: na pytanie „czy można sortować hash” odpowiadam: hasha się nie sortuje :). To co możesz zrobić to posortować wcześniej pobraną listę kluczy, np.
Jeśli chciałbyś przesortować klucze to musisz utworzyć sobie nowy hash i dodawać do niego elementy w odpowiedniej kolejności.
c34: dzięki, poprawione.
MySZ: spokojnie, nic się nie zaczyna. To przemyślana decyzja. Ruby nie musi czerpać nic z PHP.
@Mysz: Nie ma to jak mala prowokacja od rana co ? :) w PHP NIGDY nie bedziesz mogl zrobic tak http://pastie.org/327618 :P Jesli naprawde myslisz ze Ruby zblizyl sie przez to w jakikolwiek sposob do PHP to gratuluje wyobrazni ;] Jesli to tylko prowokacja, to ok lykam jak mlody pelikan, jesli nie to zapraszam do przecytania dokumentacji http://www.ruby-doc.org/core/classes/Hash.html i http://www.ruby-doc.org/core/classes/Enumerable.html oraz zapoznania sie z dokumentacja chodzby Pythona i jego rozwiazan w tej kwesti.
Przepraszam ze sie podszylem, totalnie nie chcacy, oczywiscie wpis byl mojego autorstwa :)
@radarek:
>> {„b” => 1, „a” => 2}.sort
=> [[„a”, 2], [„b”, 1]]
;]
Bo skoro juz mamy zachowanie kolejnosci to mozna by owa kolejnosc zmienic poprzez sortowanie (a nie zamieniac na tablice)
team, wiem do czego zmierzałeś, ale moim zdaniem dobrze, że zmiana dotyczy tylko i wyłącznie tego co opisałem i nic poza tym. Inaczej mięlibyśmy phpowe array, które ni to jest tablicą ni to hashem.
Tak jak mówiłem, jak chcesz to możesz sobie sam utworzyć nowy hash, np tak:
team oczywiście zawsze możesz sobie napisać własną implementację klasy SortedHash, które będzie hybrydą pomiędzy tablicą a hashem :).
Wiem, wiem ;) („team” chyba jest srednim skrotem od mojego nicka :P)
Ach, to moje roztargnienie. Wymyśliłem Ci właśnie skróconą wersję pseudonimu :).
Radarek,Paweł Kondzior: oczywiście że to był żart. Pomimo mojej swojej dość małej sympatii do Rubiego, wcale nie życzę mu podążania „the PHP way” :)@MySZ: oczywiście KISS w php złą drogą nie jest, jednak programisci się gdzieś pogubili pare lat temu podczas wcielania jej w życie.
Radarek, OrderedHasha jak najbardziej się sortuje. Po to właśnie powstał – żeby móc ustawić arbitralny porządek, a nie „kolejność jest ukryta żeby interpreter mógł przeszukiwać w czasie O(1)” ;)
Mam nadzieję że implementacja w Ruby1.9 jest dojrzalsza niż w Railsach 2.0. Tak, kochani, RoR od grudnia’07 ma OrderedHasha, konkretnie dziedziczącego z Array i zhackowanego w jednym nieudokumentowanym (stąd próżno szukać w railsowym rdocu) ekranie kodu. Żeby się do czegoś nadawał, trzeba mu jeszcze parę linijek dopisać/domonkeypatchować.
Chciałbym tylko, żeby OrderedHash mógł być inicjalizowany/tworzony on-demand, a nie domyślnie zastępował wszystkie hashe w języku. Czasem (często) nie obchodzi mnie kolejność par, bo nie zamierzam po hashu iterować, a stały czas wyszukiwania (wyjęcia) czy włożenia elementu jest nie do przecenienia przy większych zestawach danych…
Tomash: zależy jak będziemy definiować „ordered hash”. W 1.9 Hash po prostu pamięta kolejność wstawianych elementów i tyle. Nie udostępnia żadnego interfejsu do przesortowania ich, jeśli chciałbyś to zrobić to musisz (jak pisałem) utworzyć nowy hash i powstawiać do niego elementy w zadanej kolejności.
Cała reszta zostaje taka sama. Wstawianie, pobieranie, usuwanie elementu ma dokładnie taką samą (tj. O(1)) złożoność. Gdyby miało być inaczej to na pewno nie zostałoby to zrobione.
Aha, czyli nie jest to OrderedHash sensu stricte. Uff :)