【问题标题】:How do I get the elements of a complex nested hash in ruby?如何在 ruby​​ 中获取复杂嵌套哈希的元素?
【发布时间】:2014-10-20 09:35:27
【问题描述】:

我在 Ruby 程序中有以下嵌套哈希。如何编写一个for 循环,最终打印出每个数组中包含的元素?

alternatives = {
  "JAVA" => {
    "/usr/bin" => [
      "java", "keytool", "orbd", "pack200", "rmid",
      "rmiregistry", "servertool", "tnameserv", "unpack200"
    ],
    "/usr/lib" => [
      "jre", "jre_exports" 
    ],
    "/usr/share" => [
      "java.1", "keytool.1", "orbd.1", "pack200.1", "rmid.1",
      "rmiregistry.1", "servertool.1", "tnameserv.1", "unpack200.1"
    ]
  },
  "JDK" => {
    "/usr/bin" => [
      "javac", "appletviewer", "apt", 
      "extcheck", "jar", "jarsigner", "javadoc", "javah", "javap", 
      "jconsole", "jdb", "jhat", "jinfo", "jmap", "jps", 
      "jrunscript", "jstack", "jstat", "jstatd", "native2ascii",
      "policytool", "rmic", "schemagen", "servialver", "wsgen",
      "wsimport", "xjc" 
    ],
    "/usr/lib" => [
      "java_sdk", "java_sdk_exports"
    ],
    "/usr/share/man/man1" => [
      "javac.1", "appletviewer.1", "apt.1", 
      "extcheck.1", "jar.1", "jarsigner.1", "javadoc.1", "javah.1",
      "javap.1", "jconsole.1", "jdb.1", "jhat.1", "jinfo.1", "jmap.1",
      "jps.1", "jrunscript.1", "jstack.1", "jstat.1", "jstatd.1",
      "native2ascii.1", "policytool.1", "rmic.1", "schemagen.1",
      "servialver.1", "wsgen.1", "wsimport.1", "xjc" 
    ]
  }
}

这是我正在尝试做的伪代码

alternatives.each do |first_level_keys| # Gets the JAVA, JDK keys
  first_level_keys.each do |second_level_keys| # Gets the /usr/... keys
    second_level_keys.each do |array|
      array.each do |elem|
        puts "elem: " + elem
      end
    end
  end
end

我看到“Accessing elements of nested hashes in ruby”,但似乎无法理解。

【问题讨论】:

标签: ruby


【解决方案1】:

我想这就是你所需要的:

alternatives.each_value {|h| h.each_value {|a| a.each {|e| puts "elem: #{e}"}}}
elem: java
elem: keytool
elem: orbd
elem: pack200
...
elem: wsgen.1
elem: wsimport.1
elem: xjc

如果对象有嵌套的数组和散列,并且嵌套的级别不同,您可以打印所有本身不是数组或散列的值,如下所示。对于散列中的key-value 对,如果它不是数组或散列,则会打印value

def print_innermost(o)
  case o
  when Hash then  o.each { |_,v| print_innermost(v) }
  when Array then o.each { |e|   print_innermost(e) }
  else puts "elem: #{o}"
  end
end

print_innermost(alternatives)
elem: java
elem: keytool
elem: orbd
elem: pack200
...
elem: wsgen.1
elem: wsimport.1
elem: xjc

alternatives = [{a: {b: {c: [1,2], d: {e: [3,4], f: 5}}}}, [{g: [6,7]}], 8]

print_innermost(alternatives)
elem: 1
elem: 2
elem: 3
elem: 4
elem: 5
elem: 6
elem: 7
elem: 8

【讨论】:

    【解决方案2】:

    "Accessing elements of nested hashes in ruby" 解决了类似的问题,但有很大的不同:在该示例中,OP 知道他在寻找什么键。就您而言,在我看来,您只是试图访问列表中的所有值,因此在其他问题中提交的答案并不完全适用。

    如果您想要做的只是访问数组中的元素,您可以通过不同的方式来处理它。我的特定方法不在for 循环中(for 循环甚至没有在 Ruby 中使用,有关更多信息,请参阅“for vs each in Ruby”),而是使用递归方法。您创建一个接受参数的函数。如果它是一个数组,它将打印元素,否则我们假设它是一个 Hash,并且每个 value 都会调用该函数(我们只是忽略键)

    代码是:

    def get_elements(obj)
      if obj.is_a?(Array)
        obj.each {|element| puts "elem:" + element }
      else
        obj.each {|key, value| get_elements(value) }
      end
    end
    
    get_elements(alternatives)
    

    或者,如果您了解blocks,您可以yield 元素,这样您就可以在代码的另一部分对它们做任何事情

    def get_elements(obj)
      if obj.is_a?(Array)
        obj.each {|element| yield element }
      else
        obj.each {|key, value| get_elements(value) }
      end
    end
    
    get_elements(alternatives) {|elem| puts "elem:" + elem }
    

    【讨论】:

    【解决方案3】:

    你不需要获取第一级哈希的键,然后使用键来获取值——因为你可以直接获取值:

    alternatives.values.each do |hash| # Gets the values for JAVA, JDK keys
      hash.values.each do |arr| # Gets the values for the /usr/... keys
        arr.each do |elmt|
          puts "elem: " + elmt
        end
      end
    end
    

    如果你的哈希有不同层次的嵌套,那么递归就是答案,虽然递归很难学。

    警告!提前递归!

    def get_arrays(hash)
      results = []
    
      hash.values.each do |value|
        case value
          when Hash
            get_arrays(value).each do |elmt|
              results << elmt
            end
          when Array
            results << value
        end
    
      end
    
      results
    end
    
    
    h = {
      a: [1, 2],
      b: 100,
      c: {
           d: [3, 4, 5],
           e: { f: [6, 7] }
         },
      j: { 
           k: [8, 9], 
           l: [10, 11, 12]
         },
    }
    
    
    
    results = get_arrays(h)
    p results
    
    results.each do |arr|
      arr.each do |elmt|
        print "#{elmt} "
      end
      puts 
    end
    
    
    --output:--
    [[1, 2], [3, 4, 5], [6, 7], [8, 9], [10, 11, 12]]
    1 2 
    3 4 5 
    6 7 
    8 9 
    10 11 12 
    

    【讨论】:

      【解决方案4】:

      我知道您要求基于循环的解决方案,但如果您愿意使用标准库中的 Hash 和 Array 方法,则没有必要。

      我会这样做:

      alternatives.values.collect(&:values).flatten
      

      解释:

      • .values 删除键,给你一个哈希数组:[{'usr/bin' =&gt; [...], 'usr/lib' =&gt; [...]}, ...]
      • .collect(&amp;:values) 获取该哈希数组并再次删除键,为您提供 字符串数组数组,如下所示:[[["java",...],[jre, ...]], ...]
      • .flatten 将数组转换为字符串的一维数组。

      我会添加 uniq.sort! 以删除任何重复项并按字母顺序对结果进行排序,除非您想要重复项(然后删除 uniq

      alternatives.values.collect(&:values).flatten.uniq.sort!
      

      【讨论】:

      • 你认为 collect() 不是一个循环? flatten() 怎么样?
      • 我的意思是 for 循环。
      • 好吧,你为什么不把你的文字改成说'for'循环。
      • 因为我觉得已经够清楚了。尤其是现在我们有了这些 cmets。
      【解决方案5】:

      我使用了修改后的 7stud 的答案。

      由于我还需要键的值(例如,JAVA、JDK、/usr/....,所以我结束了这样做:

      alternatives.keys.each do |alt_keys| # Gets the values for JAVA, JDK keys
        alternatives[alt_keys].each do |arr| # Gets the values for the /usr/... keys
          puts "array key: " + arr.to_s
          alternatives[alt_keys][arr].each do |elmt|
            puts "elem: " + elmt
          end
        end
      end
      

      谢谢大家,我稍后也会尝试其他答案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-12-31
        • 1970-01-01
        • 2015-11-13
        • 2010-12-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多