2012-06-30 25 views
38

entiendo que con el fin de resumir los elementos de matriz en Ruby se puede utilizar el método de inyección, es decirCómo sumar propiedades de los objetos dentro de una matriz en Ruby

array = [1,2,3,4,5]; 
puts array.inject(0, &:+) 

Pero ¿cómo resumir las propiedades de los objetos dentro de una matriz de objetos, por ejemplo?

Hay una variedad de objetos y cada objeto tiene una propiedad "efectivo", por ejemplo. Entonces quiero sumar sus saldos de efectivo en un total. Algo así como ...

array.cash.inject(0, &:+) # (but this doesn't work) 

Soy consciente de que probablemente podría hacer una nueva matriz compuesta sólo por el dinero en efectivo propiedad y resumir esto, pero estoy buscando un método más limpio si es posible!

Respuesta

50
array.map(&:cash).inject(0, &:+) 

o

array.inject(0){|sum,e| sum + e.cash } 
+0

perfecto gracias! –

+3

Esto pasa por 'array' dos veces, lo que puede no ser aconsejable si hay muchos elementos.¿Por qué no usar un bloque apropiado para 'inyectar'? También 'reduce/inyectar' directamente toma un argumento de símbolo, no es necesario' Symbol # to_proc' :-) –

+0

tenga en cuenta que no necesita enviar un bloque, 'inject' sabe qué hacer con un símbolo:' inject (0,: +) ' – tokland

8

#reduce toma un bloque (el &:+ es un acceso directo para crear un proc/bloque que hace +). Esta es una forma de hacer lo que quiere:

array.reduce(0) { |sum, obj| sum + obj.cash } 
+2

' # reduce' es un alias para '# inject' en 1.9+, por cierto. – Theo

+0

+1 para no iterar sobre 'array' dos veces. El alias también está allí en 1.8.7 por cierto. –

+1

, ya que Michael dice que es más eficiente en el uso del espacio que map + reduce, pero a costa de la modularidad (en este caso, es pequeño, no hace falta decirlo). En Ruby 2.0 podemos tener ambas cosas gracias a la pereza: 'array.lazy.map (&: cash) .reduce (0,: +)'. – tokland

1

No hay necesidad de utilizar inicial en inyectar y más operación puede ser más corto

array.map(&:cash).inject(:+) 
+3

Tiene razón sobre el argumento de símbolo, pero si 'array' puede estar vacío, quiere el argumento:' [] .inject (: +) # => nil', '[] .inject (0,: +) # => 0' a menos que desee tratar el 'nil' por separado. –

+0

Buen punto, no lo pensé. – megas

36

También puede intentar:

array.sum(&:cash)

Es un atajo para el negocio de las inyecciones y me parece más legible.
http://api.rubyonrails.org/classes/Enumerable.html

+3

Si está utilizando Rails, este es el camino a seguir. – Dennis

+0

Tenga en cuenta que si su matriz es el resultado de algún tipo de filtrado en un objeto ActiveRecord, p. '@orders = Order.all; @ orders.select {| o | o.status == 'paid'} .sum (&: cost) ', luego también puede obtener el mismo resultado con una consulta:' @ orders.where (status:: paid) .sum (: cost) '. – Dennis

+0

Si los registros no están almacenados en el DB, la suma será 0, donde inyectaría funcionaría. – dgmora

2

manera más concisa:

array.map(&:cash).sum 

Si la matriz resultante del mapa tiene elementos nulos:

array.map(&:cash).compact.sum 
0

Si el valor de inicio para la suma es 0, entonces resumir por sí sola es idéntica para inyectar: ​​

array.map(&:cash).sum 

Y Preferiría la versión de bloque:

array.sum { |a| a.cash } 

Porque el símbolo de Proc a menudo es demasiado limitado (sin parámetros, etc.).

(necesita Active_Support)

0

Aquí algunos puntos de referencia interesantes

array = Array.new(1000) { OpenStruct.new(property: rand(1000)) } 

Benchmark.ips do |x| 
    x.report('map.sum') { array.map(&:property).sum } 
    x.report('inject(0)') { array.inject(0) { |sum, x| sum + x.property } } 
    x.compare! 
end 

y resultados

Calculating ------------------------------------- 
      map.sum 249.000 i/100ms 
      inject(0) 268.000 i/100ms 
------------------------------------------------- 
      map.sum  2.947k (± 5.1%) i/s -  14.691k 
      inject(0)  3.089k (± 5.4%) i/s -  15.544k 

Comparison: 
      inject(0):  3088.9 i/s 
      map.sum:  2947.5 i/s - 1.05x slower 

Como se puede ver inyectar un poco más rápido

Cuestiones relacionadas