Po co używać symboli jako kluczy z krzyżykiem w Rubim?

162

Wiele razy ludzie używają symboli jako kluczy w hashu Rubiego.

Jaka jest przewaga nad używaniem sznurka?

Na przykład:

hash[:name]

vs.

hash['name']
Maks
źródło

Odpowiedzi:

227

TL; DR:

Używanie symboli nie tylko oszczędza czas podczas dokonywania porównań, ale także oszczędza pamięć, ponieważ są one przechowywane tylko raz.

Symbole rubinowe są niezmienne (nie można ich zmienić), co znacznie ułatwia wyszukiwanie czegoś

Krótka (ish) odpowiedź:

Używanie symboli nie tylko oszczędza czas podczas dokonywania porównań, ale także oszczędza pamięć, ponieważ są one przechowywane tylko raz.

Symbole w Rubim to po prostu „niezmienne ciągi znaków” … to znaczy, że nie można ich zmienić, co oznacza, że ​​ten sam symbol, do którego odwołuje się wiele razy w kodzie źródłowym, jest zawsze przechowywany jako ta sama jednostka, np. Ma ten sam identyfikator obiektu .

Z drugiej strony łańcuchy są zmienne , można je zmienić w dowolnym momencie. Oznacza to, że Ruby musi przechowywać każdy napis, o którym wspominasz w kodzie źródłowym, w osobnej encji, np. Jeśli masz ciąg znaków „nazwa” wielokrotnie wspomniany w kodzie źródłowym, Ruby musi przechowywać je wszystkie w osobnych obiektach typu String, ponieważ może się później zmienić (taka jest natura łańcucha Ruby).

Jeśli używasz ciągu jako klucza Hash, Ruby musi oszacować ciąg i spojrzeć na jego zawartość (i obliczyć na nim funkcję skrótu) i porównać wynik z (zahaszowanymi) wartościami kluczy, które są już zapisane w Hash .

Jeśli używasz symbolu jako klucza Hash, jest domniemane, że jest niezmienny, więc Ruby może po prostu porównać (funkcję skrótu) object-id z (zaszyfrowanymi) identyfikatorami obiektów kluczy, które są już przechowywane w Hash. (o wiele szybciej)

Wada: Każdy symbol zajmuje miejsce w tabeli symboli interpretera Rubiego, które nigdy nie jest zwalniane. Symbole nigdy nie są zbierane jako śmieci. Tak więc przypadek narożny ma miejsce, gdy masz dużą liczbę symboli (np. Automatycznie generowanych). W takim przypadku powinieneś ocenić, jak wpływa to na rozmiar twojego interpretera Rubiego.

Uwagi:

Jeśli wykonujesz porównania ciągów, Ruby może porównywać symbole tylko po ich identyfikatorach obiektów, bez konieczności ich oceny. To znacznie szybsze niż porównywanie ciągów znaków, które należy ocenić.

Jeśli masz dostęp do skrótu, Ruby zawsze stosuje funkcję skrótu, aby obliczyć „klucz skrótu” z dowolnego klucza, którego używasz. Możesz sobie wyobrazić coś w rodzaju skrótu MD5. A potem Ruby porównuje te „zaszyfrowane klucze” ze sobą.

Długa odpowiedź:

https://web.archive.org/web/20180709094450/http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings

http://www.randomhacks.net.s3-website-us-east-1.amazonaws.com/2007/01/20/13-ways-of-looking-at-a-ruby-symbol/

Tilo
źródło
5
Fyi, symbole będą GCd w następnej wersji Rubiego: bugs.ruby-lang.org/issues/9634
Ajedi32
2
Ponadto ciągi znaków są automatycznie zamrażane, gdy są używane jako klucze haszujące w Rubim. Więc nie jest do końca prawdą, że ciągi znaków są zmienne, gdy mówimy o nich w tym kontekście.
Ajedi32,
1
Świetny wgląd w temat i pierwszy link w sekcji „Długa odpowiedź” został usunięty lub przeniesiony.
Hbksagar
2
Symbole to śmieci zebrane w Rubim 2.2
Marc-André Lafortune
2
Świetna odpowiedź! Jeśli chodzi o trolling, Twoja „krótka odpowiedź” jest również wystarczająco długa. ;)
technophyle
22

Powodem jest wydajność, z wieloma korzyściami w ciągu:

  1. Symbole są niezmienne, więc pytanie „co się stanie, jeśli klucz się zmieni?” nie trzeba go pytać.
  2. Ciągi znaków są zduplikowane w kodzie i zazwyczaj zajmują więcej miejsca w pamięci.
  3. Wyszukiwania skrótów muszą obliczyć skrót kluczy, aby je porównać. Dotyczy to O(n)ciągów znaków i stałych symboli.

Co więcej, Ruby 1.9 wprowadził uproszczoną składnię tylko dla hasha z kluczami symboli (np. h.merge(foo: 42, bar: 6)), A Ruby 2.0 ma argumenty słów kluczowych, które działają tylko dla kluczy z symbolami.

Uwagi :

1) Możesz być zaskoczony, gdy dowiesz się, że Ruby traktuje Stringklucze inaczej niż jakikolwiek inny typ. W rzeczy samej:

s = "foo"
h = {}
h[s] = "bar"
s.upcase!
h.rehash   # must be called whenever a key changes!
h[s]   # => nil, not "bar"
h.keys
h.keys.first.upcase!  # => TypeError: can't modify frozen string

Tylko dla kluczy łańcuchowych Ruby użyje zamrożonej kopii zamiast samego obiektu.

2) Litery „b”, „a” i „r” są zapisywane tylko raz dla wszystkich wystąpień :barw programie. Przed Rubim 2.2 ciągłe tworzenie nowych Symbols, których nigdy nie używano ponownie, było złym pomysłem , ponieważ pozostały one na zawsze w globalnej tabeli wyszukiwania symboli. Ruby 2.2 zbierze je jako śmieci, więc nie martw się.

3) W rzeczywistości obliczenie skrótu dla symbolu nie zajęło czasu w Ruby 1.8.x, ponieważ identyfikator obiektu był używany bezpośrednio:

:bar.object_id == :bar.hash # => true in Ruby 1.8.7

W Rubim 1.9.x sytuacja uległa zmianie, ponieważ skróty zmieniają się z jednej sesji na drugą (łącznie z tymi w Symbols):

:bar.hash # => some number that will be different next time Ruby 1.9 is ran
Marc-André Lafortune
źródło
+1 za doskonałe oceny! Pierwotnie nie wspomniałem o funkcji skrótu w mojej odpowiedzi, ponieważ starałem się ułatwić jej czytanie :)
Tilo
@Tilo: rzeczywiście, dlatego właśnie napisałem swoją odpowiedź :-) Właśnie zredagowałem odpowiedź, aby wspomnieć o specjalnej składni w Ruby 1.9 i obiecanych nazwanych parametrach Ruby 2.0
Marc-André Lafortune
Czy możesz wyjaśnić, w jaki sposób wyszukiwania skrótu są stałe dla symboli i O (n) dla ciągów?
Asad Moosvi
7

Re: jaka jest przewaga nad używaniem sznurka?

  • Stylizacja: to w stylu Ruby
  • (Bardzo) nieco szybsze wyszukiwanie wartości, ponieważ haszowanie symbolu jest równoważne haszowaniu liczby całkowitej w porównaniu z haszowaniem ciągu.

  • Wada: zużywa miejsce w tablicy symboli programu, które nigdy nie jest zwalniane.

Larry K.
źródło
4
+1 za wzmiankę, że symbol nigdy nie jest zbierany jako śmieci.
Vortico
symbol nigdy nie jest zbierany jako śmieci - nieprawda, ponieważ rubin 2.2+
eudaimonia
0

Byłbym bardzo zainteresowany kontynuacją dotyczącą zamrożonych ciągów znaków wprowadzonych w Ruby 2.x.

Kiedy masz do czynienia z wieloma ciągami pochodzącymi z danych wejściowych (myślę o parametrach HTTP lub ładunku, na przykład przez Rack), łatwiej jest używać ciągów wszędzie.

Kiedy masz do czynienia z dziesiątkami z nich, ale one nigdy się nie zmieniają (jeśli są to „słownictwo” Twojej firmy), lubię myśleć, że ich zamrożenie może coś zmienić. Nie zrobiłem jeszcze żadnego testu porównawczego, ale myślę, że byłoby to blisko wydajności symboli.

jlecour
źródło