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

162

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

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

Par exemple:

hash[:name]

contre.

hash['name']
Max
la source

Réponses:

227

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 seule fois.

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

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 seule fois.

Les symboles dans Ruby sont essentiellement des "chaînes immuables" .. cela signifie qu'ils ne peuvent pas être modifiés, et cela implique que le même 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 modifiées à tout moment. Cela implique que Ruby doit stocker chaque chaîne que vous mentionnez dans votre code source dans son entité distincte, par exemple si vous avez une chaîne "nom" plusieurs fois mentionnée dans votre code source, Ruby doit toutes les stocker dans des objets String séparés, car ils peut changer plus tard (c'est la nature d'une chaîne Ruby).

Si vous utilisez une chaîne comme clé de hachage, Ruby doit évaluer la chaîne et regarder son contenu (et calculer une fonction de hachage à ce sujet) et comparer le résultat aux valeurs (hachées) des clés qui sont déjà stockées dans le hachage. .

Si vous utilisez un symbole comme clé de hachage, il est implicite qu'il est immuable, donc Ruby peut simplement faire une comparaison de la (fonction de hachage de) object-id avec les object-ids (hachés) des clés qui sont déjà stockées dans le Hash. (Plus vite)

Inconvénient: chaque symbole consomme un emplacement dans la table des symboles de l'interpréteur Ruby, qui n'est jamais libéré. Les symboles ne sont jamais récupérés. Ainsi, un cas d'angle est lorsque vous avez un grand nombre de symboles (par exemple, ceux générés automatiquement). Dans ce cas, vous devez évaluer comment cela affecte la taille de votre interpréteur Ruby.

Remarques:

Si vous faites des comparaisons de chaînes, Ruby peut comparer des symboles uniquement par leurs identifiants 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 hachage MD5. Et puis Ruby compare ces "clés hachées" les unes aux autres.

Longue réponse:

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
la source
5
Pour info, les symboles seront GCd dans la prochaine version de Ruby: bugs.ruby-lang.org/issues/9634
Ajedi32
2
En outre, les chaînes sont automatiquement gelées lorsqu'elles sont utilisées comme clés de hachage dans Ruby. Ce n'est donc pas tout à fait vrai que les chaînes sont mutables lorsqu'on en parle dans ce contexte.
Ajedi32
1
Excellent aperçu du sujet et le premier lien de la section "Réponse longue" est supprimé ou migré.
Hbksagar
2
Les symboles sont ramassés dans Ruby 2.2
Marc-André Lafortune
2
Très bonne réponse! Du côté de la pêche à la traîne, votre «réponse courte» est également assez longue. ;)
technophyle
22

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

  1. Les symboles sont immuables, donc 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 en mémoire.
  3. Les recherches de hachage doivent calculer le hachage des clés pour les comparer. Ceci est O(n)pour les chaînes et constante pour les symboles.

De plus, Ruby 1.9 a introduit une syntaxe simplifiée juste pour le hachage avec des clés de symboles (par exemple h.merge(foo: 42, bar: 6)), et Ruby 2.0 a des arguments de mots clés qui ne fonctionnent que pour les clés de symboles.

Remarques :

1) Vous pourriez être surpris d'apprendre que Ruby traite les Stringclés différemment de tout autre type. En effet:

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 :bardans un programme. Avant Ruby 2.2, c'était une mauvaise idée de créer constamment de nouveaux Symbolsqui n'étaient jamais réutilisés, car ils resteraient pour toujours dans la table de recherche globale de Symbol. Ruby 2.2 les ramassera, donc pas de soucis.

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é à mesure que les hachages changent 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
Marc-André Lafortune
la source
+1 pour vos excellentes notes! À l'origine, je n'ai pas mentionné la fonction de hachage dans ma réponse, car j'ai essayé de la rendre plus facile à lire :)
Tilo
@Tilo: en effet, c'est pourquoi j'ai écrit ma réponse :-) Je viens d'éditer ma réponse pour mentionner la syntaxe spéciale de Ruby 1.9 et les paramètres nommés promis de Ruby 2.0
Marc-André Lafortune
Pouvez-vous expliquer comment les recherches de hachage sont constantes pour les symboles et O (n) pour les chaînes?
Asad Moosvi
7

Re: quel est l'avantage d'utiliser une chaîne?

  • Styling: c'est le Ruby-way
  • Des recherches de valeurs (très) légèrement plus rapides car le hachage d'un symbole équivaut au hachage d'un entier par rapport au hachage d'une chaîne.

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

Larry K
la source
4
+1 pour avoir mentionné que le symbole n'est jamais ramassé.
Vortico
le symbole n'est jamais ramassé - pas vrai depuis ruby ​​2.2+
eudaimonia le
0

Je serais très intéressé par un suivi concernant les chaînes figé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'ils sont le «vocabulaire» de votre entreprise), j'aime à penser que les geler peut faire une différence. Je n'ai pas encore fait de benchmark, mais je suppose que ce serait proche de la performance des symboles.

jlecour
la source