Converting a nested hash into a flat hash

18,883

Solution 1

Another way:

def flat_hash(h,f=[],g={})
  return g.update({ f=>h }) unless h.is_a? Hash
  h.each { |k,r| flat_hash(r,f+[k],g) }
  g
end

h = { :a => { :b => { :c => 1,
                      :d => 2 },
              :e => 3 },
      :f => 4 }

flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}

Solution 2

Very similar to Adiel Mittmann's solution

def flat_hash(h, k = [])
  new_hash = {}
  h.each_pair do |key, val|
    if val.is_a?(Hash)
      new_hash.merge!(flat_hash(val, k + [key]))
    else
      new_hash[k + [key]] = val
    end
  end
  new_hash
end

Edit: Refactored for elegance. Should be almost as fast.

def flat_hash(hash, k = [])
  return {k => hash} unless hash.is_a?(Hash)
  hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) }
end

Solution 3

My attempt:

def flatten_hash(h)
  return { [] => h } unless h.is_a?(Hash)
  Hash[h.map { |a,v1| flatten_hash(v1).map { |b,v2| [[a] + b, v2] } }.flatten(1)]
end

Sorry for the bad variables names, had to fit it in one line.

Solution 4

This is not an attempt to give you the best way to do it, but it is a way :P

def flatten(hash)
  return {[] => hash} if !hash.is_a?(Hash)
  map = {}
  hash.each_pair do |key1, value1|
    flatten(value1).each_pair do |key2, value2|
      map[[key1] + key2] = value2
    end
  end
  return map
end

It works for your example, producing this result:

{[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}

It may not produce the result you expect if there are empty hashes.

Solution 5

A functional approach (see the history for an alternative implementations):

def recursive_flatten(hash)
  hash.flat_map do |key, value|
    if value.is_a?(Hash)
      recursive_flatten(value).map { |ks, v| [[key] + ks, v] } 
    else
      [[[key], value]]
    end
  end.to_h
end
Share:
18,883

Related videos on Youtube

sawa
Author by

sawa

I have published the following two Ruby gems: dom: You can describe HTML/XML DOM structures in Ruby language seamlessly with other parts of Ruby code. Node embedding is described as method chaining, which avoids unnecessary nesting, and confirms to the Rubyistic coding style. manager: Manager generates a user's manual and a developer's chart simultaneously from a single spec file that contains both kinds of information. More precisely, it is a document generator, source code annotation extracter, source code analyzer, class diagram generator, unit test framework, benchmark measurer for alternative implementations of a feature, all in one. Comments and contributions are welcome. I am preparing a web service that is coming out soon.

Updated on July 09, 2022

Comments

  • sawa
    sawa almost 2 years

    This question is the inverse of this question.

    Given a nested hash like

    {
        :a => {
           :b => {:c => 1, :d => 2},
           :e => 3,
        },
        :f => 4,
    }
    

    what is the best way to convert it into a flat hash like

    {
        [:a, :b, :c] => 1,
        [:a, :b, :d] => 2,
        [:a, :e] => 3,
        [:f] => 4,
    }
    
    • Linuxios
      Linuxios over 12 years
      So you want an array of keys in order that lead to a value?
  • sawa
    sawa over 12 years
    This one ran the fastest. Thanks.
  • Niklas B.
    Niklas B. over 12 years
    @sawa: Just a tip for the future: If you want a fast solution, mention this in the question next time. Usually the main criterion in dynamic languages like Python or Ruby is elegancy and conciseness. If you specifically ask for performance as well, you could get much better suited answers :)
  • Kyle
    Kyle over 12 years
    @sawa: refactored for elegance.
  • sawa
    sawa over 12 years
    @Kyle One thing that was good about your earlier code was that it can be redefined as a method on Hash. The rewritten one cannot.
  • rusty
    rusty over 9 years
    I know Ruby doesn't necessarily support TCO out of the box, but if you didn't return g here, would this be tail-call optimized?
  • Cary Swoveland
    Cary Swoveland over 9 years
    I've heard the term "tail-call optimized" but I have no idea what it means. Perhaps a computer scientist can answer your question.
  • Stefan Majewsky
    Stefan Majewsky over 9 years
    You can't optimize the tail call here IMO since you have a cascaded recursion.
  • Cary Swoveland
    Cary Swoveland about 3 years
    @Niklas, perhaps, but most Ruby-readers know sawa is a speed-freak.