2012-06-30 2 views
38

Я понимаю, что для того, чтобы подвести элементы массива в Ruby, можно использовать метод INJECT, т.е.Как суммировать свойства объектов в массиве в Ruby,

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

Но как я просуммировать свойства объектов внутри массива объектов, например?

Существует массив объектов, и каждый объект имеет свойство «наличными», например. Поэтому я хочу суммировать свои денежные остатки в одну общую сумму. Что-то вроде ...

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

Я понимаю, что я мог бы сделать новый массив, состоящий только из имущества денежных средств и просуммировать, но я ищу более чистый метод, если это возможно!

ответ

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

или

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

Идеальное спасибо! –

+3

Это происходит через 'array' дважды, хотя, возможно, не рекомендуется, если есть много элементов.Почему бы просто не использовать правильный блок для «инъекции»? Кроме того, 'reduce/inject' непосредственно принимает аргумент символа, не нужно' Символ # to_proc' :-) –

+0

Обратите внимание, что вам не нужно отправлять блок, 'inject' знает, что делать с символом:' inject (0,: +) ' – tokland

8

#reduce занимает блок (&:+ ярлык, чтобы создать ргос/блок, который делает +). Это один из способов сделать то, что вы хотите:

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

' # reduce' - это псевдоним для '# injection' в 1.9+, кстати. – Theo

+0

+1 для повторного итерации над 'array' дважды. Псевдоним также существует в 1.8.7 битв. –

+1

, так как Майкл говорит, что это более экономично, чем карта + уменьшает, но ценой модульности (в этом случае мало что нужно сказать). В Ruby 2.0 мы можем получить как ленту: 'array.lazy.map (&: cash) .reduce (0,: +)'. – tokland

1

Там нет необходимости использовать начальное в Inject и плюс операция может быть короче

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

Вы правы в аргументе символа, но если 'array' может быть пустым, вам нужен аргумент:' [] .inject (: +) # => nil', '[] .inject (0,: +) # => 0', если вы не хотите иметь дело с «nil» отдельно. –

+0

Хороший вопрос, не думал об этом. – megas

36

Вы можете также попробовать:

array.sum(&:cash)

Его ярлык для бизнеса для инъекций и кажется мне более читаемым.
http://api.rubyonrails.org/classes/Enumerable.html

+3

Если вы используете Rails, это путь. – Dennis

+0

Обратите внимание, что если ваш массив является результатом какой-либо фильтрации на объекте ActiveRecord, например. '@orders = Order.all; @ orders.select {| o | o.status == 'paid'} .sum (&: cost) ', то вы также можете получить тот же результат с запросом:' @ orders.where (status:: paid) .sum (: cost) '. – Dennis

+0

Если записи не хранятся в БД, сумма будет равна 0, где будет работать инъекция. – dgmora

2

Наиболее краткий способ:

array.map(&:cash).sum 

Если результирующий массив из карты имеет нулевые элементы:

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

Если начальное значение для суммирования равно 0, то просуммировать одна идентичен для инъекций:

array.map(&:cash).sum 

И Я предпочел бы вариант блока:

array.sum { |a| a.cash } 

Поскольку Proc от символа не часто слишком ограничен (без параметров и т.д.).

(Needs Active_Support)

0

Вот некоторые интересные тесты

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 

И результаты

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 

Как вы можете видеть впрыснуть немного быстрее

Смежные вопросы