Strange Hash default behaviour

I wrote a ruby program to read a list of graph edges and produce
an adjacency list using a Hash. The graph edges look like
p12 – p31
I hoped to write this as

nbs = Hash.new([])
while line = gets
if line =~ /p(\d+) – p(\d+)/
u, v = $1.to_i, $2.to_i
nbs[u] << v
nbs[v] << u
end
end

but unfortunately, the default value [] for the Hash is shared by all
elements, rather than copied, as I expected.

I was wondering if this was a conscious design decision, or an oversight.
I finally ended up with the much uglier

nbs = Hash.new()
while line = gets
if line =~ /p(\d+) – p(\d+)/
u, v = $1.to_i, $2.to_i
nbs[u] = [] unless nbs.has_key?(u)
nbs[u] << v
nbs[v] = [] unless nbs.has_key?(v)
nbs[v] << u
end
end

Is there another way to avoid the explicit tests?

regards,

%!PS % -John Tromp (http://www.cwi.nl/~tromp/)
42 42 scale 7 9 translate .07 setlinewidth .5 setgray/c{arc clip fill
setgray}def 1 0 0 42 1 0 c 0 1 1{0 3 3 90 270 arc 0 0 6 0 -3 3 90 270
arcn 270 90 c -2 2 4{-6 moveto 0 12 rlineto}for -5 2 5{-3 exch moveto
9 0 rlineto}for stroke 0 0 3 1 1 0 c 180 rotate initclip}for showpage

[all code snipped]
but unfortunately, the default value for the Hash is shared by all
elements, rather than copied, as I expected.

I was wondering if this was a conscious design decision, or an oversight.
I finally ended up with the much uglier

Is there another way to avoid the explicit tests?

It’s a common problem (are you listening, Bill :).

I don’t know about the design decision, but can offer a nicer solution. Others
will undoubtably have others; I remember there being some clever ones expressed
in the past, but not what they are :frowning:

Anyway:

(hash[key] ||= ) << value

Make sense?

Gavin

···

From: “John Tromp” tromp@serpens.ins.cwi.nl

Hello Gavin,

Wednesday, October 09, 2002, 3:15:28 PM, you wrote:

but unfortunately, the default value for the Hash is shared by all
elements, rather than copied, as I expected.
It’s a common problem (are you listening, Bill :).

i think, next question will be about

a = .fill(‘x’,0,2)
a[0] << ‘y’
p a

:slight_smile:

···


Best regards,
Bulat mailto:bulatz@integ.ru

Gavin Sinclair wrote:

From: “John Tromp” tromp@serpens.ins.cwi.nl

[all code snipped]
but unfortunately, the default value for the Hash is shared by all
elements, rather than copied, as I expected.

I was wondering if this was a conscious design decision, or an oversight.
I finally ended up with the much uglier

Is there another way to avoid the explicit tests?

It’s a common problem (are you listening, Bill :).

I don’t know about the design decision, but can offer a nicer solution.
Others will undoubtably have others; I remember there being some clever
ones expressed in the past, but not what they are :frowning:

Anyway:

(hash[key] ||= ) << value

Make sense?

Gavin

This seems similar to the problem with Array.new:

Array.new(5, ) will put the same “” in each cell, so:

a = Array.new(5, )
a[2] << 3
a # [[3],[3],[3],[3],[3]]

If you’re using 1.7?+, you can give a block to the constructor which is
evaluated to get the value of each cell:

a = Array.new(5) {}
a[2] << 3
a # [,,[3],,]

A similar thing seems to exist for Hash:

h = Hash.new()
h[:a] <<= :x
h[:b] <<= :y
h[:a] #[:x, :y]
h = Hash.new {}
h[:a] <<= :x
h[:b] <<= :y
h[:a] # [:x]

Strange thing is I can’t find this documented anywhere. Not even at
http://www.pragmaticprogrammer.com/ruby/new_features.html. Shouldn’t this
be added there?

Hi Gavin,

Yes, I am listening. Actually the first time I saw the post, it came
to my mind to include this as an addition to item #7 in the
“newcomers” list. I guess I will include the new block feature in 1.7
also (although, still ruby-lang.org lists 1.6.7 as the stable version,
and usually I don’t want to deal with some pre-release versions).

I am still “crippled”; our NNTP server seems still not working
properly. Some messages are missing and the surviving messages seems
to have hours of delay. So I am listening and posting from google,
but then google also has hours of delay, although it seems that all
the messages are complete. Definitely I cannot participate in an
“almost-real-time” mode for a while…

Regards,

Bill

···

===============================================================================
“Gavin Sinclair” gsinclair@soyabean.com.au wrote in message news:0e9f01c26f84$f4f0c060$526332d2@nosedog

From: “John Tromp” tromp@serpens.ins.cwi.nl

[all code snipped]
but unfortunately, the default value for the Hash is shared by all
elements, rather than copied, as I expected.

I was wondering if this was a conscious design decision, or an oversight.
I finally ended up with the much uglier

Is there another way to avoid the explicit tests?

It’s a common problem (are you listening, Bill :).

Hi,

Actually it is even a problem for myself when I tried to put an
example in the newcomers list :slight_smile: (Well… it is a list for newcomers
from a newcomer :slight_smile: )

I am using ruby 1.6.7 (2002-03-01) [i686-linux] and I got the
following:

hsh = Hash.new ()
hsh[‘a’] << ‘a’
puts hsh.length # >> 0 ???

I think this example is equivalent as the code in the original post,
but it seems it does not work for me. Any glitch in the code above?
(I think I have some sense on what’s actually going on, but I want to
imitate the code as in the original post.)

Regards,

Bill

···

=============================================================================
“Gavin Sinclair” gsinclair@soyabean.com.au wrote in message news:0e9f01c26f84$f4f0c060$526332d2@nosedog

It’s a common problem (are you listening, Bill :).

This seems similar to the problem with Array.new:

Array.new(5, ) will put the same “” in each cell, so:

a = Array.new(5, )
a[2] << 3
a # [[3],[3],[3],[3],[3]]

If you’re using 1.7?+, you can give a block to the constructor which is
evaluated to get the value of each cell:

a = Array.new(5) {}
a[2] << 3
a # [,,[3],,]

Very nice. When initialising an array-of-arrays like that, I usually do (I’m
not usually a 1.7 user):

arr = (1…N).map { }

A similar thing seems to exist for Hash:
[…]

Gavin

···

From: “George Ogata” g_ogata@optushome.com.au

George Ogata wrote:

h[:a] <<= :x

Why “<<=”?

I know, I know,… this is only an example, but…

Hi –

···

On Thu, 10 Oct 2002, Bill Tj wrote:

I am using ruby 1.6.7 (2002-03-01) [i686-linux] and I got the
following:

hsh = Hash.new ()
hsh[‘a’] << ‘a’
puts hsh.length # >> 0 ???

What you’re doing is adding ‘a’ to that default array, rather
than setting an ‘a’ key. So the above is the same as if you
had done:

hsh = Hash.new([‘a’])

hsh still doesn’t have any keys, just a default value.

David


David Alan Black | Register for RubyConf 2002!
home: dblack@candle.superlink.net | November 1-3
work: blackdav@shu.edu | Seattle, WA, USA
Web: http://pirate.shu.edu/~blackdav | http://www.rubyconf.com

Hi Gavin,
[…]
I am still “crippled”; our NNTP server seems still not working
properly. Some messages are missing and the surviving messages seems
to have hours of delay. So I am listening and posting from google,
but then google also has hours of delay, although it seems that all
the messages are complete. Definitely I cannot participate in an
“almost-real-time” mode for a while…

Regards,

Bill

Heh, try living in Australia. Receive ~80 messages in the morning and a tiny
trickle throughout the day.

Gavin

···

From: “Bill Tj” billtj@glue.umd.edu

“Gavin Sinclair” gsinclair@soyabean.com.au wrote in message
news:0f1701c26ff0$8e979c50$526332d2@nosedog…

Heh, try living in Australia. Receive ~80 messages in the morning and a
tiny
trickle throughout the day.

That must by me, staying up all at night in Denmark :slight_smile:

Mikkel

Hi David,

Exactly. That’s why in the original post

nbs = Hash.new()
while line = gets
if line =~ /p(\d+) – p(\d+)/
u, v = $1.to_i, $2.to_i
nbs[u] << v
nbs[v] << u
end
end

although it might seem that the “clue” was that the problem is “a
single array which is shared by many hash elements”, probably the more
immediate problem is actually “the use of hash default value as
(intended) lvalue instead of rvalue”?

I am really curious whether the original poster got “overlapped
arrays” problem , or “nbs.length == 0” problem, or both, when the code
finished executing. This is because although

nbs[12]

may return a non-empty array,

nbs.each_key {|key| puts key}

does not print anything, as nbs.length == 0. Is this a commonly known
Ruby hash feature?

To summarize, it seems that

"Given a right key, nbs may return something, but we cannot know
what those keys are from nbs alone."  :)

Maybe we can use something like this for cryptography such as hiding
data in our Ruby code so that the “naughty” user cannot do too much
damage? :slight_smile:

Regards,

Bill

···

=============================================================================
dblack@candle.superlink.net wrote in message news:Pine.LNX.4.44.0210091624440.16424-100000@candle.superlink.net

Hi –

What you’re doing is adding ‘a’ to that default array, rather
than setting an ‘a’ key. So the above is the same as if you
had done:

hsh = Hash.new([‘a’])

hsh still doesn’t have any keys, just a default value.

Christian Szegedy wrote:

Why “<<=”?

Guess I wasn’t thinking… thanks.

Hi –

···

On Thu, 10 Oct 2002, Bill Tj wrote:

I am really curious whether the original poster got “overlapped
arrays” problem , or “nbs.length == 0” problem, or both, when the code
finished executing. This is because although

nbs[12]

may return a non-empty array,

nbs.each_key {|key| puts key}

does not print anything, as nbs.length == 0. Is this a commonly known
Ruby hash feature?

Yes: the presence of a default value (even a non-nil one) is not the
same as the presence of a key/value pair. So nbs is an empty hash
whose default value happens not to be nil.

(Consider the alternative. If setting a default actually populated
the hash, what would hsh.keys consist of? :slight_smile:

David


David Alan Black | Register for RubyConf 2002!
home: dblack@candle.superlink.net | November 1-3
work: blackdav@shu.edu | Seattle, WA, USA
Web: http://pirate.shu.edu/~blackdav | http://www.rubyconf.com

Hi David,

Probably the problem is not that bad. A hash value can be populated with
the default value only when the hash[key] is called as an rvalue. As an
(imaginary) example:

hsh = Hash.new ('a')
puts hsh['x']  # >> 'a'
puts hsh['y']  # >> 'a'
hsh.keys       # ->  ['x', 'y']

Regards,

Bill

···

=========================================================================
dblack@candle.superlink.net wrote:

(Consider the alternative. If setting a default actually populated
the hash, what would hsh.keys consist of? :slight_smile:

Hi –

Hi David,

Probably the problem is not that bad. A hash value can be populated with
the default value only when the hash[key] is called as an rvalue. As an
(imaginary) example:

hsh = Hash.new ('a')
puts hsh['x']  # >> 'a'
puts hsh['y']  # >> 'a'
hsh.keys       # ->  ['x', 'y']

You can do that like this:

class Hash
alias :oldget :
def
v = oldget(k)
self[k] = v unless has_key?(k)
v
end
end

It’s a bit too “magic” for my taste. It fights against the notion of
a default, by only defaulting once. So if you change the default
dynamically, hsh[“x”] won’t default to the new default:

puts hsh[“k”] # “a”

hsh.default = “b”
puts hsh[“k”] # “a”

David

···

On Thu, 10 Oct 2002, William Djaja Tjokroaminata wrote:


David Alan Black | Register for RubyConf 2002!
home: dblack@candle.superlink.net | November 1-3
work: blackdav@shu.edu | Seattle, WA, USA
Web: http://pirate.shu.edu/~blackdav | http://www.rubyconf.com