2016-08-08 9 views
1

Lets sagen, dass ich so etwas wie diese:Ruby-Aggregation mit Objekten

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] 

Nun, was ich brauche, ist eine Funktion build_fruit_summary, die aufgrund eines bestimmten fruit_counts Array, es die folgenden Zusammenfassung zurück:

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

Ich kann einfach nicht herausfinden, der beste Weg, um die Aggregationen zu tun.

Edit:

In meinem Beispiel habe ich mehr als einen Zähler haben.

class FruitCount 
    attr_accessor :name, :count1, :count2 

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

Antwort

2

Ruby's Enumerable ist dein Freund, insbesondere each_with_object die eine Form von reduce ist.

Sie müssen zuerst die fruits Wert:

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: hinzugefügt mehrere Zählungen innerhalb der einzelnen fruchtig-Schleife.

Die oben genannten werden jedes Fruchtobjekt serialisieren - eine Zählung für jede Frucht beibehalten - in einen Hash und aggregieren sie in ein leeres list Array und weisen das Aggregat-Array der Variablen fruits zu. Jetzt

, erhalten den Gesamtwert:

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

UPDATE: insgesamt unter Berücksichtigung mehrere Zählung innerhalb einer einzigen Schleife Attribute.

die oben map S die fruit_counts Array, wobei jedes Attribut des Objekts count Rupfen, in einer Reihe von ganzen Zahlen ergibt. Dann erhält reduce die Summe der Ganzzahlen des Arrays.

legte sie nun alle zusammen in der Zusammenfassung:

fruits_summary = { fruits: fruits, total: total } 

Sie dies durch die Einführung eines FruitCollection Objekt in einem OOP-Stil, der die Enumerable Modul verwendet formalisieren können:

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 

Jetzt ist Ihre fruit_count passieren Array in das Objekt:

fruit_collection = FruitCollection.new fruit_counts 
fruits_summary = fruit_collection.summary 

Der Grund, warum das obige funktioniert, ist das Überschreiben der each Methode, die Enumerable unter der Haube für jede enumerable Methode verwendet. Dies bedeutet, dass wir each_with_object, reduce und map aufrufen können (unter anderem aufgelistet in den oben genannten Aufzählungsdokumenten) und es wird über die fruits iterieren, da wir es in der obigen each Methode angegeben haben.

Hier ist ein Artikel auf 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 

Dann benutzen Sie einfach fruit.total, wenn Sie die Summen aggregieren müssen: Ihre Mehrfachzählungen leicht durch Zugabe von insgesamt Attribut zu Ihrem Obst Objekt hinzugefügt werden können

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

Aber es gibt Ihnen jede FruitCount zur Liste hinzufügen, und ich möchte, dass durch die Frucht Namen auch aggregieren. Sehen Sie sich das Ergebnis im Beispiel an. Ich habe es geschafft, es mit einer einzigen Injektion zu tun, in jeder Iteration nach der Frucht zu suchen, die ich zu seiner Zählung hinzufügen möchte, aber ich will immer noch einen einfacheren Weg. –

+0

Große Antwort. Ein paar kleine Verbesserungen: 1) 'fruit_counts' benötigt' each_with_object' nicht, da '@ fruits' bereits ein Array/enumerable ist:' map {| fruit | {Name: Frucht.Name, ...}} '. 2) Sie können zwei Läufe der Schleife vermeiden und 'total' vereinfachen:' count: reduce (0) {| total, fruit | total + fruit.count} '. – coreyward

+0

Beachten Sie in dem Beispiel, dass ich mehrere FruitCounts für jede Frucht wie 'Orange' haben werde ... Ich möchte auch nach Obstnamen aggregieren, bitte schauen Sie sich das Beispiel an. Ich glaube, das Schwierigste hier ist, es mit den weniger Läufen zu lösen. Übrigens mag ich wirklich, wie die Antwort erklärt wurde –

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(:+) 
} 
} 

Nicht sehr effiziente Möglichkeit, obwohl :)

UPD: feste Schlüssel in fruits Sammlung

Oder etwas bessere Version:

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} 
    # }