[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Problem with Hash of Arrays

Jimi Damon

12/7/2007 10:42:00 PM

I am new to Ruby , but I consider this feature to be a bug.


What is happening is that i am creating a new Hash of Arrays. The
following code works fine


a = Hash.new(Array.new())


a[:first] += ["this"]
a[:first] += ["is a"]
a[:first] += ["string"]
puts a.to_yaml

The following also works...

a = Hash.new(Array.new())

a[:key] += ["first"]
a[:key].push("second")
a[:key].push("third")
puts a.to_yaml


But this does not

a = Hash.new(Array.new())

a[:key].push("first")
a[:key].push("second")
a[:key].push("third")


However, this does not work if you don't use the "+=" operator first.
Note, this "feature" also
occurs for the "<<" operator , or any other methods that expect that
a[:key] is already a defined array.


I think if you already specified what the new object is going to be ,
then you should be able to call a method of that object.
--
Posted via http://www.ruby-....

7 Answers

Sebastian Hungerecker

12/7/2007 11:01:00 PM

0

Jimi Damon wrote:
> I am new to Ruby , but I consider this feature to be a bug.
>
>
> What is happening is that i am creating a new Hash of Arrays. The
> following code works fine

What is happening is that you create a Hash whose default value is one Array.


> a = Hash.new(Array.new())
>
>
> a[:first] += ["this"]
> a[:first] += ["is a"]
> a[:first] += ["string"]

Here you are assigning a new array to a[:first] three times.


> a = Hash.new(Array.new())
>
> a[:key] += ["first"]
> a[:key].push("second")
> a[:key].push("third")

Here you are assigining a new array once and then changing it two times.


> a = Hash.new(Array.new())
>
> a[:key].push("first")
> a[:key].push("second")
> a[:key].push("third")

Here you are changing the default array three times.


> However, this does not work if you don't use the "+=" operator first.

+= isn't one operator. It's a shortcut for a[:key] = a[:key] + ["first"].
This will first call the +-method on a a[:key] (i.e. it will call the + method
on the default object because that's what a[:key] points to at this time),
which will return a new array, and then assign the resulting array to
a[:key]. This does not change the default array. Calling push would because
the push method modifies its receiver while the + method doesn't.


> Note, this "feature" also
> occurs for the "<<" operator , or any other methods that expect that
> a[:key] is already a defined array.

No method expect any such thing. They don't know or care about the hash a. All
they care about is their arguments and their receiver. Since you call them on
the hash's default object, they will operate on the hash's default object.


> I think if you already specified what the new object is going to be ,
> then you should be able to call a method of that object.

I'm sorry, but I don't follow. What new object? << and pop don't create any
new objects. And where did you specify what such an object would be? Array#+
creates a new Array (unlike Array#<< and Array#pop, which just modify an
existing one), but it doesn't do so, because you specified anywhere that you
want an array. It does so because it always returns an array, because that's
what it's been defined to do.


--
NP: Katatonia - Scarlet Heavens
Jabber: sepp2k@jabber.org
ICQ: 205544826

bermonruf

12/7/2007 11:25:00 PM

0

Note: parts of this message were removed by the gateway to make it a legal Usenet post.

In addition, to create different Arrays objects use a block...

irb(main):001:0> a = Hash.new{|hash, key| hash[key] = Array.new;}
=> {}
irb(main):002:0> a[:first]
=> []
irb(main):003:0> a[:first] << "test"
=> ["test"]
irb(main):004:0> a[:sec]
=> []

Jimi Damon

12/8/2007

0

Bernardo Rufino wrote:
> In addition, to create different Arrays objects use a block...
>
> irb(main):001:0> a = Hash.new{|hash, key| hash[key] = Array.new;}
> => {}
> irb(main):002:0> a[:first]
> => []
> irb(main):003:0> a[:first] << "test"
> => ["test"]

Yes, but if you run this example and type "a" you get

irb(main):003:0> a
=> {}

I'm sorry...but I think this is incorrect... You have defined an array
as being the default type , hence after
you have performed << "test" , a should contain

=> {:first=>["test"]}

But it does not..


As for the other post.

When you define Hash.new( ) , this is the default value when you HAVE
NOT defined the key for that value.

Hence, if I do

a = Hash.new("tmp")

and type in irb
irb(main):006 a = Hash.new("tmp")
=> {}
irb(main):007 a[:first]
=> "tmp"
irb(main):008 a
=> {}
irb(main):009

This makes sense because it is accessing the default value for the key
which is "tmp"

However, with that being said, if my default type is an "array", then I
should be able to Push into that array a value and have it stay around,
other wise why does

a[:key] += ["value"]
and then
a[:key].push("another value")
work ?









--
Posted via http://www.ruby-....

Gary Wright

12/8/2007 12:14:00 AM

0


On Dec 7, 2007, at 7:00 PM, Jimi Damon wrote:

> Bernardo Rufino wrote:
>> In addition, to create different Arrays objects use a block...
>>
>> irb(main):001:0> a = Hash.new{|hash, key| hash[key] = Array.new;}
>> => {}
>> irb(main):002:0> a[:first]
>> => []
>> irb(main):003:0> a[:first] << "test"
>> => ["test"]
>
> Yes, but if you run this example and type "a" you get
>
> irb(main):003:0> a
> => {}
>
> I'm sorry...but I think this is incorrect... You have defined an array
> as being the default type , hence after
> you have performed << "test" , a should contain
>
> => {:first=>["test"]}
>
> But it does not..
>
You've mistyped something. a will indeed be "{:first=>["test"]}"
if you run Bernardo's example.

You're asking a lot of very common questions for programmers who
aren't familiar with the behavior of Ruby's Hash class. The bottom
line is that you are not pointing out bugs in Ruby's implementation
of Hash, just common misunderstandings.


1) Hash.new(x) returns x when a key isn't found. It will be the
same x for *every* key and won't store x in the hash, just return
it on a key miss.

2) Hash.new { #code } will run the block on every key miss and
return the resulting value but will *not* store the value in the
hash.

3) Hash.new { |h,k| h[k] = #code } will evaluate the code, store
it in the hash, and return the value on a key miss. Because
the value is stored in the hash on the first key miss, the code
will not execute on a subsequent lookup on the same key.

Gary Wright

Jimi Damon

12/8/2007 12:18:00 AM

0

How do you suggest creating new Hash entries where by default I want
them to be Arrays ?


In Perl I can easily ( albeit it is ugly ) type


push ( @{$hash{key}}, "New value")


This works as long as $hash{key} has either not been defined yet...or if
it is already an anonymous Array.

How can you use operators such as "Push" , or "<<" on a hash key/value
pair when the key has not been defined yet for the hash ? I want a
constructor for each "value" to make it an Array.

PLease note, I don't want to write

if hash.has_key?("key").nil?
hash["key"] = ["new value"]
else
hash["key"].push( "new value" )
end

Thanks for any suggestions and also for straightening me out about
"Default" values.


However, really what I am looking for is a default constructore for the
blank value.



--
Posted via http://www.ruby-....

Gary Wright

12/8/2007 2:47:00 AM

0


On Dec 7, 2007, at 7:17 PM, Jimi Damon wrote:
> However, really what I am looking for is a default constructore for
> the
> blank value.

Bernardo already showed you:

> In addition, to create different Arrays objects use a block...
>
> irb(main):001:0> a = Hash.new{|hash, key| hash[key] = Array.new;}
> => {}
> irb(main):002:0> a[:first]
> => []
> irb(main):003:0> a[:first] << "test"
> => ["test"]
> irb(main):004:0> a[:sec]
> => []



A block provided to Hash.new is called every time there is miss
during key lookup. The two arguments to the block are the hash
itself and the key that caused the missed lookup. If the code
in the block stores a value into the hash with that key, then there
won't be any future misses for that key.

In the example that Bernardo showed, a new array is constructed
every time a key lookup fails and then stored in the hash using
that key. Subsequent lookups with that key get that array--that
is to say, an new array is only created once per key.

Gary Wright

Sebastian Hungerecker

12/8/2007 10:52:00 AM

0

Jimi Damon wrote:
> Bernardo Rufino wrote:
> > In addition, to create different Arrays objects use a block...
> >
> > irb(main):001:0> a = Hash.new{|hash, key| hash[key] = Array.new;}
> > => {}
> > irb(main):002:0> a[:first]
> > => []
> > irb(main):003:0> a[:first] << "test"
> > => ["test"]
>
> Yes, but if you run this example and type "a" you get
>
> irb(main):003:0> a
> => {}

This is not true.
>> a = Hash.new{|hash, key| hash[key] = Array.new}
=> {}
>> a[:first] << "test"
=> ["test"]
>> a
=> {:first=>["test"]}


> I'm sorry...but I think this is incorrect... You have defined an array
> as being the default type , hence after
> you have performed << "test" , a should contain
>
> => {:first=>["test"]}
>
> But it does not..

Yes, it does. See above.


> When you define Hash.new( ) , this is the default value when you HAVE
> NOT defined the key for that value.
>
> Hence, if I do
>
> a = Hash.new("tmp")
>
> and type in irb
> irb(main):006 a = Hash.new("tmp")
> => {}
> irb(main):007 a[:first]
> => "tmp"
> irb(main):008 a
> => {}
> irb(main):009
>
> This makes sense because it is accessing the default value for the key
> which is "tmp"

Exactly.


> However, with that being said, if my default type is an "array", then I
> should be able to Push into that array a value and have it stay around,

If you push an item into the default array. It does stay around. The array
doesn't get assigned to the key, but the item stays in the array. See:

>> h = Hash.new(Array.new)
=> {}
>> h[:bla]
=> []
>> h[:bla] << "la"
=> ["la"]
>> h[:bla]
=> ["la"]
>> h[:blubb]
=> ["la"]
>> h
=> {}

After the first line the default item is an empty array. h[:bla] will give you
this array. Calling << on it, will put "la" into the array. The default item
is now ["la"]. It is not assigned to any hash key, but everytime you get the
default item, this is what you will get.


> other wise why does
> a[:key] += ["value"]
> and then
> a[:key].push("another value")
> work ?

As I explained in my previous post the first line expands to:
a[:key] = a[:key] + ["value"]
This will first evaluate a[:key] + ["value"].
This calls the + method on the default array with ["value"] as an argument.
What the method does is it creates a new array containing all the items of the
default array as well as the items of the argument. Assuming the default
array was empty before, this will evaluate to the array ["value"] (without
changing the default array). So the expression now reads
a[:key] = ["value"]
This assigns the new array ["value"] to a[:key]. a[:key] now no longer points
to the default array. As such calling push on it, no longer calls push on the
default array, but on the new array it points to.


HTH,
Sebastian
--
NP: Explosions in the Sky - A Poor Man's Memory
Jabber: sepp2k@jabber.org
ICQ: 205544826