简体   繁体   中英

Ruby — convert a nested hash to a multidimensional array

I have a hash which is named h . I want to store the contents in a multidimensional array named ar . I am getting the error no implicit conversion from nil to integer .

Here is my code:

h = {"bob" => {email: "abc" , tel: "123"} , "daisy" => {email: "cab" , tel: "123456"}}

keys = h.keys

l = h.length 

ar = Array.new(l) { Array.new(3) }

for i in 0..l-1
  ar[[2][i]] = keys[i]
  ar[[1][i]] = h[keys[i]][:email]
  ar[[0][i]] = h[keys[i]][:tel]
end

puts ar.to_s

The desired output is:

[[email_1, email_2, ..][tel_1, tel_2, ..][name_1, name_2, ..]]

For example:

[["abc", "cab"] , ["123", "123456"] , ["bob", "daisy"]]

[2][i] returns nil for i > 0 . ar[nil] raises the exception.

Here is what you do:

arr = h.map { |k, v| [v[:email], v[:tel], k] }.reduce(&:zip)

To make your code work:

Change

ar = Array.new(l) { Array.new(3) }

To

ar = Array.new(3) { Array.new(l) }

Change

ar[[2][i]] = keys[i]
ar[[1][i]] = h[keys[i]][:email]
ar[[0][i]] = h[keys[i]][:tel]

To

ar[2][i] = keys[i]
ar[1][i] = h[keys[i]][:email]
ar[0][i] = h[keys[i]][:tel]

This is the way I would handle this:

h.values.each_with_object({}) do |h,obj|
  obj.merge!(h) { |_k,v1,v2| ([v1] << v2).flatten }
end.values << h.keys
#=> [["abc", "cab"], ["123", "123456"], ["bob", "daisy"]]
  • First grab all the values (as Hashes)
  • loop through them with an accumulator ( {} )
  • merge! the values into the accumulator and on conflict append them to an array
  • return the values from the accumulator
  • then append the original keys

This is less explicit than @mudasobwa's answer and relies on the order of the first value to determine the output. eg if :tel came before :email the first 2 elements would have a reversed order

What you mostly should do is to stop writing PHP code with Ruby syntax. Here it's how is to be done in Ruby:

h.map { |k, v| [v[:email], v[:tel], k] }.reduce(&:zip)

or, even better, if you are certain of elements order in nested hashes:

h.map { |k, v| [*v.values, k] }.reduce(&:zip).map(&:flatten)

All the methods map , reduce and zip are thoroughly described in the documentation .

h.map { |k, v| [*v.values_at(:email, :tel), k] }.transpose
  #=> [["abc", "cab"], ["123", "123456"], ["bob", "daisy"]]

The intermediate calculation is as follows.

h.map { |k, v| [*v.values_at(:email, :tel), k] }
  #=> [["abc", "123", "bob"], ["cab", "123456", "daisy"]]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM