数组到哈希Ruby


192

好的,这是交易,我一直在寻找谷歌这个问题的解决方案,尽管有很多解决方案,但他们似乎并没有完成我要找的工作。

基本上我有一个像这样的数组

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

我想将其转换为哈希,所以看起来像这样

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

即,“偶数”索引上的项是键,而“奇数”索引上的项是值。

任何想法如何干净地做到这一点?我想蛮力方法是将所有偶数索引拉出到单独的数组中,然后在它们周围循环以添加值。

Answers:


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

而已。在*被称为图示操作。

每个@Mike Lewis一个警告(在评论中):“请务必小心。Ruby会在堆栈上扩展splats。如果使用大型数据集进行扩展,则可能会炸毁堆栈。”

因此,对于大多数一般使用情况,此方法很好,但是如果要对大量数据进行转换,请使用其他方法。例如,@ŁukaszNiemier(也在注释中)为大型数据集提供此方法:

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

10
@tester,*称为splat运算符。它接受一个数组并将其转换为项目的文字列表。所以*[1,2,3,4]=> 1, 2, 3, 4。在此示例中,以上等效于do Hash["item 1", "item 2", "item 3", "item 4"]。并且Hash有一个[]方法可以接受参数列表(产生偶数索引键和奇数索引值),但是Hash[]不接受数组,因此我们使用splat数组*
李·李

15
对此要非常小心。Ruby扩展堆栈上的图示。如果使用大型数据集执行此操作,则可能会耗尽堆栈。
Mike Lewis

9
在大数据表上,您可以使用Hash[a.each_slice(2).to_a]
Hauleth

4
“炸毁堆栈”是什么意思?
凯文(Kevin)

6
@Kevin,堆栈使用程序为某些特定操作分配和保留的一小部分内存。最常见的是,它用于保留到目前为止已调用的方法的堆栈。这就是术语堆栈跟踪的由来,这也是为什么无限递归方法会导致堆栈溢出的原因。此答案中的方法也使用堆栈,但是由于堆栈只是一小部分内存,因此,如果您尝试使用大型数组的此方法,它将填满堆栈并导致错误(与堆栈溢出)。
Ben Lee

103

Ruby 2.1.0 to_h在Array上引入了一种方法,该方法可以在原始数组由键值对数组组成的情况下满足您的要求:http : //www.ruby-doc.org/core-2.1.0/Array.html#method -i-to_h

[[:foo, :bar], [1, 2]].to_h
# => {:foo => :bar, 1 => 2}

1
美丽!比这里的其他一些解决方案好得多。
丹尼斯

3
对于2.1.0之前的ruby版本,只要有成对的嵌套数组,就可以使用Hash :: []方法获得相似的结果。因此a = [[:foo,:1],[bar,2]] ---哈希[a] => {:foo => 1,:bar => 2}
AfDev 2014年

@AfDev,的确,谢谢。您是正确的(忽略次要的错别字:bar需要是一个符号,并且该符号:2应该是整数。因此,更正后的表达式是a = [[:foo, 1], [:bar, 2]])。
Jochem Schulenklopper 2014年

28

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

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

1
[* arr]是什么意思?
艾伦·科罗马诺

1
@Marius:*arr转换arr为参数列表,因此这将[]使用arr的内容作为参数调用Hash方法。
Chuck

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 2012年

2
不。它将返回{ [1, 2] => [3, 4] }。而且,由于问题的标题为“ Array to Hash”,而内置的“ Hash to Array”方法具有:{ 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]],所以我想尝试获得内置的“ Hash to Array”方法的逆向可能不止于此。实际上,这就是我到这里结束的方式。
Erik Escobedo

1
抱歉,我加了一个备用星号。Hash[arr]将为您完成工作。
Yossi 2012年

9
恕我直言更好的解决方案:Hash [* array.flatten(1)]
访客

2
Yossi:很抱歉让死者复活,但是他的回答有一个更大的问题,那就是#inject方法的使用。使用#merge!#each_with_object应该已经使用过。如果#inject坚持,#merge而不是#merge!应该使用。
鲍里斯·斯蒂尼克尼

12

这是我在搜索时寻找的东西:

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


您不想使用merge,它会在每次循环迭代时构造并丢弃新的哈希,并且非常慢。如果您有一系列哈希,请尝试[{a:1},{b:2}].reduce({}, :merge!)-将所有内容合并到相同(新)的哈希中。

谢谢,这也是我想要的!:)
Thanasis Petsas

您也可以这样做.reduce(&:merge!)
Ben Lee

1
[{a: 1}, {b: 2}].reduce(&:merge!)评估为{:a=>1, :b=>2}
Ben Lee,

之所以可行,是因为Inject / reduce具有可以忽略参数的功能,在这种情况下,它将使用数组的第一个参数作为输入参数,而将数组的其余部分用作数组。将其与从符号转换为过程结合起来,您可以获得简洁的结构。换句话说[{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) }
李·李

10

Enumerator包括Enumerable。既然2.1Enumerable也有办法#to_h。这就是为什么,我们可以写:-

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_hEnumerator对象上调用方法。


7

您可以尝试这样,对于单个数组

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}

6
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),则可以结合鲍里斯的建议并写出:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
b-studios

为了使上面代码的语义更清楚:这将创建一个“惰性哈希” h,该哈希最初为,并在需要时从原始数组a中提取元素。只有这样,它们才会实际存储在h中!
Daniel Werner

1

所有答案均假设起始数组是唯一的。OP没有指定如何处理具有重复条目的数组,这会导致重复键。

让我们看一下:

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

您将失去item 1 => item 2对,因为它被覆盖bij item 1 => item 5

Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

所有方法,包括reduce(&:merge!)相同删除的结果。

不过,这可能正是您所期望的。但是在其他情况下,您可能希望获取带有Arrayfor值的结果:

{"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执行上面的操作,但这使推理和阅读变得更加困难。

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.