it-swarm.com.de

Ruby: verschachtelten Hash verschmelzen

Ich möchte einen verschachtelten Hash zusammenführen.

a = {:book=>
    [{:title=>"Hamlet",
      :author=>"William Shakespeare"
      }]}

b = {:book=>
    [{:title=>"Pride and Prejudice",
      :author=>"Jane Austen"
      }]}

Ich möchte, dass die Verschmelzung:

{:book=>
   [{:title=>"Hamlet",
      :author=>"William Shakespeare"},
    {:title=>"Pride and Prejudice",
      :author=>"Jane Austen"}]}

Was ist der Nestweg, um dies zu erreichen?

43
user1223862

Für Rails 3.0.0+ oder höher gibt es die deep_merge - Funktion für ActiveSupport , die genau das macht, was Sie verlangen.

50
xlembouras

Ich fand einen allgemeineren Deep-Merge-Algorithmus hier und benutzte es wie folgt:

class ::Hash
    def deep_merge(second)
        merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
        self.merge(second, &merger)
    end
end

a.deep_merge(b)
43
Jon M

Um die Antworten von Jon M und koendc zu ergänzen, behandelt der folgende Code die Zusammenführung von Hashes und: nil wie oben, aber es werden auch alle Arrays, die in beiden Hashes (mit demselben Schlüssel) vorhanden sind, vereint

class ::Hash
    def deep_merge(second)
        merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
        self.merge(second.to_h, &merger)
    end
end

a.deep_merge(b)
31
Dan

Um der Vielfalt willen - und dies funktioniert nur, wenn Sie alle Schlüssel in Ihrem Hash auf dieselbe Weise zusammenführen möchten - Sie könnten dies tun:

a.merge(b) { |k, x, y| x + y }

Wenn Sie einen Block an Hash#merge übergeben, ist k der Schlüssel, der zusammengeführt wird. Der Schlüssel ist in a und b vorhanden, x ist der Wert von a[k] und y ist der Wert von b[k]. Das Ergebnis des Blocks wird zum Wert im zusammengeführten Hash für den Schlüssel k

Ich denke jedoch in Ihrem speziellen Fall, die Antwort von nkm ist besser.

10
Russell

Ein wenig zu spät, um Ihre Frage zu beantworten, aber ich habe vor einiger Zeit ein ziemlich reichhaltiges Merge-Utility geschrieben, das jetzt von Daniel Deleo auf Github verwaltet wird: https://github.com/danielsdeleo/deep_merge

Es wird Ihre Arrays genau wie gewünscht zusammenführen. Aus dem ersten Beispiel in den Dokumenten:

Wenn Sie also zwei Hashes haben:

   source = {:x => [1,2,3], :y => 2}
   dest =   {:x => [4,5,'6'], :y => [7,8,9]}
   dest.deep_merge!(source)
   Results: {:x => [1,2,3,4,5,'6'], :y => 2}

Es wird nicht zusammengeführt: y (weil int und array nicht als zusammenführbar betrachtet werden) - Durch Verwendung der Syntax bang (!) Wird die Quelle überschrieben. Bei Verwendung der Methode "non-bang" bleiben die internen Werte von dest in Ruhe, wenn ein nicht-bindbares Element vorhanden ist gefunden Die in: x enthaltenen Arrays werden zusammengefügt, da sie wissen, wie Arrays zusammengeführt werden. Es behandelt ein beliebig tiefes Zusammenführen von Hashes, die beliebige Datenstrukturen enthalten.

Viele weitere Dokumente zu Daniels Github-Repo ...

6
Steve Midgley

Alle Antworten erscheinen mir zu kompliziert. Hier ist was ich schließlich erfunden habe:

# @param tgt [Hash] target hash that we will be **altering**
# @param src [Hash] read from this source hash
# @return the modified target hash
# @note this one does not merge Arrays
def self.deep_merge!(tgt_hash, src_hash)
  tgt_hash.merge!(src_hash) { |key, oldval, newval|
    if oldval.kind_of?(Hash) && newval.kind_of?(Hash)
      deep_merge!(oldval, newval)
    else
      newval
    end
  }
end

P.S. Verwendung als öffentliche, WTFPL- oder sonstige Lizenz

3
akostadinov

Hier ist eine noch bessere Lösung für rekursives Zusammenführen, das refinements verwendet und die bang-Methode neben der block-Unterstützung hat. Dieser Code funktioniert auf pure Ruby.

module HashRecursive
    refine Hash do
        def merge(other_hash, recursive=false, &block)
            if recursive
                block_actual = Proc.new {|key, oldval, newval|
                    newval = block.call(key, oldval, newval) if block_given?
                    [oldval, newval].all? {|v| v.is_a?(Hash)} ? oldval.merge(newval, &block_actual) : newval
                }   
                self.merge(other_hash, &block_actual)
            else
                super(other_hash, &block)
            end
        end
        def merge!(other_hash, recursive=false, &block)
            if recursive
                self.replace(self.merge(other_hash, recursive, &block))
            else
                super(other_hash, &block)
            end
        end
    end
end

using HashRecursive

Nachdem using HashRecursive ausgeführt wurde, können Sie die Standardeinstellungen Hash::merge und Hash::merge! verwenden, als ob sie nicht geändert wurden. Sie können blocks mit diesen Methoden wie zuvor verwenden.

Das Neue ist, dass Sie boolean recursive (zweites Argument) an diese geänderten Methoden übergeben können und Hashes rekursiv zusammenführen.


Das Beispiel für die einfache Verwendung wird unter diese Antwort geschrieben. Hier ist ein fortgeschrittenes Beispiel.

Das Beispiel in dieser Frage ist schlecht, weil es nichts mit rekursiver Verschmelzung zu tun hat. Die folgende Zeile würde dem Beispiel der Frage entsprechen:

a.merge!(b) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}

Lassen Sie mich ein besseres Beispiel geben, um die Leistungsfähigkeit des obigen Codes zu zeigen. Stellen Sie sich zwei Räume vor, in denen sich jeweils ein Bücherregal befindet. Jedes Bücherregal besteht aus 3 Zeilen, und jedes Bücherregal verfügt derzeit über 2 Bücher. Code:

room1   =   {
    :shelf  =>  {
        :row1   =>  [
            {
                :title  =>  "Hamlet",
                :author =>  "William Shakespeare"
            }
        ],
        :row2   =>  [
            {
                :title  =>  "Pride and Prejudice",
                :author =>  "Jane Austen"
            }
        ]
    }
}

room2   =   {
    :shelf  =>  {
        :row2   =>  [
            {
                :title  =>  "The Great Gatsby",
                :author =>  "F. Scott Fitzgerald"
            }
        ],
        :row3   =>  [
            {
                :title  =>  "Catastrophe Theory",
                :author =>  "V. I. Arnol'd"
            }
        ]
    }
}

Wir werden Bücher aus dem Regal im zweiten Raum in die gleichen Reihen im Regal im ersten Raum verschieben. Zuerst werden wir dies tun, ohne das Flag recursive zu setzen, d. H. Dasselbe wie bei der Verwendung von nicht geändertem Hash::merge!:

room1.merge!(room2) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
puts room1

Die Ausgabe sagt uns, dass das Regal im ersten Raum so aussehen würde:

room1   =   {
    :shelf  =>  {
        :row2   =>  [
            {
                :title  =>  "The Great Gatsby",
                :author =>  "F. Scott Fitzgerald"
            }
        ],
        :row3   =>  [
            {
                :title  =>  "Catastrophe Theory",
                :author =>  "V. I. Arnol'd"
            }
        ]
    }
}

Wie Sie sehen können, zwang uns der recursive nicht dazu, unsere wertvollen Bücher auszuwerfen.

Jetzt machen wir dasselbe, aber mit recursive flag auf true. Sie können als zweites Argument entweder recursive=true oder nur true übergeben:

room1.merge!(room2, true) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
puts room1

Nun zeigt uns die Ausgabe, dass wir unsere Bücher tatsächlich verschoben haben:

room1   =   {
    :shelf  =>  {
        :row1   =>  [
            {
                :title  =>  "Hamlet",
                :author =>  "William Shakespeare"
            }
        ],
        :row2   =>  [
            {
                :title  =>  "Pride and Prejudice",
                :author =>  "Jane Austen"
            },
            {
                :title  =>  "The Great Gatsby",
                :author =>  "F. Scott Fitzgerald"
            }
        ],
        :row3   =>  [
            {
                :title  =>  "Catastrophe Theory",
                :author =>  "V. I. Arnol'd"
            }
        ]
    }
}

Die letzte Ausführung könnte wie folgt umgeschrieben werden:

room1 = room1.merge(room2, recursive=true) do |k, v1, v2|
    if v1.is_a?(Array) && v2.is_a?(Array)
        v1+v2
    else
        v2
    end
end
puts room1

oder

block = Proc.new {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
room1.merge!(room2, recursive=true, &block)
puts room1

Das ist es. Schauen Sie sich auch meine rekursive Version von Hash::each (Hash::each_pair) hier an.

1
MOPO3OB

Ich denke, Jon Ms Antwort ist die beste, aber sie schlägt fehl, wenn Sie einen Hash mit einem Nullwert und einem nicht definierten Wert zusammenführen .. Dieses Update behebt das Problem

class ::Hash
    def deep_merge(second)
        merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
        self.merge(second, &merger)
    end
end

a.deep_merge(b)
0
koendc