Can Ruby Do This?

M

Mr Bubb

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!
 
D

Daniel N

[Note: parts of this message were removed to make it a legal post.]

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

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!
 
S

Steve Klabnik

[Note: parts of this message were removed to make it a legal post.]

$ 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 `<main>'
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...
 
G

Gary Wright

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) }
=> # said:
top = Hash.new(&default) => {}
?> top[0][1][2][3] = 4
=> 4?> p top
{0=>{1=>{2=>{3=>4}}}}
=> nil



Gary Wright
 
K

Kirk Haines

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?

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 Haines
 
M

Marnen Laibow-Koser

Mr said:
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?

Best,
-- 
Marnen Laibow-Koser
http://www.marnen.org
(e-mail address removed)
 
M

Mr Bubb

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
 
M

Marnen Laibow-Koser

Mr said:
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. :)
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.

Best,
-- 
Marnen Laibow-Koser
http://www.marnen.org
(e-mail address removed)
 
M

Mr Bubb

Marnen said:
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. :)

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.
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.
 
W

Walton Hoops

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.
 
M

Marnen Laibow-Koser

Walton said:
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
(e-mail address removed)
 
K

Kirk Haines

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 Haines
 
R

Robert Klemme

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)}

IMHO this is the most concise and elegant way to do it in Ruby! I am
surprised you are the only advocate of this.
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 }

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,756
Messages
2,569,540
Members
45,025
Latest member
KetoRushACVFitness

Latest Threads

Top