Превратить вложенный хеш в двумерный массив в Ruby

Я хочу написать метод, который может получить вложенный хэш и вернуть вложенный массив двумерных массивов.

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

Пока у меня есть это:

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

Результаты, конечно, не желательны

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

Как я могу достичь желаемых результатов? Я также пытался отречься, но просто не могу обдумать эту проблему.

4 ответа

Решение

Начните с самого простого случая, без вложенности.

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

Теперь проблема в том, что для более сложных случаев мы не хотим возвращать ключ или значение, если это хеш, мы сначала хотим преобразовать его из хеша.

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

Это взорвется в нашем простом случае, потому что 3 не имеет метода #map, Это потому, что мы пока не рассматриваем базовый вариант. Просто берет строку кода, чтобы не пытаться отображать вещи, которые не являются хэшами. Мы хотим, чтобы он вел себя так же, как наша первая попытка: ничего не делайте, только верните ключ или значение.

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

И мы сделали.

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

Вы можете использовать рекурсию

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

Это очевидно работает с любым уровнем вложенности.

Что насчет этого:

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

Или краткий:

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

Пример:

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

Еще один вариант (с использованием встроенного Hash#to_a)

Как метод класса:

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

Как автономный метод:

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
Другие вопросы по тегам