JP Duffy
JP Duffy

Reputation: 1368

Ruby deep nested hash from array

I need to convert this array:

[:a, :b, :c, :d, :e]

Into this hash:

{:a=>{:b=>{:c=>{:d=>:e}}}}

Unfortunately, I'm missing the Recursion Lobe of the brain. I found BottomlessHash

# http://firedev.com/posts/2015/bottomless-ruby-hash/
class BottomlessHash < Hash
  def initialize
    super &-> h, k { h[k] = self.class.new }
  end
end

While I'm struggling to understand the "pretzel stab", it does the trick if you write it out explicitly,

bhash = BottomlessHash.new
bhash[:a][:b][:c][:d] = :e 
bhash # => {:a=>{:b=>{:c=>{:d=>:e}}}} 

However I can't figure out a way to pass arbitrary values programmatically.

store doesn't work, nor does send("[:a][:b][:c][:d]=", :e)

Upvotes: 1

Views: 299

Answers (4)

sawa
sawa

Reputation: 168091

[:a, :b, :c, :d, :e].reverse_each.inject{|h, k| {k => h}}
# => {:a=>{:b=>{:c=>{:d=>:e}}}}

Upvotes: 2

ndnenkov
ndnenkov

Reputation: 36101

items = [:a, :b, :c, :d, :e]

# Iterative version
def hashify(items)
  items = items.dup

  result = items.pop
  result = {items.pop => result} until items.empty?
  result
end

hashify(items) # => {:a=>{:b=>{:c=>{:d=>:e}}}}

# Recursive version
def hashify(items)
  return items.last if items.size < 2

  *head, penultimate, last = items
  hashify(head + [{penultimate => last}])
end

hashify(items) # => {:a=>{:b=>{:c=>{:d=>:e}}}}

Upvotes: 1

tadman
tadman

Reputation: 211560

What send does is call a method, just one, with zero or more arguments. It can't call multiple methods at once. Your example here:

send("[:a][:b][:c][:d]=", :e)

This is trying to call a method named, literally, [:a][:b][:b][:d]= which doesn't exist, so the send fails.

Now this bit of actual Ruby code:

x[:a][:b][:c][:d] = :e

Becomes interpreted by Ruby as:

x.send(:[], :a).send(:[], :b).send(:[], :c).send(:[]=, :d, :e)

Which is just a really long, ugly way of doing what the original code did. The key here is that each [...] part represents a method call that returns something and then the next part is evaluated against that, or chained on.

Now for the original part, this pretzel-stab:

super &-> h, k { h[k] = self.class.new }

The & means "pass through this Proc as a block argument", as in for a method with the signature:

initialize(&block)

Where &block represents a block, if given, to that method, as in:

Hash.new { |h,k| h[k] = { } }

In a more basic implementation.

The -> h, k { ... } part is traditionally written as:

lambda { |h, k| ... }

Where that's probably more recognizable.

Upvotes: 3

Sebasti&#225;n Palma
Sebasti&#225;n Palma

Reputation: 33420

array = [*:a..:e]
array[0..-2].reverse.reduce(array[-1]) { |b, a| { a => b } }
#  {:a=>{:b=>{:c=>{:d=>:e}}}}

Upvotes: 1

Related Questions