4

I want to keep track of the counts of some arbitarily-named strings and then reset the counts to zero. My thought was to do the following:

reset_hash={"string1"=>0,"string2"=>0,"string3"=>0}
=> {"string1"=>0, "string2"=>0, "string3"=>0} 

new_hash = reset_hash
=> {"string1"=>0, "string2"=>0, "string3"=>0} 

new_hash["string1"]=1
new_hash["string3"]=1
new_hash
=> {"string1"=>1, "string2"=>0, "string3"=>1}

...

Now I want to reset new_hash back to reset_hash:

new_hash = reset_hash
=> {"string1"=>1, "string2"=>0, "string3"=>1}
reset_hash
=> {"string1"=>1, "string2"=>0, "string3"=>1} 

What is going on here? It seems that reset_hash has actually been set to new_hash, which is the opposite of what I wanted. How do I implement the desired behavior?

Evan Zamir
  • 8,059
  • 14
  • 56
  • 83

4 Answers4

4

When you have a variable pointing to an object, you really just have a reference to the object. If both a and b point to the hash {1=>3, "foo" => 54}, changing a or b will change the other.

However, you can use a combination of two methods to make what you want.

A default value for the hash:

new_hash = Hash.new(0)

This gives unused values a default value of 0:

new_hash["eggs"]  # -> 0

You can then add counts:

new_hash["string1"] += 1 # => 1

When you're done just call

new_hash.clear # => {}

and your hash will be reset, but new accesses will still default to 0.

Note that if you set the default value to a type of object other than a number you may be able to change things due to the whole reference issue noted above.

irb(main):031:0> b = Hash.new("Foo") #=> {}
irb(main):032:0> b[3] #=> "Foo"
irb(main):033:0> b[33] #=> "Foo"
irb(main):034:0> b[33].upcase! #=> "FOO"
irb(main):035:0> b[3] # => "FOO"

To get around this you can pass a block to you hash:

h = Hash.new {|hash, key| hash[key] = "new default value"}

This creates a new object at key each time, so changes to one won't ripple:

d = Hash.new{ |hash,key| hash[key] = "string"} #=> {}
d[3] # => "string"
d[3].upcase! #=> "STRING"
d[5] #=> "string"
Paul Rubel
  • 26,632
  • 7
  • 60
  • 80
4

As others mentioned you have to use clone. Your task should look like this:

reset_hash={"string1"=>0,"string2"=>0,"string3"=>0}
new_hash = reset_hash.clone

new_hash["string1"]=1
new_hash["string3"]=1
new_hash

new_hash = reset_hash.clone
reset_hash
Иван Бишевац
  • 13,811
  • 21
  • 66
  • 93
1

You're modifying a single hash.

Both variables refer to the same hash. When you change an item in the hash, both references will reflect that change–because it's the same hash instdance.

Perhaps you want to copy the hash first? If you do, and you have a hash with complex objects, you'll also need to investigate shallow vs. deep copies/cloning.

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
  • I thought I was copying the hash. So how do I do that correctly? – Evan Zamir Jul 16 '12 at 20:22
  • You're copying a *reference* to the hash; you need to use [`clone` (docs)](http://apidock.com/ruby/Object/clone), noting the caveats. There are many ways to do a deep clone, including just marshalling/unmarshalling the object. – Dave Newton Jul 16 '12 at 20:24
1

You need to use clone to make your copy.

See https://stackoverflow.com/a/4157438/1118101

Otherwise, you're only creating 2 "pointers" to the same hash, not copying the contents.

Then use replace to copy the cloned contents back into your existing hash.

Community
  • 1
  • 1
theglauber
  • 28,367
  • 7
  • 29
  • 47