Any fool can make things bigger, more complex, and more violent. It takes a touch of genius and a lot of courage to move in the opposite direction. Albert Einstein

o Ruby

Nowości i zmiany w Ruby 1.9 #1 - ordered Hash

16 komentarzy | Kategorie: Ruby, Techblog | trackback
Tagi:
ruby 1.9 changes approved - logo

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.

Jeśli spodobał Ci się wpis to może umieścisz ten blog w swoim czytniku RSS?

Komentarze

1. avatar icon teamon napisał(a) 30 Lis 2008 o godz. 21:37:

To jest jeden z tych „ficzerow” ktorych mi czasem bardzo brakowalo w ruby. Mozna bedzie sortowac taki hash?

2. avatar icon c34 napisał(a) 30 Lis 2008 o godz. 22:13:

„kilku przepięć wskaźników” jest chyba bardziej po polsku niż „kilku przepiąć wskaźników”

3. avatar icon MySZ napisał(a) 01 Gru 2008 o godz. 08:37:

O, zaczyna się robić jak array() w PHP ;)

4. avatar icon Radarek napisał(a) 01 Gru 2008 o godz. 10:08:

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.

hash.keys.sort.each {|k| p hash[k] }

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.

5. avatar icon MySZ napisał(a) 01 Gru 2008 o godz. 10:18:

@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.

6. avatar icon Paweł Kondzior napisał(a) 01 Gru 2008 o godz. 10:18:

Przepraszam ze sie podszylem, totalnie nie chcacy, oczywiscie wpis byl mojego autorstwa :)

7. avatar icon teamon napisał(a) 01 Gru 2008 o godz. 15:37:

@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)

8. avatar icon Radarek napisał(a) 01 Gru 2008 o godz. 15:42:

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:

h = {"b" => "bar", "a" => "foo"}
h = Hash[*h.sort.flatten]
p h
9. avatar icon Radarek napisał(a) 01 Gru 2008 o godz. 15:46:

team oczywiście zawsze możesz sobie napisać własną implementację klasy SortedHash, które będzie hybrydą pomiędzy tablicą a hashem :).

10. avatar icon Teamon napisał(a) 01 Gru 2008 o godz. 15:50:

Wiem, wiem ;) („team” chyba jest srednim skrotem od mojego nicka :P)

11. avatar icon Radarek napisał(a) 01 Gru 2008 o godz. 18:12:

Ach, to moje roztargnienie. Wymyśliłem Ci właśnie skróconą wersję pseudonimu :).

12. avatar icon MySZ napisał(a) 02 Gru 2008 o godz. 08:40:

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” :)

13. avatar icon Paweł Kondzior napisał(a) 02 Gru 2008 o godz. 12:00:

@MySZ: oczywiście KISS w php złą drogą nie jest, jednak programisci się gdzieś pogubili pare lat temu podczas wcielania jej w życie.

14. avatar icon Tomash napisał(a) 05 Gru 2008 o godz. 15:56:

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…

15. avatar icon Radarek napisał(a) 05 Gru 2008 o godz. 16:04:

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.

16. avatar icon Tomash napisał(a) 07 Gru 2008 o godz. 23:39:

Aha, czyli nie jest to OrderedHash sensu stricte. Uff :)

Dodaj coś od siebie

Możesz korzystać ze składni Textile.

Pola oznaczone * są wymagane.

Proszę o dodawanie komentarzy związanych z tematem postu, sprawy osobiste proszę załatwiać przez maila bądź gg.

Zastrzegam sobie prawo do moderacji komentarzy (edycja, usuwanie).