Nested hash with arrays for default value

G

Glen Holcomb

I'm trying to find a "nice" way to make a nested hash with an empty array a=
s
the default "leaf" value.

Basically I'd like to be able to make an assignment as follows:

data[2][3][4][5] << 3

I can get close but I can't get it right. The data is going to be coming
straight out of a log so I can't really build the hash ahead of time.

--=20
"Hey brother Christian with your high and mighty errand, Your actions speak
so loud, I can=92t hear a word you=92re saying."

-Greg Graffin (Bad Religion)
 
A

Aldric Giacomoni

Glen said:
I'm trying to find a "nice" way to make a nested hash with an empty
array as
the default "leaf" value.

Basically I'd like to be able to make an assignment as follows:

data[2][3][4][5] << 3

I can get close but I can't get it right. The data is going to be
coming
straight out of a log so I can't really build the hash ahead of time.

--
"Hey brother Christian with your high and mighty errand, Your actions
speak
so loud, I can’t hear a word you’re saying."

-Greg Graffin (Bad Religion)

An empty array?
Well.. You can try this:
http://trevoke.net/blog/2009/11/06/auto-vivifying-hashes-in-ruby/
As indicated, I didn't come up with this, and it'll take care of
creating the hashes for you. You can probably do a check : if nil, then
create array.. Then add to array.
 
J

Jesús Gabriel y Galán

I'm trying to find a "nice" way to make a nested hash with an empty array= as
the default "leaf" value.

Basically I'd like to be able to make an assignment as follows:

=A0 =A0data[2][3][4][5] << 3

I can get close but I can't get it right. =A0The data is going to be comi= ng
straight out of a log so I can't really build the hash ahead of time.

--
"Hey brother Christian with your high and mighty errand, Your actions spe= ak
so loud, I can=92t hear a word you=92re saying."

-Greg Graffin (Bad Religion)

I've not tested this too much, but what I tried was to setup a proxy
object that would insert a hash if the [] method is called on it, or
an array if the << method was called:

class ProxyDefault
def initialize hash, key
@hash =3D hash
@key =3D key
end
=09
def [](key)
@hash[@key] =3D Hash.new {|hash,key| ProxyDefault.new(hash, key)}
@hash[@key][key]
end
=09
def << value
@hash[@key] =3D []
@hash[@key] << value
end
end
=09
h =3D Hash.new {|hash,value| ProxyDefault.new(hash, value)}

h[1][2][3] << "value"

p h
p h[1][2][3]

/temp$ ruby nested_hash_array.rb
{1=3D>{2=3D>{3=3D>["value"]}}}
["value"]

Hope this helps,

Jesus.
 
G

Glen Holcomb

2010/1/25 Jes=FAs Gabriel y Gal=E1n said:
I'm trying to find a "nice" way to make a nested hash with an empty arr=
ay
as
the default "leaf" value.

Basically I'd like to be able to make an assignment as follows:

data[2][3][4][5] << 3

I can get close but I can't get it right. The data is going to be comi= ng
straight out of a log so I can't really build the hash ahead of time.

--
"Hey brother Christian with your high and mighty errand, Your actions speak
so loud, I can=92t hear a word you=92re saying."

-Greg Graffin (Bad Religion)

I've not tested this too much, but what I tried was to setup a proxy
object that would insert a hash if the [] method is called on it, or
an array if the << method was called:

class ProxyDefault
def initialize hash, key
@hash =3D hash
@key =3D key
end

def [](key)
@hash[@key] =3D Hash.new {|hash,key| ProxyDefault.new(hash= ,
key)}
@hash[@key][key]
end

def << value
@hash[@key] =3D []
@hash[@key] << value
end
end

h =3D Hash.new {|hash,value| ProxyDefault.new(hash, value)}

h[1][2][3] << "value"

p h
p h[1][2][3]

/temp$ ruby nested_hash_array.rb
{1=3D>{2=3D>{3=3D>["value"]}}}
["value"]

Hope this helps,

Jesus.
Thanks Jesus,

I'll play around with your solution. I have the following:

data =3D Hash.new { |l, k| l[k] =3D Hash.new { |l, k| l[k] =3D Hash.new { |=
l, k|
l[k] =3D Hash.new([]) }}}

Believe me I know it's ugly and not in any way flexible. Plus it behaves i=
n
ways which make me uncomfortable when I try to print the contents.

--=20
"Hey brother Christian with your high and mighty errand, Your actions speak
so loud, I can=92t hear a word you=92re saying."

-Greg Graffin (Bad Religion)
 
G

Gary Wright

I'll play around with your solution. I have the following:

data = Hash.new { |l, k| l[k] = Hash.new { |l, k| l[k] = Hash.new { |l, k|
l[k] = Hash.new([]) }}}

I'm assuming you want 'infinite' depth. Consider:

default = lambda { |h,k| h[k] = Hash.new(&default) }
top = Hash.new(&default)

Gary Wright
 
J

Jesús Gabriel y Galán

I'll play around with your solution. =A0I have the following:

data =3D Hash.new { |l, k| l[k] =3D Hash.new { |l, k| l[k] =3D Hash.new = { |l, k|
l[k] =3D Hash.new([]) }}}

I'm assuming you want 'infinite' depth. =A0Consider:

default =3D lambda { |h,k| h[k] =3D Hash.new(&default) }
top =3D Hash.new(&default)

The problem is that he wants the leaves of the hash to be arrays, and
not hashes.

Jesus.
 
G

Glen Holcomb

2010/1/25 Jes=FAs Gabriel y Gal=E1n said:
I'll play around with your solution. I have the following:

data =3D Hash.new { |l, k| l[k] =3D Hash.new { |l, k| l[k] =3D Hash.ne=
w { |l,
k|
l[k] =3D Hash.new([]) }}}

I'm assuming you want 'infinite' depth. Consider:

default =3D lambda { |h,k| h[k] =3D Hash.new(&default) }
top =3D Hash.new(&default)

The problem is that he wants the leaves of the hash to be arrays, and
not hashes.

Jesus.
Exactly, infinite depth would be nice as it would make a more temporally
portable solution. The proxy looks to be working great. I am a bit
confused as to why the << method in the proxy doesn't overwrite a leaf with
a new array though. I'm not complaining as it works the way I want it to,
I'm just perplexed.

Thanks Jesus.

--=20
"Hey brother Christian with your high and mighty errand, Your actions speak
so loud, I can=92t hear a word you=92re saying."

-Greg Graffin (Bad Religion)
 
G

Gary Wright

Exactly, infinite depth would be nice as it would make a more = temporally
portable solution. The proxy looks to be working great. I am a bit
confused as to why the << method in the proxy doesn't overwrite a leaf = with
a new array though. I'm not complaining as it works the way I want it = to,
I'm just perplexed.

Oops. Sorry for the confusion. The trick with the proxy is that the
first time << is called on the proxy, it replaces itself with an empty
array. Further lookups will return the array and not the original =
proxy.

Gary Wright=
 
J

Jesús Gabriel y Galán

Exactly, infinite depth would be nice as it would make a more temporally
portable solution.

With it, you have infinite depth, until in a branch you decide to stop
by appending (<<) a value.
Then you fix the depth of that branch.
The proxy looks to be working great. =A0I am a bit
confused as to why the << method in the proxy doesn't overwrite a leaf wi= th
a new array though. =A0I'm not complaining as it works the way I want it = to,
I'm just perplexed.

When you access h[1][2][3], a proxy object is inserted in the hash for
that key. The proxy object remembers the hash and the key. When you
call << on the proxy object, it replaces itself in the hash with an
empty array, to which it appends the value. So further calls to
h[1][2][3] will return that array and no proxy objects anymore. It
works the same for the upper levels: calling h[1] inserts a proxy in
the hash. When you call [] on it (for example h[1][2]) it replaces
h[1] with a hash.

Maybe this clarifies a bit more:


/temp$ cat nested_hash_array.rb && ruby nested_hash_array.rb
class ProxyDefault
def initialize hash, key
@hash =3D hash
@key =3D key
end
=09
def [](key)
puts "the hash is: #{@hash.inspect} when calling [] on the proxy object"
@hash[@key] =3D Hash.new {|hash,key| ProxyDefault.new(hash, key)}
puts "the hash is: #{@hash.inspect} after replacing the proxy with a hash=
"
@hash[@key][key]
end
=09
def << value
puts "the hash is: #{@hash.inspect} when calling << on the proxy object"
@hash[@key] =3D [value]
puts "the hash is: #{@hash.inspect} after replacing the proxy with an arr=
ay"
@hash[@key] =09
end
end
=09
h =3D Hash.new {|hash,value| ProxyDefault.new(hash, value)}

h[1][2] << "value"

p h
p h[1][2]

the hash is: {} when calling [] on the proxy object
the hash is: {1=3D>{}} after replacing the proxy with a hash
the hash is: {} when calling << on the proxy object
the hash is: {2=3D>["value"]} after replacing the proxy with an array
{1=3D>{2=3D>["value"]}}
["value"]

Jesus.
 
G

Glen Holcomb

2010/1/26 Jes=FAs Gabriel y Gal=E1n said:
Exactly, infinite depth would be nice as it would make a more temporall= y
portable solution.

With it, you have infinite depth, until in a branch you decide to stop
by appending (<<) a value.
Then you fix the depth of that branch.
The proxy looks to be working great. I am a bit
confused as to why the << method in the proxy doesn't overwrite a leaf with
a new array though. I'm not complaining as it works the way I want it to,
I'm just perplexed.

When you access h[1][2][3], a proxy object is inserted in the hash for
that key. The proxy object remembers the hash and the key. When you
call << on the proxy object, it replaces itself in the hash with an
empty array, to which it appends the value. So further calls to
h[1][2][3] will return that array and no proxy objects anymore. It
works the same for the upper levels: calling h[1] inserts a proxy in
the hash. When you call [] on it (for example h[1][2]) it replaces
h[1] with a hash.

Maybe this clarifies a bit more:


/temp$ cat nested_hash_array.rb && ruby nested_hash_array.rb
class ProxyDefault
def initialize hash, key
@hash =3D hash
@key =3D key
end

def [](key)
puts "the hash is: #{@hash.inspect} when calling [] on th= e
proxy object"
@hash[@key] =3D Hash.new {|hash,key| ProxyDefault.new(has= h,
key)}
puts "the hash is: #{@hash.inspect} after replacing the
proxy with a hash"
@hash[@key][key]
end

def << value
puts "the hash is: #{@hash.inspect} when calling << on the
proxy object"
@hash[@key] =3D [value]
puts "the hash is: #{@hash.inspect} after replacing the
proxy with an array"
@hash[@key]
end
end

h =3D Hash.new {|hash,value| ProxyDefault.new(hash, value)}

h[1][2] << "value"

p h
p h[1][2]

the hash is: {} when calling [] on the proxy object
the hash is: {1=3D>{}} after replacing the proxy with a hash
the hash is: {} when calling << on the proxy object
the hash is: {2=3D>["value"]} after replacing the proxy with an array
{1=3D>{2=3D>["value"]}}
["value"]

Jesus.
Sorry, I should have been more specific when stating my confusion. I am
confused as to why appending a second item into a leaf results in a
multi-item array rather than a new array with only the second item.

data[1][2][3] << 4
data[1][2][3] << 5

yields
{1=3D>{2=3D>{3=3D>[4,5]}}}
looing at the proxy I was expecting
{1=3D>{2=3D>{3=3D>[5]}}}

The behavior I'm seeing is what I want I just didn't expect it. From the
code it looks like << assigns an array to the key then appends a value. I
was expecting that to overwrite the array created with the first << call at
that level with a new single item array.

--=20
"Hey brother Christian with your high and mighty errand, Your actions speak
so loud, I can=92t hear a word you=92re saying."

-Greg Graffin (Bad Religion)
 
J

Jesús Gabriel y Galán

2010/1/26 Jes=FAs Gabriel y Gal=E1n said:
Exactly, infinite depth would be nice as it would make a more temporal= ly
portable solution.

With it, you have infinite depth, until in a branch you decide to stop
by appending (<<) a value.
Then you fix the depth of that branch.
The proxy looks to be working great. =A0I am a bit
confused as to why the << method in the proxy doesn't overwrite a leaf with
a new array though. =A0I'm not complaining as it works the way I want =
it
to,
I'm just perplexed.

When you access h[1][2][3], a proxy object is inserted in the hash for
that key. The proxy object remembers the hash and the key. When you
call << on the proxy object, it replaces itself in the hash with an
empty array, to which it appends the value. So further calls to
h[1][2][3] will return that array and no proxy objects anymore. It
works the same for the upper levels: calling h[1] inserts a proxy in
the hash. When you call [] on it (for example h[1][2]) it replaces
h[1] with a hash.

Maybe this clarifies a bit more:


/temp$ cat nested_hash_array.rb && ruby nested_hash_array.rb
class ProxyDefault
=A0 =A0 =A0 =A0def initialize hash, key
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0@hash =3D hash
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0@key =3D key
=A0 =A0 =A0 =A0end

=A0 =A0 =A0 =A0def [](key)
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 puts "the hash is: #{@hash.inspect} when= calling [] on the
proxy object"
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 @hash[@key] =3D Hash.new {|hash,key| Pro= xyDefault.new(hash,
key)}
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 puts "the hash is: #{@hash.inspect} afte= r replacing the
proxy with a hash"
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0@hash[@key][key]
=A0 =A0 =A0 =A0end

=A0 =A0 =A0 =A0def << value
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0puts "the hash is: #{@hash.inspect} when = calling << on the
proxy object"
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0@hash[@key] =3D [value]
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0puts "the hash is: #{@hash.inspect} after= replacing the
proxy with an array"
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0@hash[@key]
=A0 =A0 =A0 =A0 end
end

h =3D Hash.new {|hash,value| ProxyDefault.new(hash, value)}

h[1][2] << "value"

p h
p h[1][2]

the hash is: {} when calling [] on the proxy object
the hash is: {1=3D>{}} after replacing the proxy with a hash
the hash is: {} when calling << on the proxy object
the hash is: {2=3D>["value"]} after replacing the proxy with an array
{1=3D>{2=3D>["value"]}}
["value"]

Jesus.
Sorry, I should have been more specific when stating my confusion. =A0I a= m
confused as to why appending a second item into a leaf results in a
multi-item array rather than a new array with only the second item.

data[1][2][3] << 4
data[1][2][3] << 5

yields
=A0 =A0{1=3D>{2=3D>{3=3D>[4,5]}}}
looing at the proxy I was expecting
=A0 =A0{1=3D>{2=3D>{3=3D>[5]}}}

The behavior I'm seeing is what I want I just didn't expect it. =A0From t= he
code it looks like << assigns an array to the key then appends a value. = =A0I
was expecting that to overwrite the array created with the first << call = at
that level with a new single item array.

I understood your question, so this means I explained myself really badly :=
-).
When you do this:

h[1] << 4

The following things happen:

- The method [] of h is called with parameter 1
- The hash detects that there's no entry for that key, and so calls
the default proc
- The default proc inserts a Proxy object in the hash for that key
(this proxy object remembers the hash and the key)
- The result of the default proc (which is the proxy object itself) is retu=
rned
- The method << with parameter 4 is called on the proxy object
- That method removes the proxy object from the hash and replaces
itself with an array with element 4 inside.

From now on, every time you call h[1] there is actually a value in the
hash, which is the array created by the proxy, and so the hash doesn't
call the default proc anymore, and no other proxy object is involved.
Subsequent calls to h[1] << some_value will actually call the <<
method of the array.

Hope this clears up the issue a little bit more.

Jesus.
 
G

Glen Holcomb

2010/1/26 Jes=FAs Gabriel y Gal=E1n said:
2010/1/26 Jes=FAs Gabriel y Gal=E1n said:
Exactly, infinite depth would be nice as it would make a more temporally
portable solution.

With it, you have infinite depth, until in a branch you decide to stop
by appending (<<) a value.
Then you fix the depth of that branch.

The proxy looks to be working great. I am a bit
confused as to why the << method in the proxy doesn't overwrite a le= af
with
a new array though. I'm not complaining as it works the way I want = it
to,
I'm just perplexed.

When you access h[1][2][3], a proxy object is inserted in the hash for
that key. The proxy object remembers the hash and the key. When you
call << on the proxy object, it replaces itself in the hash with an
empty array, to which it appends the value. So further calls to
h[1][2][3] will return that array and no proxy objects anymore. It
works the same for the upper levels: calling h[1] inserts a proxy in
the hash. When you call [] on it (for example h[1][2]) it replaces
h[1] with a hash.

Maybe this clarifies a bit more:


/temp$ cat nested_hash_array.rb && ruby nested_hash_array.rb
class ProxyDefault
def initialize hash, key
@hash =3D hash
@key =3D key
end

def [](key)
puts "the hash is: #{@hash.inspect} when calling [] on the
proxy object"
@hash[@key] =3D Hash.new {|hash,key| ProxyDefault.new(hash,
key)}
puts "the hash is: #{@hash.inspect} after replacing th= e
proxy with a hash"
@hash[@key][key]
end

def << value
puts "the hash is: #{@hash.inspect} when calling << on the
proxy object"
@hash[@key] =3D [value]
puts "the hash is: #{@hash.inspect} after replacing the
proxy with an array"
@hash[@key]
end
end

h =3D Hash.new {|hash,value| ProxyDefault.new(hash, value)}

h[1][2] << "value"

p h
p h[1][2]

the hash is: {} when calling [] on the proxy object
the hash is: {1=3D>{}} after replacing the proxy with a hash
the hash is: {} when calling << on the proxy object
the hash is: {2=3D>["value"]} after replacing the proxy with an array
{1=3D>{2=3D>["value"]}}
["value"]

Jesus.
Sorry, I should have been more specific when stating my confusion. I a= m
confused as to why appending a second item into a leaf results in a
multi-item array rather than a new array with only the second item.

data[1][2][3] << 4
data[1][2][3] << 5

yields
{1=3D>{2=3D>{3=3D>[4,5]}}}
looing at the proxy I was expecting
{1=3D>{2=3D>{3=3D>[5]}}}

The behavior I'm seeing is what I want I just didn't expect it. From t= he
code it looks like << assigns an array to the key then appends a value. I
was expecting that to overwrite the array created with the first << cal=
l
at
that level with a new single item array.

I understood your question, so this means I explained myself really badly
:).
When you do this:

h[1] << 4

The following things happen:

- The method [] of h is called with parameter 1
- The hash detects that there's no entry for that key, and so calls
the default proc
- The default proc inserts a Proxy object in the hash for that key
(this proxy object remembers the hash and the key)
- The result of the default proc (which is the proxy object itself) is
returned
- The method << with parameter 4 is called on the proxy object
- That method removes the proxy object from the hash and replaces
itself with an array with element 4 inside.

From now on, every time you call h[1] there is actually a value in the
hash, which is the array created by the proxy, and so the hash doesn't
call the default proc anymore, and no other proxy object is involved.
Subsequent calls to h[1] << some_value will actually call the <<
method of the array.

Hope this clears up the issue a little bit more.

Jesus.
No, you didn't acutally. I just wasn't thinking about it properly. When I
actually think about your explanation further the whole *no more proxy
object* makes perfect sense and answers my question.

Thanks for your patience and help Jesus. It is appreciated.

--=20
"Hey brother Christian with your high and mighty errand, Your actions speak
so loud, I can=92t hear a word you=92re saying."

-Greg Graffin (Bad Religion)
 

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

No members online now.

Forum statistics

Threads
473,773
Messages
2,569,594
Members
45,125
Latest member
VinayKumar Nevatia_
Top