2

I want to write a method that can receive a nested hash and return a nested array of two dimensional arrays.

hash_to_a({1=>2, 2=>3, {3=>4, 5=>6}=>7})  # [[1, 2], [2, 3], [[[3, 4], [5, 6]], 7]]
hash_to_a({{5=>{1=>3, 2=>4}}=>{7=>8}})    # [[[[5, [[1, 3], [2, 4]]]], [[7, 8]]]]
hash_to_a({5=>{1=>3, 2=>4}})              # [[5, [[1, 3], [2, 4]]]]

So far i've got this:

def hash_to_a(a_hash)
  result = []
  a_hash.each { |k, v|
    if k.is_a?(Hash) 
      result << k.to_a
    else
      result << k
    end
    if v.is_a?(Hash)
      result << v.to_a
    else
      result << v
    end
  }
  result
end

The results are of course not desirable

hash_to_a({1=>2, 2=>3, {3=>4, 5=>6}=>7})  # [1, 2, 2, 3, [[3, 4], [5, 6]], 7]
hash_to_a({{5=>{1=>3, 2=>4}}=>{7=>8}})    # [[[5, {1=>3, 2=>4}]], [[7, 8]]]
hash_to_a({5=>{1=>3, 2=>4}})              # [5, [[1, 3], [2, 4]]]

How can I accomplish the wanted results? I also tried recusion but just can't wrap my mind around this problem.

0

4 Answers 4

3

Start with your simplest case, with no nesting.

def hash_to_a(hash)  # { 3 => 4 }
  hash.map do |k, v| # 3, 4
    [k, v]           # [3, 4] 
  end
end

Now the problem is that for the more complicated cases we don't want to return the key or value if it's a hash, we want to convert it from a hash first.

def hash_to_a(hash)
  hash.map do |k, v|
    [hash_to_a(k), hash_to_a(v)]
  end
end

This will blow up with our simple case because 3 does not have a method #map. This is because we're not handling the base case yet. Just takes a line of code to keep from trying to map things that aren't hashes. We want it to behave just like our first try: do nothing but return the key or the value.

def hash_to_a(object)
  return object unless object.is_a? Hash
  object.map do |k, v|
    [hash_to_a(k), hash_to_a(v)]
  end
end

And we're done.

hash_to_a({1=>2, 2=>3, {3=>4, 5=>6}=>7})  # [[1, 2], [2, 3], [[[3, 4], [5, 6]], 7]]
hash_to_a({{5=>{1=>3, 2=>4}}=>{7=>8}})    # [[[[5, [[1, 3], [2, 4]]]], [[7, 8]]]]
hash_to_a({5=>{1=>3, 2=>4}})              # [[5, [[1, 3], [2, 4]]]]
Sign up to request clarification or add additional context in comments.

Comments

3

You can use recursion

def h_to_a(h)
  h.map { |k,v| [k.is_a?(Hash) ? h_to_a(k) : k, v.is_a?(Hash) ? h_to_a(v) : v] }
end

h_to_a({ 1=>2, 2=>3, { 3=>4, 5=>6 }=>7 })
  #=> [[1, 2], [2, 3], [[[3, 4], [5, 6]], 7]]
h_to_a({ { 5=>{ 1=>3, 2=>4 } }=>{ 7=>8 } })
  #=> [[[[5, [[1, 3], [2, 4]]]], [[7, 8]]]]
h_to_a({ 5=>{ 1=>3, 2=>4 } })
  #=> [[5, [[1, 3], [2, 4]]]]

This obviously works with any level of nesting.

Comments

2

What about this one:

def deep_to_array(hash)
  return hash unless hash.is_a?(Hash)
   array = hash.to_a
   array.each_with_index do |(k,v), index|
       array[index][0] = deep_to_array(k)
       array[index][1] = deep_to_array(v)
     end
  array
end

Or a concise one:

def deep_to_array2(hash)
  return hash unless hash.is_a?(Hash)
  hash.map do |k,v|
    [deep_to_array2(k), deep_to_array2(v)]
  end
end

Example:

deep_to_array(({1=>2, 2=>3, {3=>4, 5=>6}=>7}))
=> [[1, 2], [2, 3], [[[3, 4], [5, 6]], 7]] 

Comments

0

Yet another variation (using builtin Hash#to_a)

As class method:

class Hash
  def flatten_to_a
    to_a.map {|i| i.is_a?(Array) ? i.map {|i| i.is_a?(Hash) ? i.flatten_to_a : i} : i}
  end
end

As standalone method:

def f2a hash
  return hash unless Hash === hash
  hash.to_a.map {|i| i.is_a?(Array) ? i.map {|i| i.is_a?(Hash) ? i.flatten_to_a : i} : i}
end

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.