2010-03-21 25 views
5

tengo código:TypeError: no se puede convertir la serie en Entero

class Scene 
    def initialize(number) 
    @number = number 
    end 
    attr_reader :number 
end 

scenes = [Scene.new("one"), Scene.new("one"), Scene.new("two"), Scene.new("one")] 

groups = scenes.inject({}) do |new_hash, scene| 
    new_hash[scene.number] = [] if new_hash[scene.number].nil? 
    new_hash[scene.number] << scene 
end 

Cuando estoy lauching que me sale error:

freq.rb:11:in `[]': can't convert String into Integer (TypeError) 
     from freq.rb:11:in `block in <main>' 
     from freq.rb:10:in `each' 
     from freq.rb:10:in `inject' 
     from freq.rb:10:in `<main>' 

Si cambio de escenas a:

scenes = [Scene.new(1), Scene.new(1), Scene.new(2), Scene.new(1)] 

el problema desapareció.

¿Por qué aparece el mensaje de error en el primer caso? ¿Por qué Ruby decide convertir scene.number de String a Integer?

Y una pregunta adicional sobre el método 'inyectar'. Cuando Ruby inicializa la variable 'new_hash' y ¿cómo puede saber Ruby el tipo de esta variable?

+1

Btw: puedes hacer lo que quieras más fácilmente usando 'groups = scenes.group_by (&: number)' en lugar de inject. – sepp2k

+0

La variable new_hash se inicializa con el valor inyectado (tenga en cuenta: es una operación de plegado que tiene un valor de inicio en el acumulador) – hurikhan77

Respuesta

6

Z.E.D. Está bien. Ver Jay Fields' Thoughts: Ruby: inject para una buena explicación de inject por ejemplo.

Como se muestra, su bloque devuelve una matriz. Así que el new_hash en |new_hash, scene| termina siendo esa matriz. Cuando Ruby intenta encontrar el índice de matriz 'uno', arroja el error porque 'uno' es una Cadena, no un Entero.

Todo lo que tiene que hacer es devolver new_hash como Z.E.D. mostró, y obtendrá algo como esto:

{ 
    "two" => [ 
    #<Scene:0x101836470 @number="two"> 
    ], 
    "one" => [ 
    #<Scene:0x101836510 @number="one">, 
    #<Scene:0x1018364c0 @number="one">, 
    #<Scene:0x101836420 @number="one"> 
    ] 
} 
10

intento:

 
groups = scenes.inject({}) do |new_hash, scene| 
    new_hash[scene.number] = [] if new_hash[scene.number].nil? 
    new_hash[scene.number] << scene 
    new_hash 
end 

Rubí toma el hash vacío pasado a inyectar() y conjuntos new_hash a eso. Cuando termina el bloque, el valor de retorno se usa para inicializar new_hash la próxima vez, es decir, new_hash sigue acumulando el resultado del bloque.

En el código original no devolvía el hash pero una matriz (new_hash [scene.number] es una matriz) y el siguiente ciclo a través de Ruby se quejaba porque new_hash [scene.number] intentaba buscar en el array con un valor de cadena, de ahí el error que obtuviste.

+1

¿Por qué se modificó este valor?Esta es la respuesta correcta. – sepp2k

+0

porque falló – shingara

+0

@shingara: No, no falla. Su código se ejecuta sin error y devuelve el resultado esperado. – sepp2k

0

Además, para explicar 'cómo puede saber Ruby el tipo de esta variable' y por qué trata de 'convertir cadenas en enteros', es posible que desee revisar: Ruby variables and dynamic typing.

2

Por qué no usar group_by que es probablemente exactamente lo que intenta accomblish?

groups = scenes.group_by(&:number) 
# => {"two"=>[#<Scene:0xb728ade0 @number="two">], 
#  "one"=> 
#  [#<Scene:0xb728ae30 @number="one">, 
#  #<Scene:0xb728ae08 @number="one">, 
#  #<Scene:0xb728ada4 @number="one">]} 

inject es una operación de plegado y no exactamente lo que quiere. Al menos es engorroso usar de esta manera. merge con un bloque probablemente sería apropiado si desea aplicar algún algoritmo durante la fusión o agrupamiento.

0

Sé que se acepta una respuesta para esta pregunta, pero no puedo evitar publicar mi respuesta.

groups = scenes.inject({}) { |nh, s| nh.tap {|h| (h[s.number] ||= []) << s } } 
Cuestiones relacionadas