ネストされたハッシュをマージしたいと思います。
a = {:book=>
[{:title=>"Hamlet",
:author=>"William Shakespeare"
}]}
b = {:book=>
[{:title=>"Pride and Prejudice",
:author=>"Jane Austen"
}]}
マージを次のようにします。
{:book=>
[{:title=>"Hamlet",
:author=>"William Shakespeare"},
{:title=>"Pride and Prejudice",
:author=>"Jane Austen"}]}
これを達成するためのネスト方法は何ですか?
Rails 3.0.0+以降のバージョンでは、 deep_merge 関数が ActiveSupport まさにあなたが求めていることをします。
より一般的なディープマージアルゴリズム here を見つけて、次のように使用しました。
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)
Jon Mとkoendcの回答に追加するために、以下のコードはハッシュのマージを処理し、上記のように:nilを処理しますが、両方のハッシュに存在する配列を結合します(同じキーを使用):
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)
さまざまな理由で-これは、ハッシュ内のすべてのキーを同じ方法でマージする場合にのみ機能します-これを行うことができます:
a.merge(b) { |k, x, y| x + y }
ブロックをHash#merge
に渡すと、k
はマージされるキーであり、キーはa
とb
、x
の両方に存在しますa[k]
の値であり、y
はb[k]
の値です。ブロックの結果は、キーk
のマージされたハッシュの値になります。
あなたの特定のケースでは、nkmの答えの方が良いと思います。
あなたの質問に答えるのに少し遅れましたが、GithubでDaniel Deleoによって現在保守されているかなり豊富なディープマージユーティリティを書きました: https://github.com/danielsdeleo/deep_merge
必要に応じて正確に配列をマージします。ドキュメントの最初の例から:
したがって、次のような2つのハッシュがある場合:
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}
マージされません:y(intと配列はマージ可能と見なされないため)-bang(!)構文を使用すると、ソースが上書きされます。非bangメソッドを使用すると、マージ不可能なエンティティが見つかりました。配列をマージする方法を知っているため、:xに含まれる配列を一緒に追加します。任意のデータ構造を含むハッシュの任意の深さのマージを処理します。
ダニエルのGitHubレポジトリに関するドキュメントが増えました。
すべての答えは複雑すぎるように見えます。最終的に思いついたのは次のとおりです。
# @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
追伸パブリック、WTFPL、または任意のライセンスとして使用
再帰的マージのさらに良い解決策は、refinmentsを使用し、bang methodとblockサポートこのコードはpureRubyで動作します。
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
using HashRecursive
が実行された後、デフォルトのHash::merge
およびHash::merge!
を変更していないかのように使用できます。以前と同様に、これらのメソッドでblocksを使用できます。
新しいことは、これらの変更されたメソッドにブールrecursive
(2番目の引数)を渡すことができ、ハッシュを再帰的にマージすることです。
簡単な使用例は this answer に書かれています。高度な例を次に示します。
この質問の例は、再帰的なマージとは何の関係もないので悪いです。次の行は質問の例を満たします。
a.merge!(b) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
上記のコードの威力を示すために、より良い例を挙げましょう。 2つの部屋を想像してください。各部屋には本棚が1つあります。各本棚には3行があり、各本棚には現在2冊の本があります。コード:
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"
}
]
}
}
本を2番目の部屋の棚から最初の部屋の棚の同じ行に移動します。最初にrecursive
フラグを設定せずにこれを行います。つまり、変更されていないHash::merge!
を使用するのと同じです。
room1.merge!(room2) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
puts room1
出力から、最初の部屋の棚は次のようになることがわかります。
room1 = {
:shelf => {
:row2 => [
{
:title => "The Great Gatsby",
:author => "F. Scott Fitzgerald"
}
],
:row3 => [
{
:title => "Catastrophe Theory",
:author => "V. I. Arnol'd"
}
]
}
}
ご覧のとおり、recursive
がなかったため、私たちは貴重な本を捨てざるを得ませんでした。
次に、同じことを行いますが、recursive
フラグをtrueに設定します。 2番目の引数としてrecursive=true
または単にtrue
のいずれかを渡すことができます。
room1.merge!(room2, true) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
puts room1
これで、出力から、実際に本を移動したことがわかります。
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"
}
]
}
}
その最後の実行は、次のように書き換えられます。
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
または
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
それでおしまい。また、再帰バージョンのHash::each
(Hash::each_pair
) here もご覧ください。
Jon Mの答えは最高だと思いますが、ハッシュをnil/undefined値でマージすると失敗します。このアップデートは問題を解決します:
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)