我有一个 Ruby 哈希表:
ages = { "Bruce" => 32,
"Clark" => 28
}
假设我有另一个替换名称的哈希表,是否有一种简洁的方法可以重命名所有键,以便最终得到:ages = { "Bruce Wayne" => 32,
"Clark Kent" => 28
}
ages = { 'Bruce' => 32, 'Clark' => 28 }
mappings = { 'Bruce' => 'Bruce Wayne', 'Clark' => 'Clark Kent' }
ages.transform_keys(&mappings.method(:[]))
#=> { 'Bruce Wayne' => 32, 'Clark Kent' => 28 }
我很喜欢Jörg W Mittag的回答,但是如果你想重命名当前哈希表的键,并且而不是创建一个新的具有重命名键的哈希表,那么以下代码片段正好可以实现:
ages = { "Bruce" => 32, "Clark" => 28 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}
ages.keys.each { |k| ages[ mappings[k] ] = ages.delete(k) if mappings[k] }
ages
另外还有一个优点,就是仅对必要的键进行重命名。
性能考虑:
根据The Tin Man的回答,相比于Jörg W Mittag的方案,我的方案在仅有两个键的哈希表中约快20%。如果哈希表中包含许多键,特别是只需要重命名少量键时,其性能可能会更高。
mappings[k]
的结果为nil值,这就会失败。 - the Tin ManRuby 中也有未被充分利用的 each_with_object
方法:
ages = { "Bruce" => 32, "Clark" => 28 }
mappings = { "Bruce" => "Bruce Wayne", "Clark" => "Clark Kent" }
ages.each_with_object({}) { |(k, v), memo| memo[mappings[k]] = v }
each_with_object
被低估了,比 inject
更清晰易记。当它被引入时,它是一个受欢迎的补充。 - the Tin Man|| k
来处理映射中没有相应键的情况:ages.each_with_object({}) { |(k, v), memo| memo[mappings[k] || k] = v }
- coisnepe只是为了看看哪个更快:
require 'fruity'
AGES = { "Bruce" => 32, "Clark" => 28 }
MAPPINGS = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}
def jörg_w_mittag_test(ages, mappings)
Hash[ages.map {|k, v| [mappings[k], v] }]
end
require 'facets/hash/rekey'
def tyler_rick_test(ages, mappings)
ages.rekey(mappings)
end
def barbolo_test(ages, mappings)
ages.keys.each { |k| ages[ mappings[k] ] = ages.delete(k) if mappings[k] }
ages
end
class Hash
def tfr_rekey(h)
dup.tfr_rekey! h
end
def tfr_rekey!(h)
h.each { |k, newk| store(newk, delete(k)) if has_key? k }
self
end
end
def tfr_test(ages, mappings)
ages.tfr_rekey mappings
end
class Hash
def rename_keys(mapping)
result = {}
self.map do |k,v|
mapped_key = mapping[k] ? mapping[k] : k
result[mapped_key] = v.kind_of?(Hash) ? v.rename_keys(mapping) : v
result[mapped_key] = v.collect{ |obj| obj.rename_keys(mapping) if obj.kind_of?(Hash)} if v.kind_of?(Array)
end
result
end
end
def greg_test(ages, mappings)
ages.rename_keys(mappings)
end
compare do
jörg_w_mittag { jörg_w_mittag_test(AGES.dup, MAPPINGS.dup) }
tyler_rick { tyler_rick_test(AGES.dup, MAPPINGS.dup) }
barbolo { barbolo_test(AGES.dup, MAPPINGS.dup) }
greg { greg_test(AGES.dup, MAPPINGS.dup) }
end
这将输出:
Running each test 1024 times. Test will take about 1 second.
barbolo is faster than jörg_w_mittag by 19.999999999999996% ± 10.0%
jörg_w_mittag is faster than greg by 10.000000000000009% ± 10.0%
greg is faster than tyler_rick by 30.000000000000004% ± 10.0%
注意:barbell的解决方案使用了if mappings[k]
,如果mappings[k]
结果为nil值,则会导致生成的哈希值错误。
mappings
有替换内容时替换键,所有其他解决方案仅在未找到两个键时返回{nil=>28}
。这取决于您的要求。我不确定对基准测试的影响,我会把这留给其他人。如果您想要与其他人相同的行为,请删除提供的if mappings[k]
,或者如果您只想要mappings
中匹配的结果,则认为这将具有更清晰的结果:ages.keys.each { |k| ages.delete(k) if mappings[k].nil? || ages[ mappings[k] ] = ages[k] }
。 - webaholik我对这个类进行了猴子补丁,以处理嵌套的哈希和数组:
# Netsted Hash:
#
# str_hash = {
# "a" => "a val",
# "b" => "b val",
# "c" => {
# "c1" => "c1 val",
# "c2" => "c2 val"
# },
# "d" => "d val",
# }
#
# mappings = {
# "a" => "apple",
# "b" => "boss",
# "c" => "cat",
# "c1" => "cat 1"
# }
# => {"apple"=>"a val", "boss"=>"b val", "cat"=>{"cat 1"=>"c1 val", "c2"=>"c2 val"}, "d"=>"d val"}
#
class Hash
def rename_keys(mapping)
result = {}
self.map do |k,v|
mapped_key = mapping[k] ? mapping[k] : k
result[mapped_key] = v.kind_of?(Hash) ? v.rename_keys(mapping) : v
result[mapped_key] = v.collect{ |obj| obj.rename_keys(mapping) if obj.kind_of?(Hash)} if v.kind_of?(Array)
end
result
end
end
.responds_to?(:rename_keys)
而不是.kind_of?(Hash)
可能更灵活,对于Array
也是如此,你觉得呢? - caesarsolclass Hash
def rekey(h)
dup.rekey! h
end
def rekey!(h)
h.each { |k, newk| store(newk, delete(k)) if has_key? k }
self
end
end
ages = { "Bruce" => 32, "Clark" => 28, "John" => 36 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}
p ages.rekey! mappings
require 'facets/hash/rekey'
ages = { "Bruce" => 32, "Clark" => 28 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}
ages.rekey(mappings)
=> {"Bruce Wayne"=>32, "Clark Kent"=>28}
rekey!
版本:ages.rekey!(mappings)
ages
=> {"Bruce Wayne"=>32, "Clark Kent"=>28}
您可能希望使用Object#tap来避免在键被修改后需要返回ages
:
ages = { "Bruce" => 32, "Clark" => 28 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}
ages.tap {|h| h.keys.each {|k| (h[mappings[k]] = h.delete(k)) if mappings.key?(k)}}
#=> {"Bruce Wayne"=>32, "Clark Kent"=>28}
ages = { "Bruce" => 32, "Clark" => 28 }
mappings = {"Bruce" => "Bruce Wayne", "Clark" => "Clark Kent"}
ages = mappings.inject({}) {|memo, mapping| memo[mapping[1]] = ages[mapping[0]]; memo}
puts ages.inspect
Given(/^an organization exists with the following attributes:$/) do |table|
# Build a mapping from the "friendly" text in the test to the lower_case actual name in the class
map_to_keys = Hash.new
table.transpose.hashes.first.keys.each { |x| map_to_keys[x] = x.downcase.gsub(' ', '_') }
table.transpose.hashes.each do |obj|
obj.keys.each { |k| obj[map_to_keys[k]] = obj.delete(k) if map_to_keys[k] }
create(:organization, Rack::Utils.parse_nested_query(obj.to_query))
end
end
就其价值而言,Cucumber表格如下:
Background:
And an organization exists with the following attributes:
| Name | Example Org |
| Subdomain | xfdc |
| Phone Number | 123-123-1234 |
| Address | 123 E Walnut St, Anytown, PA 18999 |
| Billing Contact | Alexander Hamilton |
| Billing Address | 123 E Walnut St, Anytown, PA 18999 |
而 map_to_keys
看起来像这样:
{
"Name" => "name",
"Subdomain" => "subdomain",
"Phone Number" => "phone_number",
"Address" => "address",
"Billing Contact" => "billing_contact",
"Billing Address" => "billing_address"
}
mappings[k] || k
,而不是mappings[k]
,它会使不在映射中的键保持原样。 - Mladen Jablanovićages.map!
似乎不起作用,所以不得不这样做ages = Hash[ages.map {|k, v| [mappings[k] || k, v] }]
才能再次使用映射的变量。 - Chanporymap
返回一个数组的数组,你可以通过使用ages.map {...}.to_h
转换回哈希表。 - caesarsolto_h
仅适用于 Ruby 2.0 及以上版本。在 Ruby 1.9.3 中,我通过将整个内容包装在Hash[...]
中来完成它。 - digitig&mappings.method(:[])
这行代码是做什么的? - buncis