Primero, tenga en cuenta que este comportamiento se aplica a cualquier valor predeterminado que se mute posteriormente (por ejemplo, hashes y cadenas), no solo a las matrices.
TL; DR : Úselo Hash.new { |h, k| h[k] = [] }
si desea la solución más idiomática y no le importa por qué.
Lo que no funciona
Porque Hash.new([])
no funciona
Veamos más en profundidad por qué Hash.new([])
no funciona:
h = Hash.new([])
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["a", "b"]
h[1] #=> ["a", "b"]
h[0].object_id == h[1].object_id #=> true
h #=> {}
Podemos ver que nuestro objeto predeterminado se está reutilizando y mutando (esto se debe a que se pasa como el único valor predeterminado, el hash no tiene forma de obtener un nuevo valor predeterminado nuevo), pero ¿por qué no hay claves o valores? en la matriz, a pesar de que h[1]
todavía nos da un valor? Aquí hay una pista:
h[42] #=> ["a", "b"]
La matriz devuelta por cada []
llamada es solo el valor predeterminado, que hemos estado mutando todo este tiempo, por lo que ahora contiene nuestros nuevos valores. Dado <<
que no se asigna al hash (nunca puede haber asignación en Ruby sin un =
regalo † ), nunca hemos puesto nada en nuestro hash real. En su lugar, tenemos que usar <<=
(que es <<
como +=
es +
):
h[2] <<= 'c' #=> ["a", "b", "c"]
h #=> {2=>["a", "b", "c"]}
Esto es lo mismo que:
h[2] = (h[2] << 'c')
Porque Hash.new { [] }
no funciona
Usar Hash.new { [] }
resuelve el problema de reutilizar y mutar el valor predeterminado original (como se llama al bloque dado cada vez, devolviendo una nueva matriz), pero no el problema de asignación:
h = Hash.new { [] }
h[0] << 'a' #=> ["a"]
h[1] <<= 'b' #=> ["b"]
h #=> {1=>["b"]}
Que funciona
La forma de asignación
Si recordamos usar siempre <<=
, entonces Hash.new { [] }
es una solución viable, pero es un poco extraña y no idiomática (nunca la he visto <<=
usada en la naturaleza). También es propenso a errores sutiles si <<
se usa inadvertidamente.
La forma mutable
los documentación de losHash.new
estados (el énfasis es mío):
Si se especifica un bloque, se llamará con el objeto hash y la clave, y debe devolver el valor predeterminado. Es responsabilidad del bloque almacenar el valor en el hash si es necesario .
Así que debemos almacenar el valor predeterminado en el hash desde dentro del bloque si deseamos usar <<
lugar de <<=
:
h = Hash.new { |h, k| h[k] = [] }
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["b"]
h #=> {0=>["a"], 1=>["b"]}
Esto efectivamente mueve la asignación de nuestras llamadas individuales (que usarían <<=
) al bloque pasado Hash.new
, eliminando la carga del comportamiento inesperado al usar <<
.
Tenga en cuenta que hay una diferencia funcional entre este método y los demás: de esta manera asigna el valor predeterminado al leer (ya que la asignación siempre ocurre dentro del bloque). Por ejemplo:
h1 = Hash.new { |h, k| h[k] = [] }
h1[:x]
h1 #=> {:x=>[]}
h2 = Hash.new { [] }
h2[:x]
h2 #=> {}
El camino inmutable
Quizás se pregunte por qué Hash.new([])
no funciona mientras Hash.new(0)
funciona bien. La clave es que los números numéricos en Ruby son inmutables, por lo que, naturalmente, nunca terminamos por mutarlos en el lugar. Si tratamos nuestro valor predeterminado como inmutable, también podríamos usarlo Hash.new([])
bien:
h = Hash.new([].freeze)
h[0] += ['a'] #=> ["a"]
h[1] += ['b'] #=> ["b"]
h[2] #=> []
h #=> {0=>["a"], 1=>["b"]}
Sin embargo, tenga en cuenta que ([].freeze + [].freeze).frozen? == false
. Por lo tanto, si desea asegurarse de que la inmutabilidad se mantenga en todo momento, debe tener cuidado de volver a congelar el nuevo objeto.
Conclusión
De todas las formas, personalmente prefiero “la forma inmutable”; la inmutabilidad generalmente hace que el razonamiento sobre las cosas sea mucho más simple. Después de todo, es el único método que no tiene posibilidad de un comportamiento inesperado oculto o sutil. Sin embargo, la forma más común e idiomática es "la forma mutable".
Como comentario final, este comportamiento de los valores predeterminados de Hash se observa en Ruby Koans .
† Esto no es estrictamente cierto, métodos como instance_variable_set
eludir esto, pero deben existir para la metaprogramación ya que el valor l de =
no puede ser dinámico.
{ [] }
con<<=
tiene la menor cantidad de sorpresas, si no fuera por el hecho de que olvidarlo accidentalmente=
podría llevar a una sesión de depuración muy confusa.Está especificando que el valor predeterminado para el hash es una referencia a esa matriz en particular (inicialmente vacía).
Creo que quieres:
Eso establece el valor predeterminado para cada clave en una nueva matriz.
fuente
Array
instancias en cada invocación. A saber:h = Hash.new { |hash, key| hash[key] = []; puts hash[key].object_id }; h[1] # => 16348490; h[2] # => 16346570
. Además: si usa la versión de bloque que establece el valor ({|hash,key| hash[key] = []}
) en lugar de la que simplemente genera el valor ({ [] }
), entonces solo necesita<<
, no<<=
al agregar elementos.El operador
+=
cuando se aplica a esos hashes funciona como se esperaba.Esto puede deberse a que
foo[bar]+=baz
es un azúcar sintáctico porquefoo[bar]=foo[bar]+baz
cuando se evalúafoo[bar]
a la derecha de=
, devuelve el objeto de valor predeterminado y el+
operador no lo cambiará. La mano izquierda es azúcar sintáctica para el[]=
método que no cambiará el valor predeterminado .Tenga en cuenta que esto no se aplica a
foo[bar]<<=baz
lo que va a ser equivalente afoo[bar]=foo[bar]<<baz
, y<<
será cambiar el valor por defecto .Además, no encontré ninguna diferencia entre
Hash.new{[]}
yHash.new{|hash, key| hash[key]=[];}
. Al menos en ruby 2.1.2.fuente
Hash.new{[]}
es lo mismo queHash.new([])
para mí con la falta del<<
comportamiento esperado (aunque, por supuesto,Hash.new{|hash, key| hash[key]=[];}
funciona). Cosas pequeñas extrañas que rompen todas las cosas: /Cuando escribes,
pasa la referencia predeterminada de la matriz a todos los elementos en hash. por eso todos los elementos en hash se refieren a la misma matriz.
si desea que cada elemento en hash se refiera a una matriz separada, debe usar
para obtener más detalles sobre cómo funciona en ruby, consulte esto: http://ruby-doc.org/core-2.2.0/Array.html#method-c-new
fuente
Hash.new { [] }
no no funciona. Vea mi respuesta para más detalles. También es ya la solución propuesta en otra respuesta.