Ruby中的数组转哈希表

212

将这个数组转换为:

a = ["item 1", "item 2", "item 3", "item 4"] 

转换成哈希表:

{ "item 1" => "item 2", "item 3" => "item 4" }
即,位于偶数索引的元素是,而奇数索引则是

不同方法的性能进行了比较吗?我在这个相关问题下评论了一些想法。 - Cadoiz
9个回答

372
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

就是这样了,*被称为splat操作符。

根据@Mike Lewis(在评论中)的建议,有一个小细节:“要非常小心。Ruby在堆栈上展开splats。如果你用大数据集来做这个,要准备爆掉你的堆栈。”

因此,在大多数一般情况下,这种方法很好,但如果您想对大量数据进行转换,请使用其他方法。例如,@Łukasz Niemier(也在评论中)提供了适用于大数据集的另一种方法:

h = Hash[a.each_slice(2).to_a]

10
@测试人员,*被称为展开运算符。它接受一个数组,并将其转换为一个字面的项目列表。因此,*[1,2,3,4] => 1, 2, 3, 4。在这个例子中,以上等同于执行Hash["item 1", "item 2", "item 3", "item 4"]。而且,Hash有一个[]方法,可以接受一系列参数(使偶数索引成为键,奇数索引成为值),但是Hash []不能接受一个数组,所以我们使用*来展开数组。 - Ben Lee
17
非常小心这个。Ruby会在栈上扩展splat操作符。如果你在一个大型数据集上这样做,预计会导致栈溢出。 - Mike Lewis
9
在大数据表中,您可以使用Hash[a.each_slice(2).to_a] - Hauleth
4
“Blow out your stack”是一个英语习语,意思是失去耐性或情绪失控。 - Kevin
6
@Kevin,“堆栈”使用程序分配并保留用于特定操作的一小块内存区域。最常见的用途是保持已被调用的方法的堆栈。这就是“堆栈跟踪”术语的来源,也是为什么无限递归方法会导致“堆栈溢出”的原因。此答案中的方法也使用堆栈,但由于堆栈只是一个小内存区域,如果您尝试使用大型数组来尝试此方法,它将填满堆栈并导致错误(与堆栈溢出类似的错误)。 - Ben Lee
显示剩余10条评论

134

1
太棒了!比这里的其他解决方案好多了。 - Dennis
3
如果你使用早于2.1.0版本的Ruby,只要有一组嵌套数组的键值对,就可以使用Hash :: []方法获得类似的结果。例如,a =[[:foo, :1], [bar, 2]] --- Hash[a] => {:foo=>1, :bar=>2} - AfDev
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Jochem Schulenklopper

31

只需在数组中使用Hash.[]即可。例如:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}

1
[*arr] 是什么意思? - Alan Coromano
1
@Marius:*arrarr 转换为参数列表,因此这将使用 arr 的内容作为参数调用 Hash 的 [] 方法。 - Chuck
请参考此答案中关于splat操作符的说明。 - Cadoiz

26

或者如果你有一个由 [key, value] 数组构成的数组,你可以这样做:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }

2
你的回答与问题无关,而在你的情况下,使用相同的 Hash [*arr] 仍然更容易。 - Yossi
2
不是的。它会返回 { [1, 2] => [3, 4] }。由于问题的标题是“数组转哈希”,而内置的“哈希转数组”方法是:{ 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]],我认为可能有更多人试图获得内置的“哈希转数组”方法的反向操作。实际上,这就是我最终到达这里的方式。 - Erik Escobedo
1
抱歉,我多加了一个星号。Hash[arr] 将为您完成工作。 - Yossi
9
我认为更好的解决方案是:Hash[*array.flatten(1)]。该代码将数组展开为一维,并将其用作哈希表的键。 - guest
2
Yossi:抱歉打扰了,但他的回答中存在一个更大的问题,那就是使用了#inject方法。应该使用#merge!#each_with_object。如果坚持使用#inject,则应该使用#merge而不是#merge! - Boris Stitnicky
1
现在你可以简单地这样做:[[1, 2], [3, 4]].to_h - CTS_AE

17

进行谷歌搜索时我找到了以下内容:

[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}


1
你不应该使用merge,因为它会在每个循环迭代中构建和丢弃一个新哈希,速度非常慢。如果你有一个哈希数组,请尝试使用[{a:1},{b:2}].reduce({}, :merge!) - 它将所有内容合并到同一个(新的)哈希表中。 - user240438
谢谢,这正是我想要的! :) - Thanasis Petsas
你也可以使用.reduce(&:merge!) - Ben Lee
2
[{a: 1}, {b: 2}].reduce(&:merge!) evaluates to {:a=>1, :b=>2} - Ben Lee
这是因为inject/reduce有一个特性,你可以省略参数,这种情况下它会使用数组的第一个参数作为输入参数,并将数组的其余部分作为数组。再加上symbol-to-proc,你就得到了这个简洁的构造。换句话说,[{a: 1}, {b: 2}].reduce(&:merge!)[{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) }相同,它们都等同于[{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) } - Ben Lee
显示剩余2条评论

10

自从2.1版本以来,Enumerable中也有一个方法#to_h,所以Enumerator包含了它。因此,我们可以这样写:

a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}

因为在不使用块的情况下,#each_slice方法返回一个Enumerator对象,根据上述解释,我们可以在该对象上调用#to_h方法。


8
您可以尝试像这样操作,针对单个数组。
irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
  => ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
  => {"item 1"=>"item 2", "item 3"=>"item 4"}

数组的数组

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}

请参考此答案中关于星号操作符(splat operator)的说明。 - Cadoiz

7
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

或者,如果你不喜欢Hash[ ... ]
a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end

或者,如果你是一个懒惰的支持破碎函数式编程的粉丝:

h = a.lazy.each_slice( 2 ).tap { |a|
  break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"

如果你并不完全讨厌 Hash[ ... ] 但想要像使用 to_h 那样将其用作链式方法,你可以结合Boris的建议编写以下代码:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] } - b-studios
1
为了让上述代码的语义更加清晰:这将创建一个称为“懒哈希”的h,它最初为空,并且将在需要时从原始数组a中提取元素。只有在那时它们才会实际存储在h中! - Daniel Werner
这里的建议是不要对整个数组进行多次迭代吗?这可能会有一些不利之处。 - Cadoiz

2
所有答案都假设起始数组是唯一的。OP没有说明如何处理带有重复条目的数组,这会导致重复的键。

让我们来看看:

"Original Answer"翻译成中文是"最初的回答"。

a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]

您将失去item 1 => item 2对,因为它被item 1 => item 5覆盖:最初的回答
Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

所有的方法,包括reduce(&:merge!)都会得到相同的结果。这可能正是您期望的。但在其他情况下,您可能希望获得一个值为Array的结果:
{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

一个朴素的方法是创建一个帮助变量,即具有默认值的哈希表,然后在循环中填充它:

最初的回答

result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

可能可以使用assocreduce在一行中完成上述操作,但这将变得更加难以理解和阅读。最初的回答。

这似乎是一个注定要应用each_with_object({})(ruby >= 1.9.1.378)的案例,就像这个相关答案中所示。 - Cadoiz

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