5
\$\begingroup\$

I need to generate a Ruby hash that looks like this:

{ "children" => [
  { "children" => [
    { "children" => [
      { "children" => [] }
    ]}
  ]}
]}

... for an arbitrary level of nesting. So far the best I've come up with is:

def nested_hash(levels)
  return {} if levels < 1
  root = { 'children' => [] }
  children = root['children']
  (levels - 1).times { children = (children << { 'children' => [] }).first['children'] }
  root
end

This doesn't seem particularly terse or elegant. Can anyone offer suggestions on making this more terse or elegant?

\$\endgroup\$
  • \$\begingroup\$ why do you need arrays with a single element? \$\endgroup\$ – tokland Oct 2 '12 at 7:48
4
\$\begingroup\$

Recursion perhaps?

def nested_hash(levels)
  return if levels <= 0
  { "children" => [ nested_hash(levels - 1) ].compact }
end

Or it could be

def nested_hash(levels)
  return nil if levels <= 0
  array = nested_hash(levels - 1)
  { "children" => array.nil? ? [] : [array] }
end

if you prefer handling the nil upfront, instead of removing it with compact

In either case, nested_hash(3) will get you

{"children"=>[
  {"children"=>[
    {"children"=>[]}
  ]}
]}

Note that unlike yours, these ones will return nil when levels is zero or less. So you'll want to do the {} fallback elsewhere, e.g. hsh = nested_hash(x) || {}.

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$
def nested_hash(levels)
  return {} if levels < 1
  array = Array.new(levels, "children")
  array.reduce(nil) { |memo, item| { item => [memo].compact } }
end

Array.new

Array.new(levels, "children") creates a levels-sized Array of "children" Strings.

Example:

Array.new(3, "foo")
#=> ["foo", "foo", "foo"]

reduce

reduce (which is an alias for inject) iterates over all elements of the Array, and accumulates the result in memo. We seed memowith nil.

Example:

The block passed to reduce looks like this in the first iteration:

{ "children" => [nil].compact }
#=> { "children" => [] }

memo now holds that result.

The block in the second iteration:

{ "children" => [{ "children" => [] }].compact }
#=> { "children" => [{ "children" => [] }] }
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I just realized that instead of doing Array.new(levels, "children" you could also simply do array = ["children"] * levels \$\endgroup\$ – David Österreicher Oct 2 '12 at 1:06
  • \$\begingroup\$ Clever. In super-terse mode: def nested_hash(levels); levels < 1 ? nil : (["children"] * levels).reduce(nil) { |memo, item| { item => [memo].compact } }; end \$\endgroup\$ – Flambino Oct 2 '12 at 1:16
  • \$\begingroup\$ for me "terse" implies "readability" that's why I've expanded it a little bit \$\endgroup\$ – David Österreicher Oct 2 '12 at 13:56
  • \$\begingroup\$ Of course - by "super-terse" I meant "waaay to compacted to actually be useful"; wasn't being serious about writing code like that \$\endgroup\$ – Flambino Oct 2 '12 at 18:04
3
\$\begingroup\$
h = Hash.new { |hash, key| hash[key] = [Hash.new(&hash.default_proc)] }

Not sure if this fits your needs, but this creates a hash that if accessed with #[] and the key doesn't exist, automatically creates that key with the value being an array with 1 element: another hash which behaves identically.

So initially the hash is empty:

>> h = Hash.new { |hash, key| hash[key] = [Hash.new(&hash.default_proc)] }
=> {}

But will create the required structure when accessed.

>> h[:children][0][:children][0][:children]
=> [{}]
>> h
=> {:children=>[{:children=>[{:children=>[{}]}]}]}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks - that's a very neat implementation, but doesn't do what I want (when fed into a recursive parser, it would eventually cause a stack overflow). \$\endgroup\$ – Duncan Bayne Oct 10 '12 at 2:51
3
\$\begingroup\$

You can use the Ruby Gem xkeys which extends the hash to have nesting capabilities:

require 'xkeys'

h = {}.extend XKeys::Auto
h['children', 0, 'children', 0, 'children', 0, 'children'] = []

OR, maybe you have something like this...

path = ['children', 0, 'children', 0, 'children', 0, 'children']
h[*path] = []
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.