Método de Ruby Array # << no está actualizando la matriz en hash

Inspirado por ¿Cómo puedo ordenar un hash con matrices? Me pregunto cuál es la razón por la que Array#<< no funcionará correctamente en el siguiente código:

 h = Hash.new{Array.new} #=> {} h[0] #=> [] h[0] < ["a"] h[0] #=> [] # why?! h[0] += ['a'] #=> ["a"] h[0] #=> ["a"] # as expected 

¿Tiene que ver con el hecho de que << cambia la matriz in situ, mientras que la Array#+ crea una nueva instancia?

Si crea un Hash usando la forma de bloque de Hash.new , el bloque se ejecuta cada vez que intenta acceder a un elemento que en realidad no existe. Entonces, miremos lo que sucede:

 h = Hash.new { [] } h[0] << 'a' 

Lo primero que se evalúa aquí, es la expresión

 h[0] 

¿Qué pasa cuando se evalúa? Bueno, el bloque se ejecuta:

 [] 

Eso no es muy emocionante: el bloque simplemente crea una matriz vacía y la devuelve. No hace nada más. En particular, no cambia h de ninguna manera: h todavía está vacío.

A continuación, el mensaje << con un argumento 'a' se envía al resultado de h[0] que es el resultado del bloque, que es simplemente una matriz vacía:

 [] << 'a' 

¿Qué hace esto? Agrega el elemento 'a' a una matriz vacía, pero como la matriz no se asigna a ninguna variable, se recolecta inmediatamente la basura y desaparece.

Ahora, si evalúa h[0] nuevamente:

 h[0] # => [] 

h todavía está vacío, ya que nunca se le asignó nada, por lo tanto, la clave 0 sigue sin existir, lo que significa que el bloque vuelve a ejecutarse, lo que significa que devuelve una matriz vacía (pero tenga en cuenta que es completamente nueva, diferente matriz vacía ahora).

 h[0] += ['a'] 

¿Qué pasa aquí? Primero, la asignación del operador se desuga a

 h[0] = h[0] + ['a'] 

Ahora, se evalúa la h[0] en el lado derecho . ¿Y qué devuelve? Ya hemos revisado esto: h[0] no existe, por lo tanto, el bloque se ejecuta, el bloque devuelve una matriz vacía. De nuevo, esta es una tercera matriz vacía completamente nueva ahora. Este conjunto vacío recibe el mensaje + con el argumento ['a'] , lo que hace que devuelva otro conjunto nuevo que es el conjunto ['a'] . Esta matriz se asigna a h[0] .

Por último, en este punto:

 h[0] # => ['a'] 

Ahora finalmente has puesto algo en h[0] así que, obviamente, sacas lo que pones.

Entonces, para responder a la pregunta que probablemente tenías, ¿por qué no sacas lo que pusiste? ¡ No pusiste nada en primer lugar!

Si realmente desea asignar el hash dentro del bloque, debe asignar el hash dentro del bloque:

 h = Hash.new {|this_hash, nonexistent_key| this_hash[nonexistent_key] = [] } h[0] << 'a' h[0] # => ['a'] 

En realidad, es bastante fácil ver lo que está sucediendo en el ejemplo del código, si observa las identidades de los objetos involucrados. Entonces puede ver que cada vez que llama a h[0] , obtiene una matriz diferente .

El problema en su código es que h[0] << 'a' crea una nueva matriz y la da cuando indexa con h[0] , pero no almacena la matriz modificada en cualquier lugar después de la << 'a' porque no hay asignación

Mientras tanto h[0] += ['a'] funciona porque es equivalente a h[0] = h[0] + ['a'] . Es la tarea ( []= ) la que hace la diferencia.

El primer caso puede parecer confuso, pero es útil cuando solo desea recibir un elemento predeterminado inmutable de un Hash cuando no se encuentra la clave. De lo contrario, podría terminar llenando el Hash con una gran cantidad de valores sin usar con solo indexarlo.

 h = Hash.new{ |a,b| a[b] = Array.new } h[0] << "hello world" #=> ["hello world"] h[0] #=> ["hello world"]