0

I'm trying to get a nested hash to output a different instance of Hash whenever the default value is used.

The code below crashes (on my own raise that checks if the instances are equal):

def reset_region_data
  @region_data = Hash.new(Hash.new)
  # @region_data = Hash.new{ Hash.new } # Same result as the line above
  # @region_data = Hash.new { |hash,new_key|  hash[new_key] = {} }  # Same problem as the above lines.
end

def foo
  reset_region_data
  raise if @region_data[0].hash == @region_data[1 * 50 + 1].hash # <<<<< crashes
end

foo

This is weird. So the hash defaults to all of the same instances? But why?

But this code does not:

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

a[10][10] = 1
a[11][11] = 2

raise if a[10][10].hash == a[11][11].hash
p a[10][10]

And this code also doesn't crash:

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

a[10] = 1
a[11] = 2

raise if a[10].hash == a[11].hash
p a[10]
Napoleon
  • 1,072
  • 5
  • 17
  • 31
  • 1
    What exception are you getting? You probably want `Hash.new { Hash.new }`; otherwise, a single hash is created and set as the default value for the hash. The block form will be invoked (and will return a new hash) for each new default. – Chris Heald Sep 16 '14 at 23:25
  • My own raise is the exception. Because I want every instance to be different. But for some reason, they aren't. Every @region_data[x] is the same hash... – Napoleon Sep 16 '14 at 23:27
  • Use the block form as indicated. You're creating a single hash and returning that one hash for every default value right now. – Chris Heald Sep 16 '14 at 23:29
  • Hash.new{ Hash.new } gives me the same problem. – Napoleon Sep 16 '14 at 23:30
  • 1
    One of the problems with inline `rescue`'s is that they may mask the error you are getting. Change `@region_data = nil[] rescue reset_region_data` to `@region_data = nil[]` and I believe you will get the exception, `NoMethodError: undefined method [] for nil:NilClass`. `nil[]` makes no sense. – Cary Swoveland Sep 17 '14 at 01:22
  • 1
    You probably want to check e.g. `a[10].object_id` instead of `a[10].hash` - please update your question if I am right, or elaborate on why to use the hash method. Same values have same `hash`, but this does not necessarily mean that they are refering to the same object (although in your example they actually do, see my answer). – Felix Sep 17 '14 at 07:14
  • You're question is pretty disorganized. Could you post a simple, standalone program and tell us what is wrong with it? It should not have code such as `nil[]` that raises exceptions and it probably shouldn't have any exception handling either. – David Grayson Sep 17 '14 at 07:30
  • I tried it with object_id. It no longer crashes. Weird. But I thought that using .hash instead of object_id was the proper way to check them? And these code-snippets ARE standalone. I kept the nil[] in it to make sure that the rescue is not the problem in the real code. I already simplified the code snippet by a whole lot. – Napoleon Sep 17 '14 at 12:10
  • Please add a use-case of your code. What is the output (not in terms of exceptions raised) and what do you want it to be. Use e.g. `puts hash.inspect` or `puts hash.keys` or something like that. – Felix Sep 17 '14 at 14:17
  • It's used in a tile-editor. But for some reason when I change a single tile, every tile in the editor changes. And after a lot of testing and debugging I eventually found out that there was a problem with the Hash.default (all tiles used the same hash instance). So I created a new test project with just the code posted in my OP to simplify things and even that one concluded that all hashes were the same. – Napoleon Sep 17 '14 at 15:05

1 Answers1

2

If you give Hash.new an object (like another Hash.new) this very object is the default value. It is shared across different keys.

default = []
hash = Hash.new(default)
hash[:one] << 1
# now default is [1] !

You want to use Hash.new with a block, so that something new happens each time a key was not found.

Like

h = Hash.new { |hash,new_key|  hash[new_key] = {} }

There is great explanation about that e.g. in Ruby hash default value behavior . Your question is kind of a duplicate.

Also, as we figured out in the comments, you probably wanted to compare the object_id and not the hash value.

first_hash = {}
second_hash = {}

# .hash same, but different objects!
puts "initial, hash:"
puts first_hash.hash == second_hash.hash ? " - same" : " - differs"
puts "initial, object_id"
puts first_hash.object_id == second_hash.object_id ? " - same" : " - differs"
puts

# Change the world
# .hash different, and still different objects.
first_hash[:for] = "better"
puts "better world now, hash:"
puts first_hash.hash == second_hash.hash ? " - same" : " - differs"
puts "better world now, object_id"
puts first_hash.object_id == second_hash.object_id ? " - same" : " - differs"
Community
  • 1
  • 1
Felix
  • 4,510
  • 2
  • 31
  • 46
  • That doesn't work either. But replacing the .hash with .object_id for some reason no longer raises my "raise". Maybe I just used object.hash wrong somehow... – Napoleon Sep 17 '14 at 12:18
  • What do you mean with does not work?! `object_id` is unique per object. `.hash` is not (Two string variables holding the same text have same hash, but different object_id. If you change one of them, you change the hash, but not the object_id). – Felix Sep 17 '14 at 14:13
  • Then that was the problem. I got confused between the .hash instead of the .object_id. The code itself with brackets combined with the object_id works. – Napoleon Sep 17 '14 at 15:02