2017-03-23 1 views
-2

Ich habe ein Array von Hashes array_of_hash genannt:Wie ändert man Arrays von Hashes in Ruby 2.1.2?

array_of_hash = [ 
{:name=>"1", :address=>"USA", :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB"}, 
{:name=>"5", :address=>"UK", :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"BC"}, 
{:name=>"6", :address=>"CANADA", :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"CD"}, 
{:name=>"29", :address=>"GERMANY", :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], :sequence=>"DE"}, 
{:name=>"30", :address=>"CHINA", :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], :sequence=>"FG"} 
] 

I bis Gruppe will diese Hashwerte durch konsekutiven Wert des Schlüssel :name. Die erste Gruppe wäre "1" allein, da es keinen Schlüssel mit :name => "1".sucC#=> "2" gibt. Die zweite Gruppe enthält Hashwerte mit den Werten "5" und "6". Die dritte Gruppe wären die letzten zwei Hashes im Array, für die :name=>29 und :name=>30.

Meine gewünschte Array von Hashes sollte wie folgt aussehen:

[ 
    {:name=>"1", :address=>"USA", :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB"}, 
    {:name=>"5-6", :address=>"UK,CANADA", :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"BC,CD"}, 
    {:name=>"29-30", :address=>"GERMANY,CHINA", :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], :sequence=>"DE, FG"}, 
] 

Use Case II

array_of_hash = [ 
{:name=>"1", :address=>"USA", :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB"}, 
{:name=>"2", :address=>"UK", :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"BC"}, 
{:name=>"3", :address=>"CANADA", :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"CD"}, 
{:name=>"29", :address=>"GERMANY", :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], :sequence=>"DE"}, 
{:name=>"30", :address=>"CHINA", :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], :sequence=>"FG"} 
] 

Wunschergebnis für Anwendungsfall II

[ 
    {:name=>"1-3", :address=>"USA,UK,CANADA", :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB,BC,CD"}, 
    {:name=>"29-30", :address=>"GERMANY,CHINA", :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], :sequence=>"DE, FG"}, 
] 

Was ich bisher tat:

new_array_of_hashes = [] 
new_array_of_hashes << { name: array_of_hashes.map {|h| h[:name].to_i}} << {address: array_of_hashes.map {|h| h[:address]}} << {collection: array_of_hashes.map {|h| h[:collection]}} << {sequence: array_of_hashes.map {|h| h[:sequence]}} 

[{:name=>[1, 5, 6, 29, 30]}, 
{:address=>["USA", "UK", "CANADA", "GERMANY", "CHINA"]}, 
{:collection=> 
[["LAND", "WATER", "OIL", "TREE", "SAND"], 
["LAND", "WATER", "OIL", "TREE", "SAND"], 
["LAND", "WATER", "OIL", "TREE", "SAND"], 
["LAPTOP", "SHIP", "MOUNTAIN"], 
["LAPTOP", "SHIP", "MOUNTAIN"]]}, 
{:sequence=>["AB", "BC", "CD", "DE", "FG"]}] 

kann ich nur kombinieren.

+1

Wie bestimmen Sie, welche Elemente zu kombinieren und welche separat zu lassen? – moveson

+0

@moveson Wenn der ': collection' Wert gleich ist, dann kombinieren Sie – kavin

+2

In diesem Fall sollten die ersten drei Elemente alle kombiniert werden? – moveson

Antwort

2

Zuerst machen wir ein Array der Gruppen, die wir letztlich wollen. Wir verwenden Rubys Array#slice_when-Methode, die über ein Array mit dem aktuellen und dem nächsten Array-Element iteriert, so dass wir beide vergleichen können. Unsere Bedingung wird Ruby anweisen, das Array zu zerlegen, wenn die Namen (konvertiert in ganze Zahlen) nicht sequentiell sind oder wenn die Sammlungen nicht identisch sind.

>> groups = array_of_hash.slice_when { |i, j| i[:name].to_i + 1 != j[:name].to_i || i[:collection] != j[:collection] }.to_a 

Aber weil Sie Ruby-verwenden 2.1, Sie müssen slice_before verwenden und lokale Variablen verwenden Spur früherer Elemente zu halten. Per den documentation, können wir dies erreichen, indem man zuerst eine lokale Variable Grundierung:

>> prev = array_of_hash[0] 

und Zurücksetzen es dann, und einen zweiten lokalen Variable, wie wir über das Array iterieren:

>> groups = array_of_hash.slice_before { |e| prev, prev2 = e, prev; prev2[:name].to_i + 1 != prev[:name].to_i || prev2[:collection] != prev[:collection] }.to_a 

In jedem Fall groups aussehen sollte nun wie folgt aus:

=> [[{:name=>"1", 
    :address=>"USA", 
    :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], 
    :sequence=>"AB"}], 
[{:name=>"5", 
    :address=>"UK", 
    :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], 
    :sequence=>"BC"}, 
    {:name=>"6", 
    :address=>"CANADA", 
    :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], 
    :sequence=>"CD"}], 
[{:name=>"29", 
    :address=>"GERMANY", 
    :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], 
    :sequence=>"DE"}, 
    {:name=>"30", 
    :address=>"CHINA", 
    :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], 
    :sequence=>"FG"}]] 

jetzt nehmen wir die resultierende Array und Karte ihre Elemente zu einem neuen Hash, so formatiert, wie Sie angegeben haben.

Für :name nehmen wir die ersten und letzten Elemente der Gruppe, rufen Sie .uniq, um Dubletten zu entfernen, und verbinden Sie sie mit einem Bindestrich. (Wenn nur ein Element vorhanden ist, gibt join das einzelne Element unverändert zurück.)

Für :collection verwenden wir einfach die Sammlung, die im ersten Element der Gruppe gefunden wurde.

Für :sequence verbinden wir die Sequenzen jedes Elements der Gruppe mit einem Komma. (Auch hier werden einzelne Elemente unverändert zurückgegeben.)

>> groups.map { |group| {name: [group.first[:name], group.last[:name]].uniq.join('-'), 
         collection: group.first[:collection], 
         sequence: group.map { |e| e[:sequence] }.join(',') } } 

=> [{:name=>"1", 
    :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], 
    :sequence=>"AB"}, 
{:name=>"5-6", 
    :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], 
    :sequence=>"BC,CD"}, 
{:name=>"29-30", 
    :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], 
    :sequence=>"DE,FG"}] 
+0

Vielen Dank, aber meine Ruby-Version ist 2.1.2. 'when_slice' funktioniert nur in 2.2. – kavin

+0

Sie haben keine Möglichkeit zu aktualisieren? Ruby 2.1 ist jetzt 3+ Jahre alt. Der Code wird mehr ohne 'slice_when' involviert sein. – moveson

+0

Ja, das ist das Problem, das ich nicht verbessern kann. Gibt es eine ähnliche Methode wie 'when_slice'? – kavin

0
def slice_when(array) 
    big = [] 
    small = [] 
    last_index = array.size - 1 
    (0..last_index).each do |i| 
    small << array[i] 
    if last_index == i || yield(array[i], array[i + 1]) 
     big << small 
     small = [] 
    end 
    end 
    big 
end 

Sie können versuchen, diese verwenden, wenn Sie nicht wollen, slice_before verwenden. Beachten Sie, dass es bereits eine Array, nicht eine Enumurator zurückgibt.

0

-Code

def aggregate(array_of_hash) 
    array_of_hash.chunk_while { |g,h| h[:name] == g[:name].succ }. 
    flat_map { |a| a.chunk { |g| g[:collection] }.map { |_c,b| combine(b) } } 
end 

def combine(arr) 
    names  = values_for_key(arr, :name) 
    addresses = values_for_key(arr, :address) 
    sequences = values_for_key(arr, :sequence) 
    arr.first.merge { 
    name: names.size==1 ? names.first : "%s-%s" % [names.first, names[-1]], 
    address: addresses.join(','), 
    sequence: sequences.join(',') 
    } 
end 

def values_for_key(arr, key) 
    arr.map { |h| h[key] } 
end 

Beispiel

aggregate(array_of_hash) 
    #=> [{:name=>"1", :address=>"USA", 
    #  :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB"}, 
    # {:name=>"5-6", :address=>"UK,CANADA", 
    #  :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"BC,CD"}, 
    # {:name=>"29-30", :address=>"GERMANY,CHINA", 
    #  :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], :sequence=>"DE,FG"}] 

Hier zweites Beispiel.

array_of_hash[2][:collection] = ['dog', 'cat', 'pig'] 
    #=> [{:name=>"1", :address=>"USA", 
    #  :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB"}, 
    # {:name=>"5", :address=>"UK", 
    #  :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"BC"}, 
    # {:name=>"6", :address=>"CANADA", 
    #  :collection=>["dog", "cat", "pig"], :sequence=>"CD"}, 
    # {:name=>"29-30", :address=>"GERMANY,CHINA", 
    #  :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], :sequence=>"DE,FG"}] 

In diesem Beispiel werden die Hashes mit :name=>"5" und :name=>"6" können nicht gruppiert werden, da die Werte von :collection unterschiedlich sind. In der Frage wird nicht angegeben, ob diese Situation eintreten könnte. Wenn es nicht möglich ist, ist der Code immer noch korrekt, aber es könnte zu dem folgenden vereinfacht werden.

def aggregate(array_of_hash) 
    array_of_hash.chunk_while { |g,h| h[:name] == g[:name].succ }. 
    map { |a| combine(a) } 
end 

Erklärung

Für das obige Beispiel die Schritte sind wie folgt.

e0 = array_of_hash.chunk_while { |g,h| h[:name] == g[:name].succ } 
    #=> #<Enumerator: #<Enumerator::Generator:0x007fa25e022f30>:each> 

Siehe Enumerable#chunk_while, der sein Debüt in Ruby v.2.3 gemacht.

Dieser Enumerator generiert die folgenden Elemente, die an Enumerable#flat_map übergeben werden.

e0.to_a 
    #=> [[{:name=>"1", :address=>"USA", 
    #  :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB"}], 
    # [{:name=>"5", :address=>"UK", 
    #  :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"BC"}, 
    #  {:name=>"6", :address=>"CANADA", 
    #  :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"CD"}], 
    # [{:name=>"29", :address=>"GERMANY", 
    #  :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], :sequence=>"DE"}, 
    #  {:name=>"30", :address=>"CHINA", 
    #  :collection=>["LAPTOP", "SHIP", "MOUNTAIN"], :sequence=>"FG"}] 
    # ] 

e0.flat_map { |a| a.chunk { |g| g[:collection] }.map { |_,b| combine(b) } } 

gibt das Array der im Beispiel erhaltenen Hashwerte zurück. Betrachten Sie das erste Element, das von e0 erzeugt und an den Block übergeben und der Blockvariablen zugewiesen wurde, durch flat_map.

a = e0.next 
    #=> [{:name=>"1", :address=>"USA", 
    #  :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB"}] 

Die Blockberechnung ist daher

e1 = a.chunk { |g| g[:collection] } 
    #=> #<Enumerator: #<Enumerator::Generator:0x007fa25c857158>:each> 
e1.to_a 
    #=> [[["LAND", "WATER", "OIL", "TREE", "SAND"], 
    #  [{:name=>"1", :address=>"USA", 
    #  :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB"}] 
    # ] 
    # ] 

_c,b = e1.next 
    #=> [["LAND", "WATER", "OIL", "TREE", "SAND"], 
    # [{:name=>"1", :address=>"USA", 
    #  :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB"}] 
    # ] 
    # _c 
    # #=> ["LAND", "WATER", "OIL", "TREE", "SAND"] 
    # b #=> [{:name=>"1", :address=>"USA", 
    #   :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB"}] 
combine(b) 
    #=> {:name=>"1", :address=>"USA", 
    # :collection=>["LAND", "WATER", "OIL", "TREE", "SAND"], :sequence=>"AB"} 

Die verbleibenden Berechnungen ähnlich sind.

Verwandte Themen