如何优雅地重命名 Ruby 中哈希中的所有键? [复制]

Posted

技术标签:

【中文标题】如何优雅地重命名 Ruby 中哈希中的所有键? [复制]【英文标题】:How to elegantly rename all keys in a hash in Ruby? [duplicate] 【发布时间】:2011-05-07 11:21:50 【问题描述】:

我有一个 Ruby 哈希:

ages =  "Bruce" => 32,
         "Clark" => 28
       

假设我有另一个替换名称哈希,是否有一种优雅的方法可以重命名所有键,以便我最终得到:

ages =  "Bruce Wayne" => 32,
         "Clark Kent" => 28
       

【问题讨论】:

【参考方案1】:

我喜欢 Jörg W Mittag 的回答,但如果您想重命名当前哈希的键并且 不使用重命名的键创建新哈希,请执行以下操作sn-p 正是这样做的:

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%。对于具有多个键的哈希,它可能会获得更高的性能,特别是如果只有几个键需要重命名。

【讨论】:

我喜欢这个。一个让我印象深刻的问题是我在 as_json() 调用中使用了它,虽然主要属性键被转换为字符串,但是 options.merge(:methods => [:blah]) 那么这是地图中的一个键而不是字符串。 @peterept 你可以试试 options.with_indifferent_access.merge(:methods => [:blah])。这将使选项访问字符串或符号作为键。 喜欢这个答案......但我很困惑这实际上是如何工作的。每个集合的值如何设置? 嗨,@ClaytonSelby。你能更好地解释什么让你感到困惑吗? 我知道问题是“所有键”,但如果你想让它更快,你可能应该遍历映射而不是重命名的哈希。最坏的情况,速度是一样的。【参考方案2】:
ages =  'Bruce' => 32, 'Clark' => 28 
mappings =  'Bruce' => 'Bruce Wayne', 'Clark' => 'Clark Kent' 

ages.transform_keys(&mappings.method(:[]))
#=>  'Bruce Wayne' => 32, 'Clark Kent' => 28 

【讨论】:

谢谢,太好了!现在,如果我只想更改一些键名,有没有办法测试该键是否存在映射? 只需使用mappings[k] || k 而不是上面的mappings[k],它会使键不在映射中。 我注意到 ages.map! 似乎不起作用...所以必须这样做 ages = Hash[ages.map |k, v| [mappings[k] || k, v] ] 才能使用映射再次调用该变量。 map 返回一个Array of Arrays,你可以使用ages.map ....to_h转换回Hash 尽管to_h 仅在 Ruby 2.0 及更高版本中可用。在 Ruby 1.9.3 中,我通过将整个内容包装在 Hash[...] 中来做到这一点【参考方案3】:

Ruby 中还有一个未充分利用的 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 更清晰、更容易记住。它一推出就受到欢迎。 我认为这是最好的答案。您也可以使用|| k 来处理映射没有对应键的情况:ages.each_with_object() |(k, v), memo| memo[mappings[k] || k] = v 【参考方案4】:

只是看看哪个更快:

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 值,则会导致生成的哈希错误。

【讨论】:

RE: "Caution:" - 我不确定我是否认为它是“错误的”,如果mappings 有东西可以替换它,它只会替换 key ,所有其他解决方案将返回nil=>28,只有当两个键都没有找到。这取决于您的要求。我不确定对基准的影响,我会把它留给其他人。如果您想要与其他人相同的行为,只需删除提供的if mappings[k],或者如果您只想要mappings 中的匹配结果,我认为这会产生更清晰的结果:ages.keys.each |k| ages.delete(k) if mappings[k].nil? || ages[ mappings[k] ] = ages[k] 【参考方案5】:

我使用它来允许将 Cucumber 表中的“友好”名称解析为类属性,以便 Factory Girl 可以创建一个实例:

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"

【讨论】:

【参考方案6】:

Facets gem 提供了一个 rekey 方法,可以完全满足您的需求。

只要您对 Facets gem 的依赖没有问题,您就可以将映射哈希传递给 rekey,它会返回带有新键的新哈希:

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

【讨论】:

【参考方案7】:

如果映射哈希将小于数据哈希,则改为迭代映射。这对于重命名大型 Hash 中的一些字段很有用:

class 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

【讨论】:

【参考方案8】:

我对类进行了猴子修补以处理嵌套的哈希和数组:

   #  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 的等价物可能更灵活,你怎么看?【参考方案9】:

您可能希望使用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

【讨论】:

【参考方案10】:
>> x= :a => 'qwe', :b => 'asd'
=> :a=>"qwe", :b=>"asd"
>> rename=:a=>:qwe
=> :a=>:qwe
>> rename.each|old,new| x[new] = x.delete old
=> :a=>:qwe
>> x
=> :b=>"asd", :qwe=>"qwe"

这将通过重命名哈希循环。

【讨论】:

【参考方案11】:
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

【讨论】:

ages = mappings.inject() |memo, (old_key, new_key)|备忘录[新键] = 年龄[旧键];备忘录

以上是关于如何优雅地重命名 Ruby 中哈希中的所有键? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

在 Ruby 中找到第一个可能匹配的哈希键的优雅方式

如果存在,如何重命名哈希中的键

汇总所有相似哈希键的值 Ruby

使用点分路径键字符串访问 Ruby 哈希

使用点分路径键字符串访问 Ruby 哈希

ruby 在哈希中删除相同的值多个键