Why not a tricky way to define new hashes in Ruby (they all refer to the same object)

I need to set multiple hashes, and I don't want to list one line at a time, for example,

a = Hash.new b = Hash.new 

I am also a newbie that apart from Fixnums I could not do this

 a = b = Hash.new 

since both a and b will refer to the same object. I can do it

 a, b, = Hash.new, Hash.new 

If I had a group, it seemed to me that I could do it too

 a, b = [Hash.new] * 2 

this works for strings, but for hashes they all refer to the same object, despite the fact that

 [Hash.new, Hash.new] == [Hash.new] * 2 

and the previous one works.

See the sample code below for a single error message caused by a "propagation hash." Just curious why that is.

 a, b, c = [String.new] * 3 a = "hi" puts "string broken" unless b == "" puts "not equivalent" unless [Hash.new, Hash.new, Hash.new] == [Hash.new] * 3 a, b, c = [Hash.new, Hash.new, Hash.new] a['hi'] = :test puts "normal hash broken" unless b == {} a, b, c = [Hash.new] * 3 a['hi'] = :test puts "multiplication hash broken" unless b == {} 
+1
source share
2 answers

In response to the original question, an easy way to initialize multiple copies would be to use Array.new(size) {|index| block } Array.new(size) {|index| block } Array.new

 a, b = Array.new(2) { Hash.new } a, b, c = Array.new(3) { Hash.new } # ... and so on 

On the side of the note, in addition to confusing the assignment, another seemingly problem with the original is that you may have been mistaken that == compares the references to the Hash and String objects.To be clear, this is not the case.

 # Hashes are considered equivalent if they have the same keys/values (or none at all) hash1, hash2 = {}, {} hash1 == hash1 #=> true hash1 == hash2 #=> true # so of course [Hash.new, Hash.new] == [Hash.new] * 2 #=> true # however different_hashes = [Hash.new, Hash.new] same_hash_twice = [Hash.new] * 2 different_hashes == same_hash_twice #=> true different_hashes.map(&:object_id) == same_hash_twice.map(&:object_id) #=> false 
+4
source

I understand it. [String.new] * 3 does not create three String objects. It creates one and creates a 3-element array in which each element points to the same object.

The reason you do not see "string broken" is because you have assigned a new value. So, after the line a = "hi" , a refers to the new String object ("hi"), and b and c still refer to the same source object ("").

The same thing happens with [Hash.new] * 3 ; but this time you are not resetting any variables. Rather, you modify a single Hash object by adding the key / value [hi, :test] (via a['hi'] = :test ). In this step, you changed one object referenced by a , b and c .

Here is an example of contrived code to make it more specific:

 class Thing attr_accessor :value def initialize(value) @value = value end end # a, b, and c all refer to the same Thing object a, b, c = [Thing.new(0)] * 3 # Here we *modify* that object a.value = 5 # Verify b refers to the same object as a -- outputs "5" puts b.value # Now *assign* a to a NEW Thing object a = Thing.new(10) # Verify a and b now refer to different objects -- outputs "10, 5" puts "#{a.value}, #{b.value}" 

It makes sense?


Update . I am not a Ruby guru, so there may be a more common way to do this. But if you want to use multiplication type syntax to initialize an array with many different objects, you can consider this approach: create a lambdas array and then call all of them using map .

Here is what I mean:

 def call_all(lambdas) lambdas.map{ |f| f.call } end a, b, c = call_all([lambda{Hash.new}] * 3) 

You can verify that this approach works quite easily:

 x, y, z = call_all([lambda{rand(100)}] * 3) # This should output 3 random (probably different) numbers puts "#{x}, #{y}, #{z}" 

Update 2 . I like the numbers1311407 approach using Array#new much better.

+3
source

Source: https://habr.com/ru/post/917069/


All Articles