在 Ruby 中,给定以下形式之一的数组...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
...以...的形式将其转换为哈希的最佳方法是什么
{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
键和值按预期工作。
注意:有关简洁有效的解决方案,请参阅下面的 Marc-André Lafortune 的回答。
这个答案最初是作为使用 flatten 的方法的替代方案提供的,在撰写本文时,这是最受好评的。我应该澄清一下,我并不打算将此示例作为最佳实践或有效方法来介绍。原始答案如下。
警告!使用flatten的解决方案不会保留数组键或值!
基于@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'])。更糟糕的是,一个扁平化为偶数长度的不同数组只会默默地给我们一个值不正确的哈希值。
如果要使用数组键或值,可以使用map:
h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"
这保留了 Array 键:
h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
最好的方法是使用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-doc),它解决了将 an 转换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}
这个答案希望是对其他答案信息的全面总结。
鉴于问题中的数据加上一些额外内容,非常简短的版本:
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)
与splat 运算符Hash::[]
结合使用可以:
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
了关键和价值。durian
这与a1
, 一样行不通:
a5.to_h # => TypeError: wrong element type String at 0 (expected array)
所以a4.flatten
对我们没有用,我们只想使用Hash[a4]
flatten(1)
案例()a6
:但是只有部分变平呢?值得注意的是,在部分展平的数组 ( )上Hash::[]
调用using与调用 不同: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)
解决方案只会使事情恢复正常。如果我们稍后添加一个额外的键/值对:
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
'sap
只是为了更容易在此处显示结构;对此没有概念上的要求。)
因此,each_slice
仅当不平衡位位于最后时,不平衡平坦输入的解决方案才有效。
[key, value]
对(外部数组中每个项目的子数组)。#to_h
或Hash::[]
两个都可以工作。Hash::[]
的,结合 splat ( *
) 就可以了。value
一项是唯一丢失的项。旁注:我发布这个答案是因为我觉得有一些价值需要补充——一些现有的答案有不正确的信息,并且没有一个(我读过的)给出了我在这里努力做的那样完整的答案。我希望它会有所帮助。尽管如此,我还是要感谢那些在我之前的人,他们中的一些人为这个答案的部分内容提供了灵感。
附加到答案但使用匿名数组和注释:
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}
不确定这是否是最好的方法,但这有效:
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
如果你有这样的数组 -
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]}
对于性能和内存分配问题,请查看我对Rails 将哈希数组映射到单个哈希的回答,我在其中对几种解决方案进行了基准测试。
reduce
/ inject
can be the fastest or the slowest solution depending on which method you use it which.
如果数值是 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}