在 Ruby 中将数组转换为哈希值的最佳方法是什么
题
在 Ruby 中,给定以下形式之一的数组...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
...将其转换为以下形式的哈希的最佳方法是什么...
{apple => 1, banana => 2}
解决方案
笔记: :如需简洁高效的解决方案,请参阅 马克-安德烈·拉福图纳的回答 以下。
这个答案最初是作为使用扁平化方法的替代方案提供的,扁平化方法在撰写本文时获得了最高的支持。我应该澄清的是,我无意将此示例作为最佳实践或有效方法来呈现。原始答案如下。
警告! 解决方案使用 压扁 不会保留数组键或值!
基于@John Topley 的流行答案,让我们尝试一下:
a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]
这会引发错误:
ArgumentError: odd number of arguments for Hash
from (irb):10:in `[]'
from (irb):10
构造函数期望一个偶数长度的数组(例如['k1','v1','k2','v2'])。更糟糕的是,另一个扁平化为偶数长度的数组只会默默地给我们一个带有错误值的哈希值。
如果你想使用数组键或值,你可以使用 地图:
h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"
这保留了数组键:
h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
其他提示
只需使用 Hash[*array_variable.flatten]
例如:
a1 = ['apple', 1, 'banana', 2]
h1 = Hash[*a1.flatten(1)]
puts "h1: #{h1.inspect}"
a2 = [['apple', 1], ['banana', 2]]
h2 = Hash[*a2.flatten(1)]
puts "h2: #{h2.inspect}"
使用 Array#flatten(1)
限制递归所以 Array
键和值按预期工作。
最好的方法是使用 Array#to_h
:
[ [:apple,1],[:banana,2] ].to_h #=> {apple: 1, banana: 2}
注意 to_h
还接受一个块:
[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] }
# => {apple: "I like apples", banana: "I like bananas"}
笔记: to_h
接受 Ruby 2.6.0+ 中的块;对于早期的红宝石,你可以使用我的 backports
宝石和 require 'backports/2.6.0/enumerable/to_h'
to_h
Ruby 2.1.0 中引入了没有块的功能。
在 Ruby 2.1 之前,人们可以使用不太清晰的 Hash[]
:
array = [ [:apple,1],[:banana,2] ]
Hash[ array ] #= > {:apple => 1, :banana => 2}
最后,要警惕任何使用的解决方案 flatten
, ,这可能会给数组本身的值带来问题。
更新
Ruby 2.1.0 今天发布. 。我来了 Array#to_h
(发行说明 和 ruby 文档),解决了转换问题 Array
到一个 Hash
.
Ruby 文档示例:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
编辑:在我写作时看到了发布的回复,Hash[a.flatten] 似乎是可行的方法。当我思考响应时,一定错过了文档中的这一点。我认为如果需要的话,我编写的解决方案可以用作替代方案。
第二种形式更简单:
a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }
a = 数组,h = 散列,r = 返回值散列(我们累积的散列),i = 数组中的项
我能想到的第一种形式的最简洁的方法是这样的:
a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
您还可以使用以下命令将二维数组简单地转换为哈希:
1.9.3p362 :005 > a= [[1,2],[3,4]]
=> [[1, 2], [3, 4]]
1.9.3p362 :006 > h = Hash[a]
=> {1=>2, 3=>4}
附加到答案但使用匿名数组和注释:
Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
把这个答案拆开,从内部开始:
"a,b,c,d"
实际上是一个字符串。split
将逗号放入数组中。zip
与以下数组一起。[1,2,3,4]
是一个实际的数组。
中间结果是:
[[a,1],[b,2],[c,3],[d,4]]
flatten 然后将其转换为:
["a",1,"b",2,"c",3,"d",4]
进而:
*["a",1,"b",2,"c",3,"d",4]
将其展开为"a",1,"b",2,"c",3,"d",4
我们可以用它作为参数 Hash[]
方法:
Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
产生:
{"a"=>1, "b"=>2, "c"=>3, "d"=>4}
摘要 & TL;DR:
这个答案希望成为其他答案信息的全面总结。
非常简短的版本,给出了问题中的数据加上一些额外的内容:
flat_array = [ apple, 1, banana, 2 ] # count=4
nested_array = [ [apple, 1], [banana, 2] ] # count=2 of count=2 k,v arrays
incomplete_f = [ apple, 1, banana ] # count=3 - missing last value
incomplete_n = [ [apple, 1], [banana ] ] # count=2 of either k or k,v arrays
# there's one option for flat_array:
h1 = Hash[*flat_array] # => {apple=>1, banana=>2}
# two options for nested_array:
h2a = nested_array.to_h # since ruby 2.1.0 => {apple=>1, banana=>2}
h2b = Hash[nested_array] # => {apple=>1, banana=>2}
# ok if *only* the last value is missing:
h3 = Hash[incomplete_f.each_slice(2).to_a] # => {apple=>1, banana=>nil}
# always ok for k without v in nested array:
h4 = Hash[incomplete_n] # or .to_h => {apple=>1, banana=>nil}
# as one might expect:
h1 == h2a # => true
h1 == h2b # => true
h1 == h3 # => false
h3 == h4 # => true
讨论和细节如下。
设置:变量
为了显示我们将预先使用的数据,我将创建一些变量来表示数据的各种可能性。它们分为以下几类:
根据问题中直接提出的内容,如 a1
和 a2
:
(笔记:我认为 apple
和 banana
旨在表示变量。正如其他人所做的那样,我将从这里开始使用字符串,以便输入和结果可以匹配。)
a1 = [ 'apple', 1 , 'banana', 2 ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input
多值键和/或值,如 a3
:
在其他一些答案中,提出了另一种可能性(我在这里对其进行了扩展)——键和/或值可能是它们自己的数组:
a3 = [ [ 'apple', 1 ],
[ 'banana', 2 ],
[ ['orange','seedless'], 3 ],
[ 'pear', [4, 5] ],
]
不平衡阵列,如 a4
:
为了更好地衡量,我想我应该为输入不完整的情况添加一个:
a4 = [ [ 'apple', 1],
[ 'banana', 2],
[ ['orange','seedless'], 3],
[ 'durian' ], # a spiky fruit pricks us: no value!
]
现在,开始工作:
从最初平坦的数组开始, a1
:
有些人建议使用 #to_h
(它出现在 Ruby 2.1.0 中,并且可以 向后移植 到早期版本)。对于最初平坦的数组,这是行不通的:
a1.to_h # => TypeError: wrong element type String at 0 (expected array)
Hash[*a1] # => {"apple"=>1, "banana"=>2}
这就是简单情况的解决方案 a1
.
使用键/值对数组的数组, a2
:
与一系列 [key,value]
类型数组,有两种方法可以选择。
第一的, Hash::[]
仍然有效(就像它所做的那样) *a1
):
Hash[a2] # => {"apple"=>1, "banana"=>2}
然后还有 #to_h
现在工作:
a2.to_h # => {"apple"=>1, "banana"=>2}
因此,对于简单的嵌套数组情况,有两个简单的答案。
即使使用子数组作为键或值,这仍然成立,就像 a3
:
Hash[a3] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}
a3.to_h # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}
但榴莲有尖刺(异常结构会带来问题):
如果我们得到的输入数据不平衡,我们会遇到以下问题 #to_h
:
a4.to_h # => ArgumentError: wrong array length at 3 (expected 2, was 1)
但 Hash::[]
仍然有效,只需设置 nil
作为价值 durian
(以及 a4 中的任何其他数组元素,它只是一个 1 值数组):
Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
扁平化——使用新变量 a5
和 a6
提到的其他几个答案 flatten
, ,有或没有 1
参数,所以让我们创建一些新变量:
a5 = a4.flatten
# => ["apple", 1, "banana", 2, "orange", "seedless" , 3, "durian"]
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"]
我选择使用 a4
作为基础数据,因为我们遇到了平衡问题,显示为 a4.to_h
. 。我想打电话 flatten
可能是有人可能用来尝试解决该问题的一种方法,可能如下所示。
flatten
没有参数(a5
):
Hash[*a5] # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)
乍一看,这似乎有效——但它让我们在无籽橙子方面出了差错,因此也使得 3
A 钥匙 和 durian
A 价值.
这,就像 a1
, ,只是不起作用:
a5.to_h # => TypeError: wrong element type String at 0 (expected array)
所以 a4.flatten
对我们来说没有用,我们只想使用 Hash[a4]
这 flatten(1)
案件 (a6
):
但如果只是部分展平呢?值得注意的是,调用 Hash::[]
使用 splat
在部分展平的数组上(a6
) 是 不是 与调用相同 Hash[a4]
:
Hash[*a6] # => ArgumentError: odd number of arguments for Hash
预展平数组,仍然嵌套(另一种获取方式 a6
):
但如果这就是我们最初获取数组的方式呢?(也就是说,相比 a1
, ,这是我们的输入数据 - 只是这次一些数据可以是数组或其他对象。)我们已经看到了 Hash[*a6]
不起作用,但是如果我们仍然想获得 最后一个元素 (重要的!见下文)作为一个关键 nil
价值?
在这种情况下,仍然有一种方法可以做到这一点,使用 Enumerable#each_slice
让我们回到键/值 对 作为外部数组中的元素:
a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]]
请注意,这最终给我们带来了一个新的数组,它不是“完全相同的“ 到 a4
, ,但确实有 相同的价值观:
a4.equal?(a7) # => false
a4 == a7 # => true
因此我们可以再次使用 Hash::[]
:
Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]
但有一个问题!
重要的是要注意 each_slice(2)
解决方案只有在以下情况下才能使事情恢复理智 最后的 key 是缺少值的那个。如果我们稍后添加额外的键/值对:
a4_plus = a4.dup # just to have a new-but-related variable name
a4_plus.push(['lychee', 4])
# => [["apple", 1],
# ["banana", 2],
# [["orange", "seedless"], 3], # multi-value key
# ["durian"], # missing value
# ["lychee", 4]] # new well-formed item
a6_plus = a4_plus.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian", "lychee", 4]
a7_plus = a6_plus.each_slice(2).to_a
# => [["apple", 1],
# ["banana", 2],
# [["orange", "seedless"], 3], # so far so good
# ["durian", "lychee"], # oops! key became value!
# [4]] # and we still have a key without a value
a4_plus == a7_plus # => false, unlike a4 == a7
我们从中得到的两个哈希值在重要方面是不同的:
ap Hash[a4_plus] # prints:
{
"apple" => 1,
"banana" => 2,
[ "orange", "seedless" ] => 3,
"durian" => nil, # correct
"lychee" => 4 # correct
}
ap Hash[a7_plus] # prints:
{
"apple" => 1,
"banana" => 2,
[ "orange", "seedless" ] => 3,
"durian" => "lychee", # incorrect
4 => nil # incorrect
}
(笔记:我在用着 awesome_print
的 ap
只是为了更容易地展示这里的结构;对此没有概念上的要求。)
所以 each_slice
仅当不平衡位位于最后时,不平衡平坦输入的解决方案才有效。
要点:
- 只要有可能,将这些东西的输入设置为
[key, value]
对(外部数组中每个项目的子数组)。 - 当你确实可以做到这一点时,
#to_h
或者Hash::[]
两者都会起作用。 - 如果你做不到,
Hash::[]
与 splat (*
) 将工作, 只要输入是平衡的. - 与 不平衡 和 平坦的 数组作为输入,唯一合理的方法是如果 最后的
value
物品是唯一丢失的物品。
边注:我发布这个答案是因为我觉得有值得补充的价值 - 一些现有的答案包含不正确的信息,并且(我读到的)没有一个给出了我在这里努力做的完整答案。我希望它有帮助。尽管如此,我还是要感谢那些在我之前的人,他们中的一些人为这个答案的部分内容提供了灵感。
如果你有一个看起来像这样的数组 -
data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]
并且您希望每个数组的第一个元素成为哈希的键,其余元素成为值数组,那么您可以执行以下操作 -
data_hash = Hash[data.map { |key| [key.shift, key] }]
#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}
不确定这是否是最好的方法,但这有效:
a = ["apple", 1, "banana", 2]
m1 = {}
for x in (a.length / 2).times
m1[a[x*2]] = a[x*2 + 1]
end
b = [["apple", 1], ["banana", 2]]
m2 = {}
for x,y in b
m2[x] = y
end
如果数值是 seq 索引,那么我们可以有更简单的方法......这是我提交的代码,我的 Ruby 有点生锈
input = ["cat", 1, "dog", 2, "wombat", 3]
hash = Hash.new
input.each_with_index {|item, index|
if (index%2 == 0) hash[item] = input[index+1]
}
hash #=> {"cat"=>1, "wombat"=>3, "dog"=>2}