2012-06-30 4 views
38

Sono consapevole che per sommare elementi dell'array in rubino si può utilizzare il metodo di iniezione, cioèCome sommare proprietà degli oggetti all'interno di un array in Ruby

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

Ma come posso riassumere le proprietà degli oggetti all'interno di un array di oggetti, ad esempio?

C'è una matrice di oggetti e ogni oggetto ha una proprietà "contanti" per esempio. Quindi voglio riassumere i loro saldi di cassa in un totale. Qualcosa di simile ...

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

mi rendo conto che probabilmente potrebbe fare un nuovo array composto solo il denaro proprietà e riassumere questo, ma sto cercando un metodo più pulito, se possibile!

risposta

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

o

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

Perfetto, grazie! –

+3

Questo passaggio però su 'array', che potrebbe non essere consigliabile se ci sono molti elementi.Perché non usare semplicemente un blocco adeguato per "iniettare"? Anche 'reduce/inject' prende direttamente un argomento di simbolo, non c'è bisogno di' Symbol # to_proc' :-) –

+0

nota che non è necessario inviare un blocco, 'inject' sa cosa fare con un simbolo:' iniettare (0,: +) ' – tokland

8

#reduce guadagnato un blocco (il &:+ è una scorciatoia per creare un proc/blocco che fa +). Questo è un modo di fare ciò che si vuole:

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

' # reduce' è un alias per '# inject' in 1.9+, btw. – Theo

+0

+1 per non iterare su 'matrice' due volte. Lo pseudonimo è anche lì in 1.8.7 btw. –

+1

come dice Michael che è più efficiente sotto il profilo dello spazio che mappa + riduce, ma a costo di modularità (piccola in questo caso, non c'è bisogno di dirlo). In Ruby 2.0 possiamo avere entrambi grazie alla pigrizia: 'array.lazy.map (&: cash) .reduce (0,: +)'. – tokland

1

Non c'è alcun bisogno di usare iniziale iniettare e più il funzionamento può essere più breve

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

Hai ragione sull'argomento del simbolo, ma se 'array' può essere vuoto, vuoi l'argomento:' [] .inject (: +) # => nil', '[] .inject (0,: +) # => 0' a meno che tu non voglia gestire il 'nil' separatamente. –

+0

Buon punto, non ci ho pensato. – megas

36

Si potrebbe anche provare:

array.sum(&:cash)

È una scorciatoia per il business degli iniettori e mi sembra più leggibile.
http://api.rubyonrails.org/classes/Enumerable.html

+3

Se stai usando Rails, questa è la strada da percorrere. – Dennis

+0

Si noti che se la matrice è il risultato di un qualche tipo di filtro su un oggetto ActiveRecord, ad es. '@orders = Order.all; @ orders.select {| o | o.status == 'paid'} .sum (&: cost) ', puoi anche ottenere lo stesso risultato con una query:' @ orders.where (status:: paid) .sum (: cost) '. – Dennis

+0

Se i record non sono memorizzati nel DB, la somma sarà 0, dove iniettare funzionerebbe. – dgmora

2

modo più conciso:

array.map(&:cash).sum 

Se l'array risultante dalla mappa ha elementi nil:

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

Se valore iniziale per la somma è pari a 0, quindi riassumere da solo è identico iniettare:

array.map(&:cash).sum 

E Preferirei la versione a blocchi:

array.sum { |a| a.cash } 

Poiché il simbolo Proc da è spesso troppo limitato (nessun parametro, ecc.).

(bisogni Active_Support)

0

Ecco alcuni benchmark interessanti

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 

e risultati

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 

Come si può vedere iniettare un po 'più veloce

Problemi correlati