¿Por qué usar símbolos como claves hash en Ruby?

161

Muchas veces las personas usan símbolos como claves en un hash Ruby.

¿Cuál es la ventaja sobre el uso de una cadena?

P.ej:

hash[:name]

vs.

hash['name']
Max
fuente

Respuestas:

226

TL; DR:

El uso de símbolos no solo ahorra tiempo al hacer comparaciones, sino que también ahorra memoria, ya que solo se almacenan una vez.

Los símbolos de rubí son inmutables (no se pueden cambiar), lo que hace que buscar algo sea mucho más fácil

Respuesta corta (ish):

El uso de símbolos no solo ahorra tiempo al hacer comparaciones, sino que también ahorra memoria, ya que solo se almacenan una vez.

Los símbolos en Ruby son básicamente "cadenas inmutables" ... eso significa que no se pueden cambiar, e implica que el mismo símbolo cuando se hace referencia muchas veces a lo largo de su código fuente, siempre se almacena como la misma entidad, por ejemplo, tiene la misma identificación de objeto .

Las cadenas, por otro lado, son mutables , se pueden cambiar en cualquier momento. Esto implica que Ruby necesita almacenar cada cadena que menciona a lo largo de su código fuente en su entidad separada, por ejemplo, si tiene un "nombre" de cadena mencionado varias veces en su código fuente, Ruby necesita almacenar todo esto en objetos de cadena separados, porque podría cambiar más adelante (esa es la naturaleza de una cadena Ruby).

Si usa una cadena como una clave Hash, Ruby necesita evaluar la cadena y mirar su contenido (y calcular una función hash en eso) y comparar el resultado con los valores (hash) de las claves que ya están almacenadas en el Hash .

Si usa un símbolo como una clave Hash, está implícito que es inmutable, por lo que Ruby básicamente puede hacer una comparación de la (función hash del) id-objeto contra los (id. Hash) de las claves que ya están almacenadas en el hachís. (mucho mas rápido)

Desventaja: cada símbolo consume un espacio en la tabla de símbolos del intérprete de Ruby, que nunca se libera. Los símbolos nunca se recogen basura. Entonces, un caso de esquina es cuando tiene una gran cantidad de símbolos (por ejemplo, los generados automáticamente). En ese caso, debe evaluar cómo esto afecta el tamaño de su intérprete de Ruby.

Notas:

Si hace comparaciones de cadenas, Ruby puede comparar símbolos solo por sus identificadores de objeto, sin tener que evaluarlos. Eso es mucho más rápido que comparar cadenas, que deben evaluarse.

Si accede a un hash, Ruby siempre aplica una función hash para calcular una "clave hash" a partir de cualquier tecla que utilice. Puedes imaginar algo como un MD5-hash. Y luego Ruby compara esas "claves hash" entre sí.

Respuesta larga:

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
fuente
55
Fyi, Symbols será GCd en la próxima versión de Ruby: bugs.ruby-lang.org/issues/9634
Ajedi32
2
Además, las cadenas se congelan automáticamente cuando se usan como claves Hash en Ruby. Entonces, no es exactamente cierto que las cadenas son mutables cuando se habla de ellas en este contexto.
Ajedi32
1
Se eliminó o migró una gran información sobre el tema y el primer enlace en la sección "Respuesta larga".
Hbksagar
2
Los símbolos son basura recolectada en Ruby 2.2
Marc-André Lafortune
2
¡Gran respuesta! Por el lado de los curricán, su "respuesta corta" también es lo suficientemente larga. ;)
technophyle
22

La razón es la eficiencia, con múltiples ganancias sobre una cadena:

  1. Los símbolos son inmutables, por lo que la pregunta "¿qué sucede si la clave cambia?" no necesita ser preguntado
  2. Las cadenas están duplicadas en su código y, por lo general, ocuparán más espacio en la memoria.
  3. Las búsquedas de hash deben calcular el hash de las claves para compararlas. Esto es O(n)para cadenas y constante para símbolos.

Además, Ruby 1.9 introdujo una sintaxis simplificada solo para hash con teclas de símbolos (por ejemplo h.merge(foo: 42, bar: 6)), y Ruby 2.0 tiene argumentos de palabras clave que funcionan solo para teclas de símbolos.

Notas :

1) Es posible que se sorprenda al saber que Ruby trata las Stringclaves de manera diferente que cualquier otro tipo. En efecto:

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 para claves de cadena, Ruby usará una copia congelada en lugar del objeto en sí.

2) Las letras "b", "a" y "r" se almacenan solo una vez para todas las apariciones de :barun programa. Antes de Ruby 2.2, era una mala idea crear constantemente nuevos Symbolsque nunca se reutilizaran, ya que permanecerían en la tabla de búsqueda de símbolos global para siempre. Ruby 2.2 recogerá basura, así que no te preocupes.

3) En realidad, calcular el hash para un Símbolo no tardó en Ruby 1.8.x, ya que la ID del objeto se usó directamente:

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

En Ruby 1.9.x, esto ha cambiado a medida que los hash cambian de una sesión a otra (incluidas las de Symbols):

:bar.hash # => some number that will be different next time Ruby 1.9 is ran
Marc-André Lafortune
fuente
¡+1 por tus excelentes notas! Originalmente no mencioné la función hash en mi respuesta, porque intenté que fuera más fácil de leer :)
Tilo
@Tilo: de hecho, por eso escribí mi respuesta :-) Acabo de editar mi respuesta para mencionar la sintaxis especial en Ruby 1.9 y los parámetros nombrados prometidos de Ruby 2.0
Marc-André Lafortune
¿Puedes explicar cómo las búsquedas de Hash son constantes para los símbolos y O (n) para las cadenas?
Asad Moosvi
7

Re: ¿cuál es la ventaja sobre el uso de una cadena?

  • Estilo: es el camino de rubí
  • Búsqueda de valores (muy) ligeramente más rápidos, ya que el hashing de un símbolo es equivalente a un hash de un entero vs un hash de una cadena.

  • Desventaja: consume un espacio en la tabla de símbolos del programa que nunca se publica.

Larry K
fuente
44
+1 por mencionar que el símbolo nunca es basura recolectada.
Vortico
el símbolo nunca es basura recolectada - no es cierto desde ruby ​​2.2+
eudaimonia
0

Estaría muy interesado en un seguimiento sobre cadenas congeladas introducidas en Ruby 2.x.

Cuando maneja numerosas cadenas que provienen de una entrada de texto (estoy pensando en parámetros HTTP o carga útil, a través de Rack, por ejemplo), es mucho más fácil usar cadenas en todas partes.

Cuando tratas con docenas de ellos pero nunca cambian (si son el "vocabulario" de tu negocio), me gusta pensar que congelarlos puede marcar la diferencia. Todavía no he hecho ningún punto de referencia, pero supongo que estaría cerca del rendimiento de los símbolos.

jlecour
fuente