2012-06-30 48 views
38

Tôi hiểu rằng để tổng hợp các phần tử mảng trong Ruby ai có thể sử dụng phương pháp tiêm, tức làLàm thế nào để tổng hợp các thuộc tính của các đối tượng trong một mảng trong Ruby

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

Nhưng làm thế nào để tôi tổng hợp các thuộc tính của đối tượng trong một mảng đối tượng ví dụ như?

Có một mảng đối tượng và mỗi đối tượng có thuộc tính "tiền mặt" chẳng hạn. Vì vậy, tôi muốn tổng số dư tiền mặt của họ thành một tổng số. Một cái gì đó như ...

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

Tôi nhận ra rằng tôi có thể tạo một mảng mới chỉ gồm tiền mặt tài sản và tổng số tiền này, nhưng tôi đang tìm một phương pháp rõ ràng hơn nếu có thể!

Trả lời

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

hoặc

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

Perfect cảm ơn! –

+3

Điều này vượt qua 'mảng' hai lần mặc dù, có thể không được khuyến khích nếu có nhiều yếu tố.Tại sao không chỉ sử dụng một khối thích hợp cho 'inject'? Ngoài ra 'reduce/inject' trực tiếp nhận một đối số biểu tượng, không cần' Symbol # to_proc' :-) –

+0

lưu ý rằng bạn không cần phải gửi một khối, 'inject' biết phải làm gì với một biểu tượng:' inject (0,: +) ' – tokland

8

#reduce mất một khối (các &:+ là một phím tắt để tạo ra một proc/block mà không +). Đây là một cách để làm những gì bạn muốn:

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

' # reduce' là một bí danh cho '# inject' trong 1,9+, btw. – Theo

+0

+1 không lặp lại trên 'mảng' hai lần. Bí danh cũng có trong 1,8,7 btw. –

+1

như Michael nói rằng đó là không gian hiệu quả hơn mà bản đồ + giảm, nhưng với chi phí mô đun (nhỏ trong trường hợp này, không cần phải nói). Trong Ruby 2.0, chúng ta có thể có cả hai nhờ vào sự lười biếng: 'array.lazy.map (&: cash) .reduce (0,: +)'. – tokland

1

Không có nhu cầu sử dụng ban đầu trong bơm và cộng tác có thể ngắn hơn

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

Bạn đúng về đối số biểu tượng, nhưng nếu 'mảng' có thể trống, bạn muốn đối số:' [] .inject (: +) # => nil', '[] .inject (0,: +) # => 0' trừ khi bạn muốn xử lý riêng biệt 'nil'. –

+0

Điểm tốt, không nghĩ về nó. – megas

36

Bạn cũng có thể thử:

array.sum(&:cash)

Phím tắt của nó cho doanh nghiệp tiêm và có vẻ dễ đọc hơn đối với tôi.
http://api.rubyonrails.org/classes/Enumerable.html

+3

Nếu bạn đang sử dụng Rails, đây là cách để đi. – Dennis

+0

Lưu ý rằng nếu mảng của bạn là kết quả của một số loại lọc trên đối tượng ActiveRecord, ví dụ: '@orders = Order.all; @ orders.select {| o | o.status == 'paid'} .sum (&: cost) ', sau đó bạn cũng có thể nhận được kết quả tương tự với truy vấn:' @ orders.where (trạng thái:: paid) .sum (: cost) '. – Dennis

+0

Nếu các hồ sơ không được lưu trữ trong DB, tổng số sẽ là 0, nơi tiêm sẽ làm việc. – dgmora

2

cách ngắn gọn nhất:

array.map(&:cash).sum 

Nếu mảng phát sinh từ bản đồ có mục nil:

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

Nếu giá trị khởi đầu cho tổng là 0, sau đó tổng hợp thôi thì giống hệt nhau để tiêm:

array.map(&:cash).sum 

Và Tôi thích phiên bản khối:

array.sum { |a| a.cash } 

Vì biểu tượng Proc thường quá hạn chế (không có thông số, v.v ...).

(Nhu cầu Active_Support)

0

Dưới đây một số tiêu chuẩn thú vị

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 

Và kết quả

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 

Như bạn thấy tiêm một chút nhanh hơn

Các vấn đề liên quan