web-dev-qa-db-fra.com

Pourquoi utiliser des symboles comme clés de hachage dans Ruby?

Souvent, les gens utilisent des symboles comme clés dans un hash Ruby.

Quel est l'avantage par rapport à l'utilisation d'une chaîne?

Par exemple.:

hash[:name]

vs.

hash['name']
153
user979912

TL; DR:

L’utilisation de symboles permet non seulement de gagner du temps lors des comparaisons, mais également d’économiser de la mémoire, car ils ne sont stockés qu’une fois.

Les symboles Ruby sont immuables (ne peuvent pas être modifiés), ce qui facilite grandement la recherche.

Réponse courte (ish):

L’utilisation de symboles permet non seulement de gagner du temps lors des comparaisons, mais également d’économiser de la mémoire, car ils ne sont stockés qu’une fois.

Les symboles dans Ruby sont en gros "chaînes immuables" .. cela signifie qu'ils ne peuvent pas être changés, et cela implique que Ce symbole, lorsqu'il est référencé plusieurs fois dans votre code source, est toujours stocké sous la même entité, par exemple a le même identifiant d'objet.

Les chaînes, en revanche, sont mutables, elles peuvent être changées à tout moment. Cela implique que Ruby doit stocker chaque chaîne mentionnée dans le code source dans une entité distincte, par exemple. si vous avez une chaîne "nom" mentionnée plusieurs fois dans votre code source, Ruby doit stocker tous ces éléments dans des objets String distincts, car ils pourraient changer ultérieurement (c'est la nature d'un Ruby chaîne).

Si vous utilisez une chaîne en tant que clé de hachage, Ruby doit évaluer la chaîne et examiner son contenu (et calculer une fonction de hachage sur celle-ci) et comparer le résultat aux valeurs (hachées) des clés sont déjà stockés dans le hachage.

Si vous utilisez un symbole comme clé de hachage, il est implicite qu'il soit immuable. Ruby peut donc simplement comparer la (fonction de hachage) de l'id d'objet à l'ID (de hachage) de clés déjà stockées dans le hachage. (Plus vite)

Inconvénient: Chaque symbole occupe un emplacement dans la table des symboles de l'interprète Ruby, qui n'est jamais libérée. Les symboles ne sont jamais ramassés. Un coin de cas se présente donc lorsque vous avez un grand nombre de symboles (par exemple, ceux générés automatiquement). Dans ce cas, vous devez évaluer l'impact sur la taille de votre interprète Ruby.

Notes:

Si vous effectuez des comparaisons de chaînes, Ruby peut comparer des symboles uniquement par leurs ID d'objet, sans avoir à les évaluer. C'est beaucoup plus rapide que de comparer des chaînes, qui doivent être évaluées.

Si vous accédez à un hachage, Ruby applique toujours une fonction de hachage pour calculer une "clé de hachage" à partir de la clé que vous utilisez. Vous pouvez imaginer quelque chose comme un hash MD5. Ensuite, Ruby compare ces "clés hachées" les unes aux autres.

Réponse longue:

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

211
Tilo

La raison en est l'efficacité, avec des gains multiples sur une chaîne:

  1. Les symboles sont immuables, alors la question "que se passe-t-il si la clé change?" n'a pas besoin d'être demandé.
  2. Les chaînes sont dupliquées dans votre code et prennent généralement plus d'espace mémoire.
  3. Les recherches de hachage doivent calculer le hachage des clés pour les comparer. Il s’agit de O(n) pour les chaînes et constante pour les symboles.

De plus, Ruby 1.9 a introduit une syntaxe simplifiée uniquement pour le hachage avec des clés de symboles (par exemple, h.merge(foo: 42, bar: 6)), et Ruby 2.0 a arguments mot-clé qui ne fonctionne que pour touches de symbole.

Notes :

1) Vous serez peut-être surpris d'apprendre que Ruby traite les clés String différemment des autres types. Effectivement:

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

Pour les clés de chaîne uniquement, Ruby utilisera une copie figée au lieu de l'objet lui-même.

2) Les lettres "b", "a" et "r" ne sont stockées qu'une seule fois pour toutes les occurrences de :bar Dans un programme. Avant Ruby 2.2, il était déconseillé de créer constamment de nouveaux Symbols qui ne seraient jamais réutilisés, car ils resteraient indéfiniment dans la table de recherche Symbol. Ruby 2.2 va les ramasser, alors pas d'inquiétude.

3) En fait, le calcul du hachage pour un symbole n'a pas pris de temps dans Ruby 1.8.x, car l'ID d'objet était utilisé directement:

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

Dans Ruby 1.9.x, cela a changé avec le changement de hachages d'une session à l'autre (y compris ceux de Symbols):

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

Re: quel est l'avantage par rapport à l'utilisation d'une chaîne?

  • Styling: c'est la façon rubis
  • (Très) des recherches de valeurs légèrement plus rapides car hacher un symbole équivaut à hacher un entier ou hacher une chaîne.

  • Inconvénient: consomme un emplacement dans la table des symboles du programme qui n'est jamais libéré.

7
Larry K

Je serais très intéressé par un suivi concernant les chaînes gelées introduites dans Ruby 2.x.

Lorsque vous traitez de nombreuses chaînes provenant d'une entrée de texte (je pense aux paramètres HTTP ou à la charge utile, via Rack, par exemple), il est beaucoup plus facile d'utiliser des chaînes partout.

Lorsque vous traitez avec des dizaines d’entre eux mais qu’ils ne changent jamais (s’il s’agit du "vocabulaire" de votre entreprise), j’aime penser que le fait de les geler peut faire la différence. Je n'ai pas encore fait de benchmark, mais j'imagine que ce serait une performance proche des symboles.

0
jlecour