5

I'd like to flatten nested hash to an array. For example:

a = {'1'=>{'2'=>{'5'=>{},'6'=>{'8'=>{}}}},'3'=>{},'4'=>{'7'=>{}}}

and result of flatten_nested_hash(a) would be:

["1", "2", "5", "6", "8", "3", "4", "7"]

Finally I wrote some recursive function, but I feel that there must be some easier, non-recursive way of doing it.

My function looks like this:

  def flatten_nested_hash(categories)
    categories.map do |k,v|
      if v == {} 
        k
      else
        [k,flatten_nested_hash(v)]
      end
    end.flatten
  end
3
  • @raam86 PHP and Ruby are completely different languages. There may be a duplicate somewhere, but definitely not that one. Commented Jul 12, 2013 at 11:28
  • @Mischa I completely agree. Must say I was too lazy to generate the comment myself. Fixed. Commented Jul 12, 2013 at 11:34
  • stackoverflow.com/questions/16047106/… Can give another perspective on this question Commented Jul 12, 2013 at 11:34

5 Answers 5

15

Recursive.

def flatten_nested_hash(categories)
  categories.flat_map{|k, v| [k, *flatten_nested_hash(v)]}
end

Defining it on the Hash class.

class Hash
  def flatten_nested; flat_map{|k, v| [k, *v.flatten_nested]} end
end
Sign up to request clarification or add additional context in comments.

Comments

5

With Ruby 2.1 and later you can use refinements to add a method to Hash and expose it only to the modules / classes you need.

module HashRefinements
  refine Hash do
    def flatten_nested
      flat_map { |k, v| [k, *v.flatten_nested] }
    end
  end
end

In your class / module

class MyThing
  using HashRefinements

  ...

  def flatten_categories
    categories.flatten_nested
  end

  ...
end

Comments

3

This is a nested data structure - you're going to have to use some kind of recursive or iterative method to extract all the keys. That's a bit easier than what you have, though:

def deep_extract_keys(hash)
  hash.keys + hash.values.flat_map {|value| deep_extract_keys value }
end

p deep_extract_keys({"1"=>{"2"=>{"5"=>{}, "6"=>{"8"=>{}}}}, "3"=>{}, "4"=>{"7"=>{}}})

This is a breadth-first search, rather than a depth-first search, so the output is:

["1", "3", "4", "2", "5", "6", "8", "7"]

1 Comment

That changes the order of elements.
0

This is not (explicitly) recursive and works only for non-negative integer keys:

a.to_s.scan(/\d+/).map(&:to_i)   # [1, 2, 5, 6, 8, 3, 4, 7]

:-)

Comments

0
module Flattener
  def deep_flatten
    flatten.map do |item|
      case item
      when Hash, Array
        item.deep_flatten
      else
        item
      end
    end.flatten
  end
end

class Hash

  include Flattener

end

class Array

  include Flattener

end

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.