【问题标题】:How do I summarize hashes within a hash?如何在散列中总结散列?
【发布时间】:2013-12-08 03:31:16
【问题描述】:

我有一个这样的哈希:

Some_hash =
    {"Albania"=>"Europe", 
    "Andorra"=>"Europe", 
    "Austria"=>"Europe",
    Lebanon"=>"Asia", 
    "Macau"=>"Asia", 
    "Malaysia"=>"Asia",
    "Papua New Guinea"=>"Asia",
    "Jamaica"=>"North America",
    "Martinique"=>"North America",
    "Argentina"=>"South America",
    "Chile"=>"South America", 
    "Sao Tome and Principe"=>"Africa", 
    "Senegal"=>"Africa",
    "Somalia"=>"Africa",}

我想分别识别五大洲,以及属于它们的国家,这样我最终会得到这样的结果:

{"Africa" => ["Senegal", "Somalia"]}
{"Europe" => ["Albania", "Andorra", "Austria"]}

适用于所有大陆。

我试过这个:

def country
  inflation_hash = {}
  XPath.match( data, "//country").map do |element|
    inflation_hash[element.attributes["name"]] = element.attributes["continent"]
  end
  inflation_hash.each do |country, continent|
    new_hash = {}
    if inflation_hash.has_value?("Africa") == true
      new_hash["Africa"] = inflation_hash.keys
      puts new_hash
    end
  end
end

而且效果太好了。我得到了一个新的哈希:

{Africa => []} 

但我有两个问题:

  1. 我为每个非洲国家创建一个新的哈希值。
  2. 每个新哈希都包含所有密钥,其中包括所有非非洲国家/地区。

我认为第一个问题与each 方法有关,所以我必须设置一些条件,对吧?

第二个问题,我不知道如何解决。

任何指针将不胜感激。

【问题讨论】:

  • 检查通货膨胀哈希是否包含“非洲”

标签: ruby hashmap


【解决方案1】:

首先,不要像使用SomeHashXPath 那样在Ruby 中对变量使用大写字母。当变量名以大写字母开头时,这意味着它是一个常量,您可能不希望它是一个常量。

each 不是最好的方法,您可以使用inject 更简单地做到这一点,如下所示:

countries = {
    "Albania"=>"Europe", 
    "Andorra"=>"Europe", 
    "Austria"=>"Europe",
    "Lebanon"=>"Asia", 
    "Macau"=>"Asia", 
    "Malaysia"=>"Asia",
    "Papua New Guinea"=>"Asia",
    "Jamaica"=>"North America",
    "Martinique"=>"North America",
    "Argentina"=>"South America",
    "Chile"=>"South America", 
    "Sao Tome and Principe"=>"Africa", 
    "Senegal"=>"Africa",
    "Somalia"=>"Africa"}

by_continents = countries.inject({}) do |memo, (k,v)|
  memo[v] ||= []
  memo[v] << k
  memo
end

这个的输出是:

{"Europe"=>["Albania", "Andorra", "Austria"], "Asia"=>["Lebanon", "Macau", "Malaysia", "Papua New Guinea"], "North America"=>["Jamaica", "Martinique"], "South America"=>["Argentina", "Chile"], "Africa"=>["Sao Tome and Principe", "Senegal", "Somalia"]}

您拥有按大洲分组的所有国家/地区,您可以选择其中任何一个。

在你的代码中应该这样放置:

def country
  inflation_hash = {}
  XPath.match( data, "//country").map do |element|
    inflation_hash[element.attributes["name"]] = element.attributes["continent"]
  end
  by_continents = inflation_hash.inject({}) do |memo, (k,v)|
    memo[v] ||= []
    memo[v] << k
    memo
  end
  puts by_continents.inspect
  by_continents
end

【讨论】:

  • 使用each_with_objectcountries.each_with_object({}) do |(k,v), memo|inject 更容易做到这一点,并删除块中最后的memo
  • @theTinMan 的建议可以与默认值为空数组的哈希创建相结合:countries.each_with_object(Hash.new {|h,k| h[k]=[]}) {|(k,v),memo| memo[v] &lt;&lt; k}
  • 毛里西奥:感谢您的反馈。真的很想使用你的方法,但不幸的是,它只返回一个大陆和一个国家:{"Europe"=>["Albania"]}。 @theTinMan:我现在正在尝试您的方法。会告诉你这件事的进展的。谢谢大家的建议!现在一切正常运行。
  • @MsUzoAgu 您可能以错误的方式替换它,再次检查答案并按照我包含的方式使用它。
  • 嗨毛里西奥:它有效!非常感谢:)……我想了解为什么它第一次没有工作,所以将审查该代码;这也是,所以我知道下次如何使用它。另一方面,您是否有任何资源可以很好地解释注入方法?查看 RobyDoc 并没有在 hash-class 下找到任何东西;确实在方法中找到了一些东西,但是当我们提到 Enumerable 时,很难理解我们程序员的意思。再次感谢您:) 我是 Ruby 新手(一般编程;大约 2 个月前开始),所以任何资源都会很棒,非常感谢。
【解决方案2】:

这是我解决问题的方法:


def sort_by_continents
  # Initialize example Hash of countries:
  country_map = {"Albania"=>"Europe", "Andorra"=>"Europe",
                 "Lebanon"=>"Asia", "Macau"=>"Asia",
                 "Jamaica"=>"North America", "Chile"=>"South America",
                 "Senegal"=>"Africa", "Malaysia"=>"Asia"}

  # Create a new Hash where initial values are = []
  continent_map = Hash.new{|h,k| h[k] = []}

  # For each country in the initial hash:
  #   Add the corresponding country to the appropriate continent.
  country_map.each {|country,continent| continent_map[continent] << country}

  # Return the continent map.
  continent_map
end

记忆化绝对是最好和最有效的方法(如上所示),但对于初学者来说,我会说从有意义的东西开始。一旦你花更多时间在 Ruby 上,记忆和优化就会出现——我知道当我开始的时候,inject||= 的概念令人难以置信的混乱。从基础开始永远是最好的方法。

祝你好运,希望这会有所帮助!

【讨论】:

  • 感谢您的解释和分解!欣赏它:)
【解决方案3】:

你已经有了一个哈希,所以为什么不做你需要的事情:

countries.keys.each { |k| 
  (countries[countries.delete(k)] ||= []) << k
}

如果您将国家/地区完全命名为大洲,这将失败,但这不是您的情况,对吧?

【讨论】:

    【解决方案4】:

    我会考虑以下两种方式:

    version1 = countries.each_with_object({}) do |(key,value),result|
     (result[value] ||= []) << key
    end
    
    version2 = Hash[countries.group_by(&:last).map{|x,y|[x,y.map(&:first)]}]
    

    【讨论】:

      【解决方案5】:

      您可以在构建 inflation_hash 的同时执行此操作,只需使用 default_proc on the Hash to auto-vivify 新元素作为空数组:

      inflation_hash = { }
      new_hash       = Hash.new { |h, k| h[k] = [ ] }
      XPath.match(data, "//country").map do |element|
        name, continent = element.attributes.values_at('name', 'continent')
        inflation_hash[name] = continent
        new_hash[continent].push(name)
      end
      

      这将留下您目前拥有的inflation_hashnew_hash,例如:

      {
        "Africa" => ["Senegal", "Somalia"],
        "Europe" => ["Albania", "Andorra", "Austria"],
        ...
      }
      

      【讨论】:

      • 嗨mu太短了!我遵循你的代码直到最后。我需要找到一种方法让 Ruby 知道每次遇到大陆时不要创建新数组 - 我只希望它创建 5 个哈希,因为我有 5 个唯一的大陆键。正在做一些修改,会让你知道它是怎么回事。非常感谢您的回复:)
      • 为什么要创建五个散列(每个散列都有一个键和值),而单个散列就可以了?如果您确实需要一个由五个单键哈希组成的数组,那么您可以aoh = h.map { |k, v| { k =&gt; v } } 来构建它。更多地阅读default_proc 的工作原理可能会有用。
      • 嗨mu太短了!你是对的,一个哈希就可以了!我是编程新手(2 个月前开始学习),所以没想过以这种方式解决问题。我会再次尝试你的代码,让你知道它是如何工作的。另一方面,如果您能指点我以非常简单的方式解释 proc 的任何资源,我将不胜感激。我也会继续寻找(对有多少东西要学感到惊讶……但我喜欢学习如何编程,所以一点也不抱怨)。再次感谢您。会让你知道你的代码是如何工作的:)
      猜你喜欢
      • 2015-07-17
      • 2012-12-27
      • 2018-10-27
      • 2017-10-13
      • 2011-10-06
      • 2010-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多