Ruby中的哈希反转?

6

I've got a hash of the format:

{key1 => [a, b, c], key2 => [d, e, f]}

and I want to end up with:

{ a => key1, b => key1, c => key1, d => key2 ... }

如何最简单地实现这个?

我正在使用Ruby on Rails。

更新

好的,我成功从服务器日志中提取了真正的对象,它是通过AJAX推送的。

  Parameters: {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}

我不确定如何使其打印出一个数组,以便我可以按顺序阅读它以进行实验。 - cjm2671
你尝试过什么?p array会打印出一些东西。 - Mat
1
你确定它是一个数组而不是哈希表吗?你描述的方式有歧义。 - Benoit Garret
1
为什么要使用奇怪的伪代码?展示成Ruby对象... - tokland
这个更简单 -- 使用 FacetsOfRuby 中的 Hash#inverse -- 需要 'facets'。 - Tilo
8个回答

7
hash = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}

第一种变体

hash.map{|k, v| v.map{|f| {f => k}}}.flatten
#=> [{"a"=>:key1}, {"b"=>:key1}, {"c"=>:key1}, {"d"=>:key2}, {"e"=>:key2}, {"f"=>:key2}] 

或者

hash.inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2} 

更新

好的,您的哈希值是:

hash = {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash["status"].inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"12"=>"2", "7"=>"2", "13"=>"2", "8"=>"2", "14"=>"1", "1"=>"1"}

这看起来没问题,但是还不能完全让它工作;我现在已经更新了帖子,展示了实际的对象。 - cjm2671
正如我们在您之前的问题中讨论的那样,fl00r,在不需要使用时使用inject不是一个好选择。另外,您正在使用map,但实际上正在进行副作用,这很令人困惑,最好使用每个(好吧,“更好”,通常情况下每个都不好)。是的,我知道,我是一个函数狂热者 :-) - tokland

3
很多其他好的答案。只是想为Ruby 2.0和1.9.3增加一个答案:
hash = {apple: [1, 14], orange: [7, 12, 8, 13]}

Hash[hash.flat_map{ |k, v| v.map{ |i| [i, k] } }]
# => {1=>:apple, 14=>:apple, 7=>:orange, 12=>:orange, 8=>:orange, 13=>:orange}

这里是利用了Hash::[]Enumerable#flat_map的功能。
另外,在这些新版本中,还有一个Enumerable::each_with_object非常类似于Enumerable::inject/Enumerable::reduce
hash.each_with_object(Hash.new){ |(k, v), inverse|
  v.each{ |e| inverse[e] = k }
}

使用含有100个键和每个键包含100个不同值的原始哈希表进行快速基准测试(Ruby 2.0.0p0;2012款Macbook Air):

Hash::[] w/ Enumerable#flat_map
            155.7 (±9.0%) i/s -        780 in   5.066286s
Enumerable#each_with_object w/ Enumerable#each
            199.7 (±21.0%) i/s -        940 in   5.068926s

这表明对于该数据集,each_with_object 变体更快。


2

好的,让我们猜一下。你说你有一个数组,但我同意Benoit所说的,你可能拥有的是哈希表。以下是一种函数式方法:

 h = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
 h.map { |k, vs| Hash[vs.map { |v| [v, k] }] }.inject(:merge)
 #=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}

此外:
 h.map { |k, vs| Hash[vs.product([k])] }.inject(:merge)
 #=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}

我一直收到“Hash的参数数量是奇数”的错误提示。应该将Hash[vs...]改为Hash[*vs.map { |v| [v, k] }.flatten],而且inject(:merge)只适用于1.9版本。无论如何,这证明了我应该切换到1.9 :-) - undur_gongor
@undur_gongor,你是否使用的是1.8.7或更低版本的Ruby?它应该可以在1.8.7上运行。你也可以尝试“require backports”,看看是否有帮助。Hash[]现在已经接受了对键值对的处理(扁平化的需求非常可怕)。 - tokland
是的(1.8.6)。我一直很困惑。现在我该如何修正错误的注释? - undur_gongor
@undur_gongor:不用担心旧评论,人们可以看到整个对话。你可以删除它们,但那样我就好像在自言自语了 :-) - tokland

2
在这个例子中,如果一个值对应于多个键,比如"c",则...
{ :key1 => ["a", "b", "c"], :key2 => ["c", "d", "e"]}

一些其他答案可能无法得到预期结果。我们需要反转哈希来将键存储在数组中,如下所示:

{ "a" => [:key1], "b" => [:key1], "c" => [:key1, :key2], "d" => [:key2], "e" => [:key2] }

这应该可以解决问题:
reverse = {}
hash.each{ |k,vs|
    vs.each{ |v|
        reverse[v] ||= []
        reverse[v] << k
    }
}

这是我的使用情况,我会像OP一样定义我的问题(事实上,搜索类似的短语让我来到这里),因此我认为这个答案可能会帮助其他搜索者。

1

如果您想正确处理重复值,那么您应该使用 Ruby 的 Facets 中的 Hash#inverse 方法

Hash#inverse 保留了重复值,例如它确保 hash.inverse.inverse == hash

可以选择以下两种方式:

用法如下:

require 'facets'

h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
 => {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]} 

h.inverse
 => {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2} 

代码看起来像这样:

# this doesn't looks quite as elegant as the other solutions here,
# but if you call inverse twice, it will preserve the elements of the original hash

# true inversion of Ruby Hash / preserves all elements in original hash
# e.g. hash.inverse.inverse ~ h

class Hash

  def inverse
    i = Hash.new
    self.each_pair{ |k,v|
      if (v.class == Array)
        v.each{ |x|
          i[x] = i.has_key?(x) ? [k,i[x]].flatten : k
        }
      else
        i[v] = i.has_key?(v) ? [k,i[v]].flatten : k
      end
    }
    return i
  end

end


h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
 => {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]} 

h.inverse
 => {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2} 

1

如果您想要反转这种格式的哈希表,以下内容可能会对您有所帮助:

a = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
a.inject({}) do |memo, (key, values)|
  values.each {|value| memo[value] = key }
  memo
end

这将返回:

{"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}

1
new_hash={}
hash = {"key1" => ['a', 'b', 'c'], "key2" => ['d','e','f']}
hash.each_pair{|key, val|val.each{|v| new_hash[v] = key }}

这会给出

new_hash # {"a"=>"key1", "b"=>"key1", "c"=>"key1", "d"=>"key2", "e"=>"key2", "f"=>"key2"}

0

实现你所需的一种方法:

arr = [{["k1"] => ["a", "b", "c"]}, {["k2"] => ["d", "e", "f"]}]

results_arr = []
arr.each do |hsh|
  hsh.values.flatten.each do |val|
    results_arr << { [val] => hsh.keys.first }···
  end
end


Result: [{["a"]=>["k1"]}, {["b"]=>["k1"]}, {["c"]=>["k1"]}, {["d"]=>["k2"]}, {["e"]=>["k2"]}, {["f"]=>["k2"]}]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接