在散列数组中查找来自一个散列的所有键

时间:2015-02-15 18:37:30

标签: ruby

我有一个哈希,我需要将其与哈希数组进行比较。

例如:

# Array of Hashes to compare another hash against
@stored_hash = [
   {:a => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
   {:a => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
   {:a => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
]

# Hash to compare to Array of Hashes
@sample_hash = {:d => 3, :e => 4, :f => 5}

我正在寻找@sample_hash中与所有@stored_hashes

中任何一个键匹配的键

我可以将单个哈希与另一个哈希进行比较,如此

res = @sample_hash.keys.select { |k| @stored_hash.key?(k) }

但我不能让它为一系列哈希工作。帮助赞赏。

修改

这是我到目前为止所尝试过的。

res = @sample_hash.keys.select { |k| @stored_hash.map(&:keys).include?(k) }

这似乎只是拖延程序(我正在使用非常大的数据集)

我也试过这个:

res = @sample_hash.keys.select { |k| @stored_hash.map { |x| x.key?(k) } }

哪会返回结果,但如果我反过来:

res = @sample_hash.keys.reject { |k| @stored_hash.map { |x| x.key?(k) } }

我没有得到任何结果(我知道哈希中有常见和非常见键的事实)

1 个答案:

答案 0 :(得分:2)

我们可以分两步完成。

第1步

stored_hash = [
   { :a => 1, :b => 2, :c => { :d => 3, :e => 4, :f => 5 } },
   { :a => 1, :b => 2, :c => { :d => 3, :e => 4, :g => 5 } },
   { :a => 1, :b => 2, :c => { :d => 3, :e => 4, :h => 5 } },
]

请注意,我稍微更改了stored_hash

def all_keys_all_hashes(arr)
  arr.each_with_object([]) { |h,keys| keys.concat(all_keys(h)) }.uniq
end

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

keys = all_keys_all_hashes(stored_hash)
  #=> [:a, :b, :c, :d, :e, :f, :g, :h]

第2步

sample_hash = {:d => 3, :e => 4, :f => 5, :z => 99 }

sample_hash.keys & keys
  #=> [:d, :e, :f] 

<强>附录

如果您遇到类似这样的问题,那么这是一个“快速和肮脏”的方式来获取stored_hash中的所有密钥,前提是它们都是符号,字符串或自然数字。方法是将数组转换为字符串,然后使用正则表达式提取键,然后进行一些处理。

首先,为了处理更一般的情况,让我们修改stored_hash的前两个元素中的每一个中的第一个键:

stored_hash = [
   {"a" => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
   { 99 => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
   { :a => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
]

你可以这样:

stored_hash.to_s.scan(/[{,]\s*\K[^{,]+?(?==>)/).uniq.map do |s|
  case
  when (s[0] == ?:)   then s[1..-1].to_sym
  when (s =~ /^\d+$/) then s.to_i
  else s.gsub(?", '')
  end
end
  #=> ["a", :b, :c, :d, :e, :f, 99, :a] 

\K指示Ruby匹配之前的内容,但不将其包含在它保留的匹配中。当前一个匹配是可变长度(\s*)时,它特别方便,因为正向外观不能用于可变长度匹配。

这不能正确处理带引号的符号(例如,:"text"),但修改正则表达式来处理这种情况并不困难。

以下是步骤:

str = stored_hash.to_s
  #=> "[{\"a\"=>1, :b=>2, :c=>{:d=>3, :e=>4, :f=>5}}, " +
  #    "{99=>1, :b=>2, :c=>{:d=>3, :e=>4, :f=>5}}, " +
  #    "{:a=>1, :b=>2, :c=>{:d=>3, :e=>4, :f=>5}}]"

(我已将字符串分成三部分,因此无需使用水平滚动即可读取。)

a1 = str.scan(/[{,]\s*\K[^{,]+?(?==>)/)
  #=> ["\"a\"", ":b", ":c", ":d", ":e", ":f",
  #    "99", ":b", ":c", ":d", ":e", ":f",
  #    ":a", ":b", ":c", ":d", ":e", ":f"]
a2 = a1.uniq
  #=> ["\"a\"", ":b", ":c", ":d", ":e", ":f", "99", ":a"]
a2.map do |s|
  case
  when (s[0] == ?:)   then s[1..-1].to_sym
  when (s =~ /^\d+$/) then s.to_i
  else s.gsub(?", '')
  end
end
  #=> ["a", :b, :c, :d, :e, :f, 99, :a]