Сглаживание вложенного хэша в один хэш с Ruby/Rails
Я хочу "сгладить" (не в классическом смысле .flatten
) вниз хэш с различными уровнями глубины, например:
{
:foo => "bar",
:hello => {
:world => "Hello World",
:bro => "What up dude?",
},
:a => {
:b => {
:c => "d"
}
}
}
вниз в хэш с одним уровнем, и все вложенные ключи объединены в одну строку, поэтому он станет следующим:
{
:foo => "bar",
:"hello.world" => "Hello World",
:"hello.bro" => "What up dude?",
:"a.b.c" => "d"
}
но я не могу придумать, как это сделать. Это немного напоминает вспомогательные функции deep_
, которые Rails добавляет к хэшам, но не совсем то же самое. Я знаю, что рекурсия была бы здесь, но я никогда не писал рекурсивную функцию в Ruby.
Ответы
Ответ 1
Вы можете сделать это:
def flatten_hash(hash)
hash.each_with_object({}) do |(k, v), h|
if v.is_a? Hash
flatten_hash(v).map do |h_k, h_v|
h["#{k}.#{h_k}".to_sym] = h_v
end
else
h[k] = v
end
end
end
flatten_hash(:foo => "bar",
:hello => {
:world => "Hello World",
:bro => "What up dude?",
},
:a => {
:b => {
:c => "d"
}
})
# => {:foo=>"bar",
# => :"hello.world"=>"Hello World",
# => :"hello.bro"=>"What up dude?",
# => :"a.b.c"=>"d"}
Ответ 2
Потому что я люблю Enumerable#reduce
и ненавижу строки, видимо:
def flatten_hash(param, prefix=nil)
param.each_pair.reduce({}) do |a, (k, v)|
v.is_a?(Hash) ? a.merge(flatten_hash(v, "#{prefix}#{k}.")) : a.merge("#{prefix}#{k}".to_sym => v)
end
end
irb(main):118:0> flatten_hash(hash)
=> {:foo=>"bar", :"hello.world"=>"Hello World", :"hello.bro"=>"What up dude?", :"a.b.c"=>"d"}
Ответ 3
Главный проголосовавший ответ здесь не будет полностью сглаживать объект, он не сглаживает массивы. Я скорректировал это ниже и предложил сравнение:
x = { x: 0, y: { x: 1 }, z: [ { y: 0, x: 2 }, 4 ] }
def top_voter_function ( hash )
hash.each_with_object( {} ) do |( k, v ), h|
if v.is_a? Hash
top_voter_function( v ).map do |h_k, h_v|
h[ "#{k}.#{h_k}".to_sym ] = h_v
end
else
h[k] = v
end
end
end
def better_function ( a_el, a_k = nil )
result = {}
a_el = a_el.as_json
a_el.map do |k, v|
k = "#{a_k}.#{k}" if a_k.present?
result.merge!( [Hash, Array].include?( v.class ) ? better_function( v, k ) : ( { k => v } ) )
end if a_el.is_a?( Hash )
a_el.uniq.each_with_index do |o, i|
i = "#{a_k}.#{i}" if a_k.present?
result.merge!( [Hash, Array].include?( o.class ) ? better_function( o, i ) : ( { i => o } ) )
end if a_el.is_a?( Array )
result
end
top_voter_function( x ) #=> {:x=>0, :"y.x"=>1, :z=>[{:y=>0, :x=>2}, 4]}
better_function( x ) #=> {"x"=>0, "y.x"=>1, "z.0.y"=>0, "z.0.x"=>2, "z.1"=>4}
Я ценю, что этот вопрос немного стар, я пошел искать онлайн для сравнения моего кода выше, и это то, что я нашел. Он отлично работает при использовании событий для службы аналитики, такой как Mixpanel.
Ответ 4
Или, если вы хотите, чтобы версия с исправленной обезьяной или ответ Uri отправились на your_hash.flatten_to_root
:
class Hash
def flatten_to_root
self.each_with_object({}) do |(k, v), h|
if v.is_a? Hash
v.flatten_to_root.map do |h_k, h_v|
h["#{k}.#{h_k}".to_sym] = h_v
end
else
h[k] = v
end
end
end
end