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


Answers:


91

注意:有关简洁高效的解决方案,请参阅Marc-AndréLafortune的答案

该答案最初是作为使用flatten的方法的替代方法提供的,在撰写本文时,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'])。更糟糕的是,扁平化为均匀长度的另一个Array只会默默地为我们提供具有错误值的Hash。

如果要使用数组键或值,可以使用map

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

这保留了Array键:

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

15
这与Hash [a3]相同,因为a3 == a3.map {| k,v | [k,v]}是正确的,实际上等同于a3.dup。
2012年

2
为什么不指定贴图的深度,而不是使用贴图?例如:h3 = Hash[*a3.flatten(1)]否则h3 = Hash[*a3.flatten]将引发错误。
Jeff McCune

3
这个答案是无效的。它也已经过时了。看我的答案。
马克-安德烈·Lafortune

1
是的,我认为Marc-André's to_h更好。
B 2015年

1
@Marc-AndréLafortune谢谢,我已经更新了答案,可以将用户定向到您的答案。

145

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


4
哦,口才!这就是为什么我喜欢Ruby
iGbanam'2

11
警告:如果需要数组键或值,使用展平的答案将导致问题。
炖2012年

我在下面发布了一个替代解决方案,可以避免数组键或值出现问题。
炖2012年

5
最好不要尝试为此采取万能的解决方案。如果您的键和值按[[key1,value1],[key2,value2]]配对,则只需将其传递给Hash []即可,而不会发胖。Hash [a2] == Hash [* a2.flatten]。如果数组已经按[key1,value1,key2,value2]中的方式进行了展平,则只需在var前面加上*,Hash [* a1]
群集

8
FWIW,如果您确实想要(更多)一种全能版本,则还可以使用Hash[*ary.flatten(1)],它将保留数组键和值。递归flatten破坏了它们,这很容易避免。
布鲁克林

79

最好的方法是使用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,这可能会导致数组本身的值产生问题。


4
感谢新.to_h方法的简单性!
上瘾的编码

3
to_h比上面的答案更喜欢该方法,因为它表达了对数组进行操作进行转换的意图。
B 2015年

1
@BSeven Array#to_h也不Enumerable#to_h在核心ruby 1.9中。
钢铁救主

如果我有一个数组,[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]并且希望输出为该{"apple" =>[1,3], "banana"=>[2,4]}怎么办?
nishant

@NishantKumar,这是一个不同的问题。
马克-安德烈·Lafortune


9

编辑:看到我写作时张贴的回复,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 }

2
单线+1 a.inject({}),可以更灵活地分配值。
克里斯·布鲁姆

也可以h = {}通过使用inject从第二个示例中删除,最后以a.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
lindes

你可以做a.each_slice(2).to_h
Conor O'Brien '18

6

您还可以使用以下命令将2D数组简单地转换为哈希值:

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

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

1.9.3p362 :006 > h = Hash[a]

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

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

讨论和细节如下。


设置:变量

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

根据问题中的直接内容,是a1a2

(注:我猜想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::[]结合图示运营商的作用:

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::[]仍然有效,只需将其设置nildurian(以及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):

但是,仅部分展平呢?值得一提的是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)解决方案只有在最后一个解决方案时才能恢复理智键是缺少值的键。如果以后再添加一个额外的键/值对:

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仅当不平衡位恰好在末端时,才能解决不平衡平面输入的问题。


外卖:

  1. 尽可能将这些内容设置为 [key, value]对(外部数组中每个项目的子数组)。
  2. 当您确实可以做到时, #to_hHash::[]两个都将起作用。
  3. 如果您无法执行此操作,请Hash::[]与splat(*)结合使用,只要输入平衡即可
  4. 使用不平衡平坦的数组作为输入,这唯一合理地起作用的唯一方法是,如果最后 value一项是唯一缺少的一项。

旁注:之所以发布此答案,是因为我觉得有需要添加的价值–一些现有答案的信息不正确,而且(我读到的)没有一个我在此所做的完整回答。希望对您有所帮助。但是,我要感谢那些在我之前的人,其中一些人为部分答案提供了灵感。


3

附加答案,但使用匿名数组并注释:

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

展平然后将其转换为:

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

这也可以在不使用splat(*)和flatten:Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]=>的情况下工作{"a"=>1, "b"=>2, "c"=>3, "d"=>4}。我添加的答案中有更多详细信息。
林德斯

0

如果您的数组看起来像这样-

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

0

不知道这是否是最好的方法,但这可以工作:

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

-1

如果数值是seq索引,那么我们可以采用更简单的方法...这是我的代码提交,My 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}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.