在 Ruby 中将数组转换为散列的最佳方法是什么

在 Ruby 中,给定以下形式之一的数组..。

[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]

... 什么是最好的方法,转换成一个散列的形式..。

{apple => 1, banana => 2}
152472 次浏览

只需使用 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键和值能够按预期工作。

编辑: 在我写作的时候看到了回复,Hash [ a.flatten ]看起来是个不错的选择。 肯定是我在思考回复的时候漏掉了文档中的那一部分。认为我写的解决方案可以作为替代品,如果需要的话。

第二种形式比较简单:

a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }

A = array,h = hash,r = return-value hash (我们累积的那个) ,i = 数组中的 item

我能想到的做第一种形式的最简单的方法是这样的:

a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }

不知道这是不是最好的办法,但是这个办法很有效:

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}

在答案后面附加但使用匿名数组并注释:

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

把这个答案拆开,从内部开始:

  • "a,b,c,d"实际上是一个字符串。
  • 将逗号上的 split转换为数组。
  • 与下面的数组一起使用。
  • [1,2,3,4]是一个实际的数组。

中间结果是:

[[a,1],[b,2],[c,3],[d,4]]

然后把它变成:

["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}

注意 : 要获得简洁有效的解决方案,请参见下面的 Marc-André Lafortune 的回答

这个答案最初是作为使用扁平化方法的替代方法提供的,扁平化方法在撰写本文时得到了最高的支持率。我应该澄清一下,我并不打算把这个例子作为一个最佳实践或者一个有效的方法来呈现。原始答案如下。


警告! 使用 压平的解决方案将不保留 Array 键或值!

基于@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

构造函数期望一个偶数长度的 Array (例如[‘ k1’,‘ v1,‘ k2’,‘ v2’])。更糟糕的是,一个不同的数组,平坦到一个偶数的长度将只是默默地给我们一个不正确的值散列。

如果要使用 Array 键或值,可以使用 地图:

h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"

这保留了 Array 键:

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}

您还可以简单地使用以下方法将2D 数组转换为 hash:

1.9.3p362 :005 > a= [[1,2],[3,4]]


=> [[1, 2], [3, 4]]


1.9.3p362 :006 > h = Hash[a]


=> {1=>2, 3=>4}

更新

Ruby2.1.0今天发布。我还提供了 Array#to_h(释放通知书Ruby-Doc) ,它解决了将 Array转换为 Hash的问题。

Ruby docs 示例:

[[:foo, :bar], [1, 2]].to_h    # => {:foo => :bar, 1 => 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 + 的代码块; 对于早期的 Ruby,你可以使用我的 backports gem 和 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的任何解决方案,这可能会给数组本身的值带来问题。

如果你有这样的数组-

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]}

摘要 & 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

接下来是讨论和细节。


设置: 变量

为了预先显示我们将要使用的数据,我将创建一些变量来表示数据的各种可能性。它们属于以下类别:

根据问题的直接内容,作为 a1a2:

(注意: 我假设 applebanana是用来表示变量的。正如其他人所做的那样,从现在开始我将使用字符串,以便输入和结果能够匹配。)

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::[]操作员可以:

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}

使用新的变量 a5a6

其他一些答案提到了 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) :

但如果只是部分变平呢?值得注意的是,在部分平坦的数组(a6)上使用 splat调用 Hash::[]与调用 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_printap只是为了更容易在这里显示结构; 这里没有概念上的要求。)

因此,each_slice解决方案,一个不平衡的平面输入只有工作,如果不平衡的位是在最后。


外卖:

  1. 只要有可能,将输入设置为 [key, value]对(外部数组中每个项目的子数组)。
  2. 当你确实可以做到这一点,无论是 #to_hHash::[]都将工作。
  3. 如果你不能,Hash::[]结合板(*)将工作,只要输入是平衡的
  4. 使用 不平衡平的数组作为输入,唯一合理工作的方法是,如果 最后 value项目是唯一丢失的项目。

附注: 我发布这个答案是因为我觉得这里有值得添加的东西——一些现有的答案有不正确的信息,而且没有(我读到的)给出一个完整的答案,因为我正努力在这里做。希望能有所帮助。尽管如此,我还是要感谢在我之前的那些人,他们中的一些人为这个问题的部分答案提供了灵感。

对于性能和内存分配问题,请检查 我的回答Rails 将哈希数组映射到单个哈希,在这里我标记了几个解决方案。

reduce/inject可以是最快的解决方案,也可以是最慢的解决方案,这取决于你使用哪种方法。