Perché usare i simboli come chiavi di hash in Ruby?

Molte volte le persone usano i simboli come chiavi in ​​un hash di Ruby.

Qual è il vantaggio rispetto all’utilizzo di una stringa?

Per esempio:

hash[:name] 

vs.

 hash['name'] 

TL; DR:

L’uso dei simboli non solo consente di risparmiare tempo durante i confronti, ma salva anche la memoria, perché vengono memorizzati solo una volta.

I simboli Ruby sono immutabili (non possono essere modificati), il che rende molto più semplice cercare qualcosa

Risposta breve (ish):

L’uso dei simboli non solo consente di risparmiare tempo durante i confronti, ma salva anche la memoria, perché vengono memorizzati solo una volta.

I simboli in Ruby sono fondamentalmente “stringhe immutabili” . Ciò significa che non possono essere modificati e implica che lo stesso simbolo quando viene fatto riferimento più volte al codice sorgente, viene sempre memorizzato come la stessa entity framework, ad esempio ha lo stesso ID object .

Le stringhe d’altra parte sono mutevoli , possono essere modificate in qualsiasi momento. Ciò implica che Ruby deve memorizzare ogni stringa menzionata in tutto il codice sorgente nella sua quadro separata, ad esempio se hai un “nome” di stringa più volte menzionato nel tuo codice sorgente, Ruby ha bisogno di memorizzarli tutti in oggetti String separati, perché potrebbe cambiare in seguito (questa è la natura di una stringa di Ruby).

Se si utilizza una stringa come chiave hash, Ruby deve valutare la stringa e controllarne il contenuto (e calcolare una funzione di hash su di essa) e confrontare il risultato con i valori (hash) delle chiavi che sono già memorizzati nell’hash .

Se si usa un simbolo come chiave hash, è implicito che è immutabile, quindi Ruby può fondamentalmente fare solo un confronto tra la (funzione hash di) id-object contro l’object-hash (hash) delle chiavi che sono già memorizzate in l’hash. (più veloce)

Lato negativo: ogni simbolo consuma uno spazio nella tabella dei simboli dell’interprete Ruby, che non viene mai rilasciato. I simboli non vengono mai raccolti dalla spazzatura. Quindi un caso d’angolo è quando hai un gran numero di simboli (ad esempio quelli generati automaticamente). In questo caso dovresti valutare come questo influisce sulla dimensione dell’interprete Ruby.

Gli appunti:

Se fai confronti con le stringhe, Ruby può confrontare i simboli solo con i loro ID object, senza doverli valutare. È molto più veloce rispetto al confronto di stringhe, che devono essere valutate.

Se accedi a un hash, Ruby applica sempre una funzione hash per calcolare una “chiave hash” da qualunque chiave tu usi. Puoi immaginare qualcosa come un MD5-hash. E poi Ruby confronta quelle “chiavi con l’hash” l’una contro l’altra.

Risposta lunga:

http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings

http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol

Il motivo è l’efficienza, con più guadagni su una stringa:

  1. I simboli sono immutabili, quindi la domanda “cosa succede se la chiave cambia?” non ha bisogno di essere chiesto.
  2. Le stringhe sono duplicate nel codice e in genere richiedono più spazio in memoria.
  3. Le ricerche hash devono calcolare l’hash delle chiavi per confrontarle. Questo è O(n) per le stringhe e costante per i simboli.

Inoltre, Ruby 1.9 ha introdotto una syntax semplificata solo per hash con le chiavi dei simboli (ad es. h.merge(foo: 42, bar: 6) ) e Ruby 2.0 ha argomenti di parole chiave che funzionano solo per le chiavi dei simboli.

Note :

1) Potresti essere sorpreso di apprendere che Ruby tratta le chiavi String diverso rispetto a qualsiasi altro tipo. Infatti:

 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 

Solo per le chiavi stringa, Ruby utilizzerà una copia congelata anziché l’object stesso.

2) Le lettere “b”, “a” e “r” sono memorizzate una sola volta per tutte le occorrenze di :bar in un programma. Prima di Ruby 2.2, era una ctriggers idea creare costantemente nuovi Symbols che non fossero mai stati riutilizzati, poiché rimarrebbero per sempre nella tabella di ricerca dei simboli globale. Ruby 2.2 li raccoglierà, quindi non preoccuparti.

3) In realtà, il calcolo dell’hash per un simbolo non ha richiesto alcun tempo in Ruby 1.8.x, poiché l’ID object è stato utilizzato direttamente:

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

In Ruby 1.9.x, questo è cambiato quando gli hash cambiano da una sessione all’altra (compresi quelli di Symbols ):

 :bar.hash # => some number that will be different next time Ruby 1.9 is ran 

Ri: qual è il vantaggio rispetto all’utilizzo di una stringa?

  • Styling: è il modo Ruby
  • (Molto) un po ‘più veloce del valore in quanto l’hashing di un simbolo equivale all’hashing di un numero intero rispetto all’hashing di una stringa.

  • Svantaggio: consuma uno spazio nella tabella dei simboli del programma che non viene mai rilasciato.

Sarei molto interessato a un follow-up riguardante le stringhe congelate introdotte in Ruby 2.x.

Quando si affrontano numerose stringhe provenienti da un input di testo (sto pensando a parametri HTTP o payload, ad esempio tramite Rack, è molto più semplice utilizzare le stringhe ovunque.

Quando ne hai a che fare con dozzine, ma non cambiano mai (se sono il tuo “vocabolario” aziendale), mi piace pensare che il congelamento possa fare la differenza. Non ho ancora fatto alcun punto di riferimento, ma suppongo che sarebbe vicino alle prestazioni dei simboli.