Как сгладить хэш, сделав каждый ключ уникальным значением?

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

Пример Хеша источника:

{
  "Name" => "Kim Kones",
  "License Number" => "54321",
  "Details" => {
    "Name" => "Kones, Kim",
    "Licenses" => [
      {
        "License Type" => "PT",
        "License Number" => "54321"
      },
      {
        "License Type" => "Temp",
        "License Number" => "T123"
      },
      {
        "License Type" => "AP",
        "License Number" => "A666",
        "Expiration Date" => "12/31/2020"
      }
    ]
  }
}

Пример желаемого хэша:

{
  "Name" => "Kim Kones",
  "License Number" => "54321",
  "Details_Name" => "Kones, Kim",
  "Details_Licenses_1_License Type" => "PT",
  "Details_Licenses_1_License Number" => "54321",
  "Details_Licenses_2_License Type" => "Temp",
  "Details_Licenses_2_License Number" => "T123",
  "Details_Licenses_3_License Type" => "AP",
  "Details_Licenses_3_License Number" => "A666",
  "Details_Licenses_3_Expiration Date" => "12/31/2020"
}

Для чего это стоит, вот моя последняя попытка, прежде чем отказаться.

def flattify(hashy)
    temp = {}
    hashy.each do |key, val|
        if val.is_a? String
            temp["#{key}"] = val
        elsif val.is_a? Hash
            temp.merge(rename val, key, "")
        elsif val.is_a? Array
            temp["#{key}"] = enumerate val, key
        else
        end
        print "=> #{temp}\n"
    end
    return temp
end

def rename (hashy, str, n)
    temp = {}
    hashy.each do |key, val|
        if val.is_a? String
            temp["#{key}#{n}"] = val
        elsif val.is_a? Hash
            val.each do |k, v|
                temp["#{key}_#{k}#{n}"] = v
            end
        elsif val.is_a? Array
            temp["#{key}"] = enumerate val, key
        else

        end
    end
    return flattify temp
end

def enumerate (ary, str)
    temp = {}
    i = 1
    ary.each do |x|
        temp["#{str}#{i}"] = x
        i += 1
    end
    return flattify temp
end

Ответ 1

Интересный вопрос!

Теория

Здесь рекурсивный метод для анализа ваших данных.

  • Он отслеживает, какие ключи и индексы он нашел.
  • Он добавляет их в массив tmp.
  • Как только листовой объект найден, он записывается в хеш как значение, с присоединенным ключом tmp.
  • Этот небольшой хэш затем рекурсивно сливается с основным хешем.

Код

def recursive_parsing(object, tmp = [])
  case object
  when Array
    object.each.with_index(1).with_object({}) do |(element, i), result|
      result.merge! recursive_parsing(element, tmp + [i])
    end
  when Hash
    object.each_with_object({}) do |(key, value), result|
      result.merge! recursive_parsing(value, tmp + [key])
    end
  else
    { tmp.join('_') => object }
  end
end

В качестве примера:

require 'pp'
pp recursive_parsing(data)
# {"Name"=>"Kim Kones",
#  "License Number"=>"54321",
#  "Details_Name"=>"Kones, Kim",
#  "Details_Licenses_1_License Type"=>"PT",
#  "Details_Licenses_1_License Number"=>"54321",
#  "Details_Licenses_2_License Type"=>"Temp",
#  "Details_Licenses_2_License Number"=>"T123",
#  "Details_Licenses_3_License Type"=>"AP",
#  "Details_Licenses_3_License Number"=>"A666",
#  "Details_Licenses_3_Expiration Date"=>"12/31/2020"}

Отладка

Здесь представлена ​​модифицированная версия с отладкой старой школы. Это может помочь вам понять, что происходит:

def recursive_parsing(object, tmp = [], indent="")
  puts "#{indent}Parsing #{object.inspect}, with tmp=#{tmp.inspect}"
  result = case object
  when Array
    puts "#{indent} It an array! Let parse every element:"
    object.each_with_object({}).with_index(1) do |(element, result), i|
      result.merge! recursive_parsing(element, tmp + [i], indent + "  ")
    end
  when Hash
    puts "#{indent} It a hash! Let parse every key,value pair:"
    object.each_with_object({}) do |(key, value), result|
      result.merge! recursive_parsing(value, tmp + [key], indent + "  ")
    end
  else
    puts "#{indent} It a leaf! Let return a hash"
    { tmp.join('_') => object }
  end
  puts "#{indent} Returning #{result.inspect}\n"
  result
end

При вызове с recursive_parsing([{a: 'foo', b: 'bar'}, {c: 'baz'}]) он отображает:

Parsing [{:a=>"foo", :b=>"bar"}, {:c=>"baz"}], with tmp=[]
 It an array! Let parse every element:
  Parsing {:a=>"foo", :b=>"bar"}, with tmp=[1]
   It a hash! Let parse every key,value pair:
    Parsing "foo", with tmp=[1, :a]
     It a leaf! Let return a hash
     Returning {"1_a"=>"foo"}
    Parsing "bar", with tmp=[1, :b]
     It a leaf! Let return a hash
     Returning {"1_b"=>"bar"}
   Returning {"1_a"=>"foo", "1_b"=>"bar"}
  Parsing {:c=>"baz"}, with tmp=[2]
   It a hash! Let parse every key,value pair:
    Parsing "baz", with tmp=[2, :c]
     It a leaf! Let return a hash
     Returning {"2_c"=>"baz"}
   Returning {"2_c"=>"baz"}
 Returning {"1_a"=>"foo", "1_b"=>"bar", "2_c"=>"baz"}

Ответ 2

В отличие от других, у меня нет любви к each_with_object:-). Но мне нравится передавать единый хеш-результат, поэтому мне не нужно снова и снова сливать хэши-ремиксы.

def flattify(value, result = {}, path = [])
  case value
  when Array
    value.each.with_index(1) do |v, i|
      flattify(v, result, path + [i])
    end
  when Hash
    value.each do |k, v|
      flattify(v, result, path + [k])
    end
  else
    result[path.join("_")] = value
  end
  result
end

(Некоторые детали, взятые у Эрика, см. комментарии)

Ответ 3

Нерекурсивный подход, используя BFS с массивом в качестве очереди. Я сохраняю пары ключ-значение, в которых значение не является массивом/хешем, а также помещает массив/хэш-содержимое в очередь (с комбинированными ключами). Поворот массивов в хеши (["a", "b"] & mapsto; {1=>"a", 2=>"b"}), так как это было аккуратно.

def flattify(hash)
  (q = hash.to_a).select { |key, value|
    value = (1..value.size).zip(value).to_h if value.is_a? Array
    !value.is_a?(Hash) || !value.each { |k, v| q << ["#{key}_#{k}", v] }
  }.to_h
end

Одна вещь, которая мне нравится в этом, - это сочетание клавиш как "#{key}_#{k}". В моем другом решении я мог бы также использовать строку path = '' и расширять ее с помощью path + "_" + k, но это могло бы привести к тому, что я должен был бы избежать или усовершенствовать дополнительный код.