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?
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.
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)
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)
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.
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 ...
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
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.
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)