如何重构将此数组转换为哈希值

时间:2013-05-31 06:13:08

标签: ruby-on-rails ruby arrays hash

给定此数组(从文件生成)

["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], 
["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], 
["Camden", "DM1182", "54.64 USD"]]

我将它转换为由第二个元素(sku)索引的哈希,代码如下:

result = Hash.new([])
trans_data.each do |arr|
  result[arr[1]].empty? ? result[arr[1]] = [[arr[0], arr[2]]] : result[arr[1]] << [arr[0], arr[2]] 
end
result

这将以我想要的格式输出哈希:

{"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]}

我不觉得我的代码是......干净。有没有更好的方法来实现这个目标?

编辑:到目前为止,我可以将其替换为:(result[arr[1]] ||= []) << [arr[0], arr[2]]

散列没有默认值

6 个答案:

答案 0 :(得分:7)

看起来人们需要了解group_by

ary = [
  ["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"],
  ["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"],
  ["Camden", "DM1182", "54.64 USD"]
]
hash = ary.group_by{ |a| a.slice!(1) }

结果是:

=> {"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]}

可以在没有slice!的情况下相当简洁地编写,允许ary保持不变,并且无需引入任何额外的类或模块:

irb(main):036:0> Hash[ary.group_by{ |a| a[1] }.map{ |k, v| [k, v.map{ |a,b,c| [a,c] } ] }]
=> {"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]}
irb(main):037:0> ary
=> [["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], ["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], ["Camden", "DM1182", "54.64 USD"]]

其他几个答案正在使用each_with_object,这消除了使用Hash[...]将返回的数组强制转换为哈希的需要。以下是我尝试初始化未知密钥时使用each_with_object来避免块内部的线噪声的方法:

ary.each_with_object(Hash.new{ |h,k| h[k] = [] }) { |(a, b, c), h| 
  h[b] << [a, c] 
}
=> {"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]}

这利用Hash.new获取一个初始化块,当先前未定义一个键时,该初始化块被调用。

答案 1 :(得分:4)

使用Facets中的抽象Enumerable#map_by的功能方法:

require 'facets'
records.map_by { |name, key, price| [key, [name, price]] }
#=> {"DM1210"=>[["Yonkers", "70.00 USD"], ... }

遗憾的是Ruby不会在核心内发送map_by,它是Enumerable#group_by的非常有用(因为它未知)的变体(您选择分组键积累的价值。)

答案 2 :(得分:3)

怎么样?
result = trans_data.each_with_object({}) do |arr, hash|
  (hash[arr[1]] ||= []) << [arr[0], arr[2]]
end

答案 3 :(得分:2)

注意:接受的答案是最好的答案,但我真的很高兴我使用的奇怪的真棒和我如何解释它:

arr = [["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], 
["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], 
["Camden", "DM1182", "54.64 USD"]]
 arr.each_with_object({}){|(a, b, c), hash| (hash[b] || hash[b]=[]).push [a,c]}

为every_with_object推荐给年长的上帝!

说明: 这里有两件古怪的事情。第一个是(a, b, c)魔法,我觉得它的作用是这样的:

( 

  #This bit:
  arr.collect{|(a,b,c)| "#{a}#{b}#{c}"}

) - (

  #Is equivalent to this bit:
  (0..arr.size).collect {|i|
    (a,b,c) = arr[i] #=> (a,b,c) = ["Yonkers", "DM1210", "70.00 USD"]
    "#{a}#{b}#{c}"
  }

  #as you can see, they generate identical arrays:
) == []

请注意,在某些情况下,您可以将parens视为隐式:arr.collect{|a, b, c| [a, b, c]} == arr

第二个古怪的事情:

(hash[b] || hash[b]=[]).push(...)

请记住,Ruby中的所有内容都是表达式和​​引用。

[

 (hash[:a] || "foo") == (nil || "foo"),
 (hash[:b]=[]) == [],
 (hash[:b]=[]) === hash[:b],
 (hash[:b] || "foo") == ([] || "foo"),

] == [true, true, true, true]

hash[b],当密钥不存在时,求值为nil(这是假的),因此我们评估并返回后半部分:hash[b]=[],它返回赋值的值,这是hash[b]现在引用的数组,因此我们可以继续使用它,hash[b]将[仍然是]引用更新后的数组。

:d

PS - 我认为,这是我第一次回答的Ruby问题,这是我第一次想到,更不用说能够将评论转化为代码了,哦,我做的我喜欢。谢谢你的谜题!

答案 4 :(得分:0)

试试这个

arr = [["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], ["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], ["Camden", "DM1182", "54.64 USD"]] 

hash = Hash.new{|h,k| h[k] = []}

 arr.each{|a| hash[a[1]].push([a[0],a[2]])}


   hash => {"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]}

答案 5 :(得分:0)

或多或少从facets library tokland中提取:

ary = [["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], ["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], ["Camden", "DM1182", "54.64 USD"]]

hash = {}
ary.each{ |a,b,c| (hash[b] ||= []) << [a,c] }

hash
# => {"Camden"=>[["DM1182", "54.64 USD"]], "Nashua"=>[["DM1182", "58.58 AUD"]], "Scranton"=>[["DM1210", "68.76 USD"]], "Yonkers"=>[["DM1210", "70.00 USD"], ["DM1182", "19.68 AUD"]]}