2016-08-08 54 views
1

紅寶石聚集可以說我有這樣的事情:使用對象

class FruitCount 
    attr_accessor :name, :count 

    def initialize(name, count) 
    @name = name 
    @count = count 
    end 
end 

obj1 = FruitCount.new('Apple', 32) 
obj2 = FruitCount.new('Orange', 5) 
obj3 = FruitCount.new('Orange', 3) 
obj4 = FruitCount.new('Kiwi', 15) 
obj5 = FruitCount.new('Kiwi', 1) 

fruit_counts = [obj1, obj2, obj3, obj4, obj5] 

現在我需要的,是一個功能build_fruit_summary這是由於給定fruit_counts陣列,它返回以下總結:

fruits_summary = { 
    fruits: [ 
    { 
     name: 'Apple', 
     count: 32 
    }, 
    { 
     name: 'Orange', 
     count: 8 
    }, 
    { 
     name: 'Kiwi', 
     count: 16 
    } 
    ], 
    total: { 
    name: 'AllFruits', 
    count: 56 
    } 
} 

我只是無法弄清楚聚合的最佳方式。

編輯:

在我的例子中,我有一個以上的計數。

class FruitCount 
    attr_accessor :name, :count1, :count2 

    def initialize(name, count1, count2) 
    @name = name 
    @count1 = count1 
    @count2 = count2 
    end 
end 

回答

2

Ruby's Enumerable是你的朋友,特別是each_with_object這是reduce一種形式。

首先需要的fruits值:

fruits = fruit_counts.each_with_object([]) do |fruit, list| 
    aggregate = list.detect { |f| f[:name] == fruit.name } 

    if aggregate.nil? 
    aggregate = { name: fruit.name, count: 0 } 
    list << aggregate 
    end 

    aggregate[:count] += fruit.count 
    aggregate[:count2] += fruit.count2 
end 

UPDATE:單果味循環中添加多個計數。

上面的命令會將每個水果對象(保持每個水果的計數)序列化爲一個散列並將它們聚合到一個空的list數組中,並將聚合數組分配給fruits變量。

現在,得到了總價值:

total = { name: 'AllFruits', count: fruit_counts.map { |f| f.count + f.count2 }.reduce(:+) } 

UPDATE:總要考慮到一個循環中的多個計數屬性。

以上map s fruit_counts數組,採用每個對象的count屬性,產生一個整數數組。然後,reduce正在獲得數組整數的總和。

現在把它全部摘要:

fruits_summary = { fruits: fruits, total: total } 

您可以在OOP風格形式化通過引入使用Enumerable模塊FruitCollection對象:

class FruitCollection 
    include Enumerable 

    def initialize(fruits) 
    @fruits = fruits 
    end 

    def summary 
    { fruits: fruit_counts, total: total } 
    end 

    def each(&block) 
    @fruits.each &block 
    end 

    def fruit_counts 
    each_with_object([]) do |fruit, list| 
     aggregate = list.detect { |f| f[:name] == fruit.name } 

     if aggregate.nil? 
     aggregate = { name: fruit.name, count: 0 } 
     list << aggregate 
     end 

     aggregate[:count] += fruit.count 
     aggregate[:count2] += fruit.count2 
    end 
    end 

    def total 
    { name: 'AllFruits', count: map { |f| f.count + f.count2 }.reduce(:+) } 
    end 
end 

現在通過你的fruit_count排列成該對象:

fruit_collection = FruitCollection.new fruit_counts 
fruits_summary = fruit_collection.summary 

上述工作原因是通過覆蓋each方法,其中Enumerable方法在每個enumerable方法下使用。這意味着我們可以調用each_with_object,reducemap(其中列出在上面的可枚舉文檔中),並且它會遍歷fruits,因爲我們在上面的each方法中告訴它。

這是關於Enumerable的文章。

UPDATE:

class FruitCount 
    attr_accessor :name, :count1, :count2 

    def initialize(name, count1, count2) 
    @name = name 
    @count1 = count1 
    @count2 = count2 
    end 

    def total 
    @count1 + @count2 
    end 
end 

就用fruit.total每當你需要聚合總計:

fruit_counts.map(&:total).reduce(:+) 
+0

但你添加的每個FruitCount到列表中,我希望聚合也由水果名稱。看看示例中的結果。我已經設法通過單次注入來完成,在每次迭代中查看我想要添加到其計數的水果,但我仍然想要一個更簡單的方法。 –

+0

很棒的回答。一些小的改進: 1)'fruit_counts'不需要'each_with_object',因爲'@ fruits'已經是一個數組/可枚舉:'map {| fruit | {name:fruit.name,...}}。 2)你可以避免循環的兩次運行,並簡化'total':'count:reduce(0){| total,fruit | total + fruit.count}'。 – coreyward

+0

請注意,在示例中,我將爲每個水果設置多個FruitCount,例如'Orange'...我也想通過水果名稱進行聚合,請查看示例。我相信這裏最難的事情是用更少的運行來解決它。順便說一句,我真的很喜歡這個答案是如何解釋的 –

0
fruits_summary = { 
    fruits: fruit_counts 
    .group_by { |f| f.name } 
    .map do |fruit_name, objects| 
     { 
     name: fruit_name, 
     count: objects.map(&:count).reduce(:+) 
     } 
    end, 
total: { 
    name: 'AllFruits', 
    count: fruit_counts.map(&:count).reduce(:+) 
} 
} 
你多計數可以通過添加一個總的屬性到你的水果對象很容易地添加

雖然效率不是很高,但:) :)

UPD:固定密鑰在fruits收集

還是略勝一籌的版本:

fruits_summary = { 
    fuits: fruit_counts 
    .reduce({}) { |acc, fruit| acc[fruit.name] = acc.fetch(fruit.name, 0) + fruit.count; acc } 
    .map { |name, count| {name: name, count: count} }, 
    total: { 
    name: 'AllFruits', 
    count: fruit_counts.map(&:count).reduce(:+) 
    } 
} 
0
counts = fruit_counts.each_with_object(Hash.new(0)) {|obj, h| h[obj.name] += obj.count} 
    #=> {"Apple"=>32, "Orange"=>8, "Kiwi"=>16} 

fruits_summary = 
    { fruits: counts.map { |name, count| { name: name, count: count } }, 
    total: { name: 'AllFruits', count: counts.values.reduce(:+) } 
    } 
    #=> {:fruits=>[ 
    #  {:name=>"Apple", :count=>32}, 
    #  {:name=>"Orange", :count=> 8}, 
    #  {:name=>"Kiwi", :count=>16}], 
    # :total=> 
    #  {:name=>"AllFruits", :count=>56} 
    # }