【问题标题】:Ruby: Get all keys in a hash (including sub keys)Ruby:获取哈希中的所有键(包括子键)
【发布时间】:2013-11-27 06:14:47
【问题描述】:

让我们有这个哈希:

hash = {"a" => 1, "b" => {"c" => 3}}
hash.get_all_keys 
=> ["a", "b", "c"]

我如何获取所有密钥,因为 hash.keys 只返回 ["a", "b"]

【问题讨论】:

  • 谢谢,但以后在选择答案之前请考虑稍等片刻,因为这可能会阻止其他可能提供更好的答案。

标签: ruby hash


【解决方案1】:

这将为您提供任何嵌套级别的所有键的数组。

def get_em(h)
  h.each_with_object([]) do |(k,v),keys|      
    keys << k
    keys.concat(get_em(v)) if v.is_a? Hash
  end
end

hash = {"a" => 1, "b" => {"c" => {"d" => 3}}}
get_em(hash) #  => ["a", "b", "c", "d"]

【讨论】:

    【解决方案2】:

    我发现grep 在这里很有用:

    def get_keys(hash)
      ( hash.keys + hash.values.grep(Hash){|sub_hash| get_keys(sub_hash) } ).flatten
    end
    
    p get_keys my_nested_hash #=> ["a", "b", "c"]
    

    我喜欢这个解决方案,因为它很短,但读起来非常好。

    【讨论】:

      【解决方案3】:

      保留键层次结构的版本

      • 适用于数组
      • 适用于嵌套哈希

      keys_only.rb

      # one-liner
      def keys_only(h); h.map { |k, v| v = v.first if v.is_a?(Array); v.is_a?(Hash) ? [k, keys_only(v)] : k }; end
      
      # nicer
      def keys_only(h)
        h.map do |k, v|
          v = v.first if v.is_a?(Array);
      
          if v.is_a?(Hash)
            [k, keys_only(v)]
          else
            k
          end
        end
      end
      
      hash = { a: 1, b: { c: { d: 3 } }, e: [{ f: 3 }, { f: 5 }] }
      keys_only(hash)
      # => [:a, [:b, [[:c, [:d]]]], [:e, [:f]]]
      

      P.S.:是的,它看起来像一个词法分析器:D

      奖励:在一个漂亮的嵌套列表中打印键

      # one-liner
      def print_keys(a, n = 0); a.each { |el| el.is_a?(Array) ? el[1] && el[1].class == Array ? print_keys(el, n) : print_keys(el, n + 1) : (puts "  " * n + "- #{el}") }; nil; end
      
      # nicer
      def print_keys(a, n = 0)
        a.each do |el|
          if el.is_a?(Array)
             if el[1] && el[1].class == Array
               print_keys(el, n)
             else
               print_keys(el, n + 1)
             end
          else
            puts "  " * n + "- #{el}"
          end
        end
      
        nil
      end
      
      
      > print_keys(keys_only(hash))
      - a
        - b
            - c
              - d
        - e
          - f
      

      【讨论】:

        【解决方案4】:
        def get_all_keys(hash)
          hash.map do |k, v|
            Hash === v ? [k, get_all_keys(v)] : [k]
          end.flatten
        end
        

        【讨论】:

        • === 是不对称的,这意味着 v === Hash 给出的结果与 Hash === v 不同。
        • 我认为 === 很难阅读和理解,它真的应该只用在 case 语句中。
        • 是的,但是就像 case 语句一样,为大小写相等运算符构建了一个函数。
        【解决方案5】:

        请看下面的代码:

        hash = {"a" => 1, "b" => {"c" => 3}}
        keys = hash.keys + hash.select{|_,value|value.is_a?(Hash)}
               .map{|_,value| value.keys}.flatten
        p keys
        

        结果:

        ["a", "b", "c"]
        

        新的解决方案,考虑到@Bala 的 cmets。

        class Hash
          def recursive_keys
            if any?{|_,value| value.is_a?(Hash)}
               keys + select{|_,value|value.is_a?(Hash)}
                            .map{|_,value| value.recursive_keys}.flatten
            else
               keys
            end
          end
        end
        
        hash =  {"a" => 1, "b" => {"c" => {"d" => 3}}, "e" => {"f" => 3}}
        p hash.recursive_keys
        

        结果:

        ["a", "b", "e", "c", "d", "f"]
        

        【讨论】:

        • 如果哈希是hash = {"a" =&gt; 1, "b" =&gt; {"c" =&gt; {"c" =&gt; 3}}, "c" =&gt; {"b" =&gt; 3}},这可能会失败
        • 你是对的。如果 hash 是嵌套多级的,我应该给出一个递归的解决方案。
        • @Bala,我给出一个新的解决方案。
        【解决方案6】:

        还可以处理包含哈希的嵌套数组

        def all_keys(items)
          case items
          when Hash then items.keys + items.values.flat_map { |v| all_keys(v) }
          when Array then items.flat_map { |i| all_keys(i) }
          else []
          end
        end
        

        【讨论】:

          【解决方案7】:
          class Hash
          
            def get_all_keys
              [].tap do |result|
                result << keys
                values.select { |v| v.respond_to?(:get_all_keys) }.each do |value| 
                  result << value.get_all_keys
                end
              end.flatten
            end
          
          end
          
          hash = {"a" => 1, "b" => {"c" => 3}}
          puts hash.get_all_keys.inspect # => ["a", "b", "c"]
          

          【讨论】:

          • tap 的有趣用法。您可能想使用__method__ 代替:get_all_keys。如果方法被重命名并且没有改变,则可能需要一段时间才能找到错误。旁白:你知道为什么respond to? 不检查它的参数是否是现有方法吗?
          • @CarySwoveland:谢谢,我喜欢__method__ 的想法。我不明白你对respond_to?行为的问题,请解释一下。好像我错过了一些重要的事情。
          • v.respond_to?(sym) => 对于不是方法的任何符号 sym 为 false。 Ruby 可以在运行时检查 sym 是否是一个方法,如果不是则引发异常,但它显然没有这样做。我想知道为什么。我突然想到,要使 method_missing 有用,Ruby 可能永远不会检查 Symbol 是否是现有方法。
          • Ruby 只检查方法名称是否在 methods 数组中,因此无法识别 method_missing 是否可能响应。如果不实际运行该方法,几乎​​不可能分析method_missing 的所有可能行为,这可能会产生副作用。有趣的阅​​读 Ruby 1.9.2 中的新 respond_to_missing?blog.marc-andre.ca/2010/11/15/methodmissing-politely
          【解决方案8】:

          这是另一种方法:

          def get_all_keys(h)
            h.each_with_object([]){|(k,v),a| v.is_a?(Hash) ? a.push(k,*get_all_keys(v)) : a << k }
          end
          
          hash = {"a" => 1, "b" => {"c" => {"d" => 3}}}
          p get_all_keys(hash)
          # >> ["a", "b", "c", "d"]
          

          【讨论】:

          • Arup,我认为each_with_object 被大大低估了。例如,在这里,人们经常会看到reduce([])(又名inject),但这需要将保存键的数组返回到块中,这可能需要最后那个丑陋的; keys)。我在这里考虑了each_with_object,但决定在之前使用keys = [],在之后使用keys。人们经常面临这样的选择。我认为更简单的方法更具可读性,但它确实增加了两行。您对此有何看法?
          • @CarySwoveland 在这种情况下,我总是使用each_with_object。对我来说,它在 Ruby 中看起来更具表现力。正如我经常发现的 Ruby 专家喜欢简短而富有表现力的代码。我厌倦了做同样的事情。 :)
          【解决方案9】:

          我确信有一个更优雅的解决方案,但这个选项有效:

          blah = {"a" => 1, "b" => {"c" => 3}}
          results = []
          blah.each do |k,v|
            if v.is_a? Hash
              results << k
              v.each_key {|key| results << key}
            else
              results << k
            end
          end
          puts results
          

          【讨论】:

            【解决方案10】:

            hash.keys 是我见过的最简单的返回哈希键值数组的方法。

            【讨论】:

            • 但它不返回嵌套键 { a: { b: 1 } }.keys # [:a] :(.
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-03-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-07-01
            • 2021-12-11
            相关资源
            最近更新 更多