如何递归检查是否存在特定密钥

时间:2016-05-25 16:36:48

标签: ruby-on-rails ruby recursion hash

给定一个无限嵌套参数的Ruby哈希,我想写一个函数,如果给定的键在这些参数中,则返回true

这是我到目前为止的功能,但它不太正确,我不知道为什么:

def has_key(hash, key)
    hash.each do |k, v|
        if k == key
            return true
        elsif v.class.to_s == "Array"
            v.each do |inner_hash|
                return has_key(inner_hash,key)
            end
        else 
            return false
        end
    end
end

该方法应返回以下结果:

# all check for presence of "refund" key

has_key({
  "refund" => "2"
}, "refund")
=> true

has_key({
  "whatever" => "3"
}, "refund")
=> false

has_key({
  "whatever" => "3", 
  "child_attributes" => [{
     "refund" => "1"
  }]
}, "refund")
=> true    

has_key({
  "whatever" => "3", 
  "child_attributes" => [{
    "nope" => "4"
  }]
}, "refund")
=> false

has_key({
  "whatever" => "3", 
  "child_attributes" => [{
    "a" => "1", 
    "refund" => "2"
  }]
}, "refund")
=> true

has_key({
  "whatever" => "3", 
  "child_attributes" => [
    {"a" => "1", "b" => "2"},
    {"aa" => "1", "refund" => "2"}
  ]
}, "refund")
=> true

has_key({
  "whatever" => "3", 
  "child_attributes" => [
    {"a" => "1", "b" => "2"},
    {"grand_child_attributes" => [
      {"test" => "3"}
    ]}
  ]
}, "refund")
=> false

has_key({
  "whatever" => "3", 
  "child_attributes" => [
    {"a" => "1", "b" => "2"},
    {"grand_child_attributes" => [
      {"test" => "3"}, {"refund" => "5"}
    ]}
  ]
}, "refund")
=> true

has_key({
  "whatever" => "3", 
  "child_attributes" => [
    {"a" => "1", "b" => "2"},
    {"grand_child_attributes" => [
      {"test" => "3", "refund" => "5"}
    ]}
  ]
}, "refund")
=> true

5 个答案:

答案 0 :(得分:1)

以下内容可行。

def has_key(hash, key)
  hash.each do |k, v|
    return true if k == key
    if v.is_a? Array
      v.each do |h|
        rv = has_key(h, key)
        return rv if rv
      end
    end
  end
  false
end     

这会通过所有测试。还有一个:

h = { "a" => 1, 
      "b" => [{ "c" => 2, "d" => 3 },
              {"e"=> [{ "f" => "4" },
                      { "g" => [{ "h" => 5 },
                                { "i" => 6, "refund" => 7 }
                               ]
                      }             
                     ]
              }
             ]
    }

has_key h, "refund"
  #=> true

h["b"][1]["e"][1]["g"] = [{ "h"=>5 }] 
h
  #=> {"a"=>1, "b"=>[{"c"=>2, "d"=>3}, {"e"=>[{"f"=>"4"}, {"g"=>[{"h"=>5}]}]}]} 
has_key h, "refund"
  #=> false 

受@ Wand的回答启发,

h = {"a"=>"3", "b"=>[{"c"=>"1", "d"=>"2"}, {"e"=>[{"test"=>"3", "refund"=>"5"}]}]}

您不必加载JSON:

str = h.to_s
  #=> "{\"a\"=>\"3\", \"b\"=>[{\"c\"=>\"1\", \"d\"=>\"2\"}, {\"e\"=>[{\"test\"=>\"3\", \"refund\"=>\"5\"}]}]}" 
str =~ /\"refund\"=>/
  #=> 60 (truthy) 

我承认对任何将哈希转换为字符串,然后解析字符串的方法感到有些不舒服,因为担心字符串格式将来可能会发生变化。

答案 1 :(得分:1)

您的代码问题似乎就在这里:

elsif v.class.to_s == "Array"
    v.each do |inner_hash|
        return has_key(inner_hash,key)
    end
else

这将始终返回has_key(inner_array[0])而不检查后续值。修复是仅在它是真的时返回,否则继续检查,如下所示:

elsif v.class.to_s == "Array"
    v.each do |inner_hash|
        if(has_key(inner_hash,key))
            return true
        end
    end
else
    return false

答案 2 :(得分:1)

我做的事情如下:

class Hash
  def key_exists?(key)
    self.keys.include?(key) ||
    self.values.any?{ |v|
      Hash === v &&
        v.key_exists?(key)
    }
  end
end

{'a' => 1}.key_exists?('a')  # => true
{'b' => 1}.key_exists?('a')  # => false
{'b' => {}}.key_exists?('a')  # => false

{'b' => {'a' => {}}}.key_exists?('a')  # => true
{'b' => {'a' => 1}}.key_exists?('a')  # => true

{'b' => {'b' => {}}}.key_exists?('a')  # => false
{'b' => {'b' => {'a' => 1}}}.key_exists?('a')  # => true

插入有关扩展核心类和建议的所有常见警告,以使用其他方法在此处执行此操作。

注意:同样," iterate over every key in nested hash"可用于轻松确定真/假值,并演示扩展核心类的安全方法。

答案 3 :(得分:0)

给我一​​个镜头,我试了一下,似乎对我很好

def has_key(hash, key)
  if hash.keys.include?(key)
    true
  else
    # get the values of the current hash, flatten out to not include arrays
    new_hash = hash.values.flatten
    # then filter out any element that's not a hash
    new_hash = new_hash.select {|b| b.is_a?(Hash)}
    # merge all the hashes into single hash
    new_hash = new_hash.inject {|first, second| first.merge(second)}

    if new_hash
      has_key(new_hash, key)
    else
      false
    end
  end
end

答案 4 :(得分:0)

您可以将哈希值转换为JSON,然后检查JSON中是否存在"refund":,因为任何键都将以"key":的形式序列化为JSON。

require "json"
hash.to_json.include?('"refund":')