Can Ruby Do This?

In Perl, you can create a hash of arbitrary depth like so:

$hash->{foo}->{bar}->{baz} = 123;

I’ve tried this in ruby:

hash[:foo][:bar][:baz] = 123

but I get nil.= errors

I could of course do a check for null at each level, but oy, that’s so
cumbersome. Is there a ruby way?

Thanks!

Interesting little problem.

Just as a quick belt out, you could do something like this:

def infinite_hash
Hash.new{|h,k| h[k] = infinite_hash}
end
=> nil

a = infinite_hash
=> {}

a[:foo][:bar][:baz] = “barry”
=> “barry”

a
=> {:foo=>{:bar=>{:baz=>“barry”}}}

HTH
Daniel

$ irb
irb(main):001:0> hash[:foo][:bar][:baz] = 123
NoMethodError: undefined method []' for nil:NilClass from (irb):1 from /usr/bin/irb:12:in
irb(main):002:0> hash = { :foo => { :bar => { :baz => 123} } }
=> {:foo=>{:bar=>{:baz=>123}}}
irb(main):003:0> hash[:foo][:bar][:baz]
=> 123

I’m not sure if there’s a way that’s a little less verbose…

On Feb 4, 2010, at 5:58 PM, Mr Bubb wrote:

In Perl, you can create a hash of arbitrary depth like so:

$hash->{foo}->{bar}->{baz} = 123;

It is a bit tricky because you have to specify a default
proc that refers to itself (to get the infinite depth):

default = lambda { |h,k| h[k] = Hash.new(&default) }
=> #Proc:[email protected]:3(irb)

top = Hash.new(&default)
=> {}

?> top[0][1][2][3] = 4
=> 4

?> p top
{0=>{1=>{2=>{3=>4}}}}
=> nil

Gary W.

On Thu, Feb 4, 2010 at 3:58 PM, Mr Bubb [email protected] wrote:

I could of course do a check for null at each level, but oy, that’s so
cumbersome. Is there a ruby way?

Yes.

You need to understand Ruby a little better, though.

h = {}
p h[:a]

=> nil

That is, if you have a hash, and you reference an element in the hash
that does not exist, the default behavior is for the hash to return
nil.

So, if you do this:

h = {}
p h[:a][:b]

You will get this:

NoMethodError: undefined method `[]’ for nil:NilClass

This is because h[:a] returns a nil, as you already discovered, and
NilClass does not define an [] method. Since, in Ruby, you are just
chaining method calls, your h[:a][:b][:c] style syntax can only work
if the return element from any given level is an object that provides
an [] method.

Now, consider this example:

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

=> {}

Ah! Now we’re on to something.

h = Hash.new {|h,k| h[k] = {}}
p h[:a][:b]

=> nil

Hmmm.

h = Hash.new {|h,k| h[k] = {}}
p h[:a][:b][:c]

=> NoMethodError: undefined method `[]’ for nil:NilClass

Oh, dang. Do you see what’s happening here? Hash’s new method can
take a block. If it is given a block, then it will call that block
with arguments of the hash, itself, and the key that was given, if
it’s asked to lookup a key that does not exist in the hash. So, h[:a]
ran that block, creating a value at :a => {}. h[:a][:b], though, gets
us back to the default Hash behavior. Still, you see one solution
here, I bet:

h = Hash.new {|h,k| h[k] = Hash.new {|h2,k2| h2[k2] = {}}}
h[:a][:b][:c] = 123
p h[:a][:b][:c]

=> 123

That works. It’s kind of hard to easily read, though. So, you could
rewrite it like this:

h = Hash.new do |h1,k1|
h1[k1] = Hash.new do |h2,k2|
h2[k2] = {}
end
end

That, at least, lets the eye follow along a little better, but it’s
still limited because you must decide what depth of auto
initialization you are going to support ahead of time. If you try
something like h[:a][:b][:c][:d][:e] you will again get that
unpleasant NoMethodError.

However, step back just a moment. The hash initializer accepts a
block. And, if you look at the Hash methods, you’ll see a
Hash#default_proc.

It does just what it sounds like it does – it returns the default
proc, which is what the block is turned into when the new method
captures it and stores it.

So, all you need to do is this:

h = Hash.new {|hash,key| hash[key] = Hash.new(&hash.default_proc)}
h[:a][:b][:c][:d][:e] = 12345
p h[:a][:b][:c][:d][:e]

=> 12345

Hopefully this helps you follow what’s happening with Ruby’s method
calls and Hash initializers.

Kirk H.

Well, in my job (bioinformatics), I only use code like this about a
hundred times a day. I’ve been doing this job for 15 years, and
actually, there is no better way. You must not need to quickly create
data structures from flat files, I guess.

Jim

Marnen Laibow-Koser wrote:

Sure, but there’s probably also a better way that doesn’t involve so
much Hash nesting. Can you explain what you’re using this for?

Mr Bubb wrote:

In Perl, you can create a hash of arbitrary depth like so:

$hash->{foo}->{bar}->{baz} = 123;

I’ve tried this in ruby:

hash[:foo][:bar][:baz] = 123

but I get nil.= errors

I could of course do a check for null at each level, but oy, that’s so
cumbersome. Is there a ruby way?

Sure, but there’s probably also a better way that doesn’t involve so
much Hash nesting. Can you explain what you’re using this for?

Thanks!

Best,
–Â
Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Mr Bubb wrote:

Well, in my job (bioinformatics), I only use code like this about a
hundred times a day. I’ve been doing this job for 15 years, and
actually, there is no better way.

If you’re so sure of that, why are you asking for help? There may be no
better way in Perl, but Ruby is not Perl. :slight_smile:

You must not need to quickly create
data structures from flat files, I guess.

Instead of assuming what my needs are, let’s focus on yours. Nested
hashes may be the best solution in Ruby, but if you provide more
information about your use case, we can give you better help.

Jim

Best,
–Â
Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Marnen Laibow-Koser wrote:

If you’re so sure of that, why are you asking for help? There may be no
better way in Perl, but Ruby is not Perl. :slight_smile:

Is there a hidden camera, or something? It’s not legit to say “I’m a
ruby newbie, is there a way to do x in ruby?” If I had said, hey perl
has one line comments, how do you do one line comments in ruby, would
you answer, well, why do you need one line comments? Jeez.

You must not need to quickly create
data structures from flat files, I guess.

Instead of assuming what my needs are, let’s focus on yours. Nested
hashes may be the best solution in Ruby, but if you provide more
information about your use case, we can give you better help.

This is like the old Bostonian joke:

Question: “Can you tell me how to get to Roxbury?”
Answer: “Whaddaya wanna go there for?”

I guess what I’m saying is, thanks anyway.

On 2/4/2010 7:46 PM, Mr Bubb wrote:

Marnen Laibow-Koser wrote:

If you’re so sure of that, why are you asking for help? There may be no
better way in Perl, but Ruby is not Perl. :slight_smile:

Is there a hidden camera, or something? It’s not legit to say “I’m a
ruby newbie, is there a way to do x in ruby?” If I had said, hey perl
has one line comments, how do you do one line comments in ruby, would
you answer, well, why do you need one line comments? Jeez.

No I wouldn’t, if on the other hand you came to me and asked “How do you
do write a ‘for’ loop in Ruby?”
I would indeed ask “why do you need a ‘for’ loop?” Why ask? Because
while writing something akin to a ‘for’
is possible in Ruby, Ruby has other methods of achieving the same
result which are preferred. Thus,
seeing as your original question has been dealt with by others, it makes
perfect sense to ask why
you would want to do something that just doesn’t seem like the best
Ruby solution in most cases.

On Thu, Feb 4, 2010 at 7:33 PM, Mr Bubb [email protected] wrote:

Well, in my job (bioinformatics), I only use code like this about a
hundred times a day. I’ve been doing this job for 15 years, and
actually, there is no better way. You must not need to quickly create
data structures from flat files, I guess.

Let me know if my detailed explanation didn’t make sense. The long
and the short of it is this:

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

It may well be the best way to do whatever it is you are doing. What,
I think, Marnen was trying to communicate, though, is that there may
be an alternative that performs better in Ruby which would become more
obvious if we understood the actual use a little better.

Just as a quick example, there was a thread last week where someone
was asking about while loop performance, and wondering why a while
loop in Ruby isn’t so fast as he expected.

i = 0
while i < 10000000
i += 1
end

Perfectly legitimate Ruby, and comparable to similar looping
structures in other languages. But, the real answer there is that
there’s another way to do it in Ruby that’s substantially faster.

0.upto(10000000) {|i| #do whatever you need to do with i }

So, if the auto nesting hashes work for you, that’s great. If you
want to bend people’s minds towards perhaps suggesting a better
alternative, though, provide more details. Someone may surprise you
with something unexpected, and superior.

Kirk H.

Walton H. wrote:

On 2/4/2010 7:46 PM, Mr Bubb wrote:

Marnen Laibow-Koser wrote:

If you’re so sure of that, why are you asking for help? There may be no
better way in Perl, but Ruby is not Perl. :slight_smile:

Is there a hidden camera, or something? It’s not legit to say “I’m a
ruby newbie, is there a way to do x in ruby?” If I had said, hey perl
has one line comments, how do you do one line comments in ruby, would
you answer, well, why do you need one line comments? Jeez.

No I wouldn’t, if on the other hand you came to me and asked “How do you
do write a ‘for’ loop in Ruby?”
I would indeed ask “why do you need a ‘for’ loop?” Why ask? Because
while writing something akin to a ‘for’
is possible in Ruby, Ruby has other methods of achieving the same
result which are preferred. Thus,
seeing as your original question has been dealt with by others, it makes
perfect sense to ask why
you would want to do something that just doesn’t seem like the best
Ruby solution in most cases.

Well said. If you want to get the most out of a language, it’s worth
investigating its idioms. Sometimes they are not worth copying, but
more often they are. You can write Fortran in Ruby, or Lisp in C++, but
usually you don’t want to.

Best,
–Â
Marnen Laibow-Koser
http://www.marnen.org
[email protected]

On 05.02.2010 06:01, Kirk H. wrote:

Hash.new(&hash.default_proc)}
IMHO this is the most concise and elegant way to do it in Ruby! I am
surprised you are the only advocate of this.

structures in other languages. But, the real answer there is that
there’s another way to do it in Ruby that’s substantially faster.

0.upto(10000000) {|i| #do whatever you need to do with i }

This one does one iteration more than the original code if I’m not
mistaken. There’s also

10000000.times {|i| #do whatever you need to do with i }

So, if the auto nesting hashes work for you, that’s great. If you
want to bend people’s minds towards perhaps suggesting a better
alternative, though, provide more details. Someone may surprise you
with something unexpected, and superior.

Just as a vague idea: a similar thing could be created with OpenStruct
accesses - saves you a lot of typing of []. Then you could do
hash.foo.bar.baz = 123

class AutoNest
def method_missing(s,*a,&b)
case s
when /\A(\w+)=\z/
class <<self;self;end.class_eval { attr_accessor $1 }
send(s,*a)
when /\A\w+\z/
class <<self;self;end.class_eval { attr_accessor s }
x = self.class.new
send("#{s}=",x)
x
else
super
end
end
end

hash = AutoNest.new
hash.foo.bar.baz = 123
p hash, hash.foo.bar.baz

I’d probably stick with your solution though.

Kind regards

robert

Robert K.:

On 05.02.2010 06:01, Kirk H. wrote:

But, the real answer there is that there’s another
way to do it in Ruby that’s substantially faster.

0.upto(10000000) {|i| #do whatever you need to do with i }

This one does one iteration more than the original
code if I’m not mistaken. There’s also

10000000.times {|i| #do whatever you need to do with i }

A (very) minor nitpick, but – as this thread will hopefully turn
into a newbie magnet – worth noting that the above are not perfectly
equivalent; Integer#upto will go all the way up to 10000000, while
Integer#times will iterate 10000000 times (so actually one less). :slight_smile:

Just as a vague idea: a similar thing could be created with OpenStruct
accesses - saves you a lot of typing of []. Then you could do
hash.foo.bar.baz = 123

If the nesting represents any kind of object structure (which in this
case it seems to do), I’d actually prefer the OpenStruct solution.

— Shot

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs