Как сгладить хэш, сделав каждый ключ уникальным значением?
Я хочу взять хэш с вложенными хэшами и массивами и сгладить его в один хеш с уникальными значениями. Я все время пытаюсь подходить к этому под разными углами, но тогда я делаю это более сложным, чем нужно, и теряюсь в том, что происходит.
Пример Хеша источника:
{
"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
, но это могло бы привести к тому, что я должен был бы избежать или усовершенствовать дополнительный код.