2010-11-30 7 views
5

¿Por qué el siguiente código emite un error?¿Por qué el método de inyección de Ruby no puede resumir las longitudes de cadena sin el valor inicial?

['hello','stack','overflow'].inject{|memo,s|memo+s.length} 

TypeError: can't convert Fixnum into String 
     from (irb):2:in `+' 
     from (irb):2:in `block in irb_binding' 
     from (irb):2:in `each' 
     from (irb):2:in `inject' 
     from (irb):2 

Si se pasa el valor inicial, funciona bien:

['hello','stack','overflow'].inject(0){|memo,s|memo+s.length} 
=> 18 
+1

puedo encontrar la siguiente sintaxis muy agradable '[ 'hola', 'pila', 'desbordamiento']. Mapa (y : length) .inject (&: +) 'pero ciertamente no es relevante para su pregunta. –

+1

@Jonas, sí, eso es genial. Y como la reducción es una sumatoria, podemos escribir: ['hola', 'pila', 'desbordamiento'], mapa (&: longitud) .sum – tokland

+1

Sí, si está utilizando http://api.rubyonrails.org/classes /Enumerable.html puedes Hasta donde yo sé, no hay 'sum' en Ruby estándar. –

Respuesta

16

Usted tiene la respuesta en apidock:

Si no se especifica explícitamente un valor inicial para la nota, luego usa el primer elemento de colección que se usa como el valor inicial de la nota.

es decir, sin un valor inicial, que estamos tratando de hacer 'hello' + 'stack'.length

+0

Gracias! ¡Directo al grano! –

3

Sin el valor inicial, inject utiliza el primer elemento de la colección como el valor inicial.

ver ruby-doc.

6

Como el mensaje de error ya le dice, el problema es que tiene un TypeError. El hecho de que Ruby se escriba de manera dinámica e implícita no significa que no tenga que pensar en los tipos.

El tipo de Enumerable#inject sin un acumulador explícita (esto generalmente se llama reduce) es algo así como

reduce :: [a] → (a → a → a) → a 

o en una notación más Rubyish acabo de componer

Enumerable[A]#inject {|A, A| A } → A 

Usted notará que todos los tipos son iguales El tipo de elemento del Enumerable, los dos tipos de argumento del bloque, el tipo de retorno del bloque y el tipo de retorno del método general.

En su caso, los tipos para el bloque simplemente no suman. El bloque pasa dos String sy se supone que devuelve un String. Pero llama al método + en el primer argumento (que es un String) con un argumento que es Integer. Pero String#+ no toma Integer solo toma String o más precisamente algo que puede ser convertido a String, es decir, algo que responde a #to_str. Es por eso que obtienes un TypeError por String#+.

El tipo de Enumerable#injectcon un acumulador explícita (esto generalmente se llama fold) es algo así como

fold :: [b] → a → (a → b → a) → a 

o

Aquí vemos que el acumulador puede tener un diferente tipo que el tipo de elemento de la colección. Que es precisamente lo que necesitas.

Estas dos reglas generalmente obtienen a través de todos los problemas relacionados con la PI Enumerable#inject:

  1. del tipo de acumulador y el tipo de retorno del bloque debe ser el mismo
  2. cuando no está pasando un acumulador explícita, la tipo de acumulador es el mismo que el tipo de elemento

Regla # 1 será lo más a menudo que morder cuando haces algo como

acc[key] = value 

en su bloque, porque las asignaciones evalúan el valor asignado, no el receptor de la asignación. Tendrá que reemplazar esto con

acc.tap { acc[key] = value } 

En su caso particular, las dos soluciones ya se han mencionado. O bien utilizar un acumulador explícita

ary.reduce(0){|acc, e| acc + e.length } 

o convertir a enteros primeros

ary.map(&:length).reduce(:+) 
+1

@Andrew Grimm: Thnaks fro the speling fxies :-) –

+0

¡Gracias por una respuesta tan detallada! –

Cuestiones relacionadas