我想动态创建一个Hash,而不会覆盖数组数组中的键。每个数组都有一个字符串,其中包含应创建的嵌套键。但是,我遇到了覆盖密钥的问题,因此只有最后一个密钥
data = {}
values = [
["income:concessions", 0, "noi", "722300", "purpose", "refinancing"],
["fees:fee-one", "0" ,"income:gross-income", "900000", "expenses:admin", "7500"],
["fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
]
应该是什么样的:
{
"income" => {
"concessions" => 0,
"gross-income" => "900000"
},
"expenses" => {
"admin" => "7500",
"other" => "0"
}
"noi" => "722300",
"purpose" => "refinancing",
"fees" => {
"fee-one" => 0,
"fee-two" => 0
},
"address" => {
"zip" => "10019"
}
}
这是我目前的代码,我有什么可以避免在合并时覆盖密钥?
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
a = data.merge!(hh)
end
end
end
答案 0 :(得分:4)
您提供的代码可以修改为合并哈希而不是覆盖:
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
data.merge!(hh) { |_, old, new| old.merge(new) }
end
end
end
但此代码仅适用于两个嵌套级别。
顺便说一下,我在问题上注明了ruby-on-rails
标记。有deep_merge
方法可以解决问题:
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
data.deep_merge!(hh)
end
end
end
答案 1 :(得分:3)
values.flatten.each_slice(2).with_object({}) do |(f,v),h|
k,e = f.is_a?(String) ? f.split(':') : [f,nil]
h[k] = e.nil? ? v : (h[k] || {}).merge(e=>v)
end
#=> {"income"=>{"concessions"=>0, "gross-income"=>"900000"},
# "noi"=>"722300",
# "purpose"=>"refinancing",
# "fees"=>{"fee-one"=>"0", "fee-two"=>"0"},
# "expenses"=>{"admin"=>"7500", "other"=>"0"},
# "address"=>{"zip"=>"10019"}}
步骤如下。
values = [
["income:concessions", 0, "noi", "722300", "purpose", "refinancing"],
["fees:fee-one", "0" ,"income:gross-income", "900000", "expenses:admin", "7500"],
["fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
]
a = values.flatten
#=> ["income:concessions", 0, "noi", "722300", "purpose", "refinancing",
# "fees:fee-one", "0", "income:gross-income", "900000", "expenses:admin", "7500",
# "fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
enum1 = a.each_slice(2)
#=> #<Enumerator: ["income:concessions", 0, "noi", "722300",
# "purpose", "refinancing", "fees:fee-one", "0", "income:gross-income", "900000",
# "expenses:admin", "7500", "fees:fee-two", "0", "address:zip", "10019",
# "expenses:other","0"]:each_slice(2)>
我们可以通过将它转换为数组来查看此枚举器将生成的值。
enum1.to_a
#=> [["income:concessions", 0], ["noi", "722300"], ["purpose", "refinancing"],
# ["fees:fee-one", "0"], ["income:gross-income", "900000"],
# ["expenses:admin", "7500"], ["fees:fee-two", "0"],
# ["address:zip", "10019"], ["expenses:other", "0"]]
继续,
enum2 = enum1.with_object({})
#=> #<Enumerator: #<Enumerator:
# ["income:concessions", 0, "noi", "722300", "purpose", "refinancing",
# "fees:fee-one", "0", "income:gross-income", "900000", "expenses:admin", "7500",
# "fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
# :each_slice(2)>:with_object({})>
enum2.to_a
#=> [[["income:concessions", 0], {}], [["noi", "722300"], {}],
# [["purpose", "refinancing"], {}], [["fees:fee-one", "0"], {}],
# [["income:gross-income", "900000"], {}], [["expenses:admin", "7500"], {}],
# [["fees:fee-two", "0"], {}], [["address:zip", "10019"], {}],
# [["expenses:other", "0"], {}]]
enum2
可以被认为是复合枚举器(尽管Ruby没有这样的概念)。生成的哈希最初是空的,如图所示,但是将填充,因为enum2
第一个值由enum2
生成并传递给块,块值由一个名为消歧或分解的过程分配值。 / p>
(f,v),h = enum2.next
#=> [["income:concessions", 0], {}]
f #=> "income:concessions"
v #=> 0
h #=> {}
我们现在执行块计算。
f.is_a?(String)
#=> true
k,e = f.is_a?(String) ? f.split(':') : [f,nil]
#=> ["income", "concessions"]
e.nil?
#=> false
h[k] = e.nil? ? v : (h[k] || {}).merge(e=>v)
#=> {"concessions"=>0}
如果h[k]
没有密钥nil
,则 h
等于k
。在那种情况下(h[k] || {}) #=> {}
。如果h
确实有一个密钥k
(h[k]
而非nil
)。(h[k] || {}) #=> h[k]
。
第二个值现在由enum2
生成并传递给块。
(f,v),h = enum2.next
#=> [["noi", "722300"], {"income"=>{"concessions"=>0}}]
f #=> "noi"
v #=> "722300"
h #=> {"income"=>{"concessions"=>0}}
请注意,哈希h
已更新。回想一下,在生成enum2
的所有元素之后,块将返回它。我们现在执行块计算。
f.is_a?(String)
#=> true
k,e = f.is_a?(String) ? f.split(':') : [f,nil]
#=> ["noi"]
e #=> nil
e.nil?
#=> true
h[k] = e.nil? ? v : (h[k] || {}).merge(e=>v)
#=> "722300"
h #=> {"income"=>{"concessions"=>0}, "noi"=>"722300"}
其余的计算方法类似。
答案 2 :(得分:2)
merge
会覆盖重复的密钥。
{ "income"=> { "concessions" => 0 } }.merge({ "income"=> { "gross-income" => "900000" } }
完全覆盖"income"
的原始值。你想要的是一个递归合并,而不是只是合并顶级哈希,而是在重复时合并嵌套值。
merge
需要一个块,您可以在其中指定在复制时要执行的操作。来自文档:
合并!(other_hash){| key,oldval,newval |阻止}→hsh
将other_hash的内容添加到hsh。如果未指定块,则使用来自other_hash的值覆盖具有重复键的条目,否则通过使用键调用块来确定每个重复键的值,将其值设置为hsh并将其值设置为other_hash
使用此功能,您可以在一行中定义一个简单的recursive_merge
def recursive_merge!(hash, other)
hash.merge!(other) { |_key, old_val, new_val| recursive_merge!(old_val, new_val) }
end
values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')
if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
a = recursive_merge!(data, hh)
end
end
end
更多的行将为您提供更强大的解决方案,它将覆盖非哈希的重复键,甚至可以像merge
def recursive_merge!(hash, other, &block)
hash.merge!(other) do |_key, old_val, new_val|
if [old_val, new_val].all? { |v| v.is_a?(Hash) }
recursive_merge!(old_val, new_val, &block)
elsif block_given?
block.call(_key, old_val, new_val)
else
new_val
end
end
end
h1 = { a: true, b: { c: [1, 2, 3] } }
h2 = { a: false, b: { x: [3, 4, 5] } }
recursive_merge!(h1, h2) { |_k, o, _n| o } # => { a: true, b: { c: [1, 2, 3], x: [3, 4, 5] } }
注意:如果您正在使用Rails,此方法会重现您从ActiveSupport Hash#deep_merge
获得的结果。
答案 3 :(得分:1)
这就是我处理这个问题的方法:
def new_h
Hash.new{|h,k| h[k] = new_h}
end
values.flatten.each_slice(2).each_with_object(new_h) do |(k,v),obj|
keys = k.is_a?(String) ? k.split(':') : [k]
if keys.count > 1
set_key = keys.pop
obj.merge!(keys.inject(new_h) {|memo,k1| memo[k1] = new_h})
.dig(*keys)
.merge!({set_key => v})
else
obj[k] = v
end
end
#=> {"income"=>{
"concessions"=>0,
"gross-income"=>"900000"},
"noi"=>"722300",
"purpose"=>"refinancing",
"fees"=>{
"fee-one"=>"0",
"fee-two"=>"0"},
"expenses"=>{
"admin"=>"7500",
"other"=>"0"},
"address"=>{
"zip"=>"10019"}
}
说明:
new_h
),用于在任何级别(Hash
)设置默认new_h
的新Hash.new{|h,k| h[k] = new_h}
Array
(values.flatten
).each_slice(2)
)Hash
(.each_with_object(new_h.call) do |(k,v),obj|
)keys = k.is_a?(String) ? k.split(':') : [k]
)obj.merge!(keys.inject(new_h.call) {|memo,k1| memo[k1] = new_h.call})
)obj.dig(*keys.merge!({set_key => v})
)obj[k] = v
)只要深度链没有被破坏,这就具有无限的深度说[["income:concessions:other",12],["income:concessions", 0]]
在这种情况下后一个值优先(注意:这适用于所有答案的一种方式或者另一种情况,例如前者获胜的答案,但由于数据结构不准确,价值仍然存在。)