Why does the behavior of array.each depend on the syntax of Array.new?

I am using Ruby 1.9.2-p290 and found:

a = Array.new(2, []).each {|i| i.push("a")} => [["a", "a"], ["a", "a"]] 

This is not what I would expect. But the following constructor style does what I expect:

 b = Array.new(2) {Array.new}.each {|i| i.push("b")} => [["b"], ["b"]] 

Is the first example of expected behavior?

In ruby-doc, it looks like my argument size=2 is the same argument for both constructors. I think that if each method receives this argument, it will use it the same way for both constructors.

+6
source share
3 answers

This is a common misunderstanding. In the first example, you create an array with two elements. Both of them are pointers to the same array . So, when you repeat your outer array, you add 2 elements to the inner array, which is then reflected in your output twice

Compare these:

 > array = Array.new(5, []) => [[], [], [], [], []] # Note - 5 identical object IDs (memory locations) > array.map { |o| o.object_id } => [70228709214620, 70228709214620, 70228709214620, 70228709214620, 70228709214620] > array = Array.new(5) { [] } => [[], [], [], [], []] # Note - 5 different object IDs (memory locations) > array.map { |o| o.object_id } => [70228709185900, 70228709185880, 70228709185860, 70228709185840, 70228709185780] 
+10
source

In the first case, you use one default array instance for the elements of the main array:

 a = Array.new(2, []).each {|i| i.push("a")} 

The second argument is simply recycled, so push is applied to the same instance twice. You only created one instance here, one that comes as an argument, so it is used again and again.

The second way is the right way:

 b = Array.new(2) {Array.new}.each {|i| i.push("b") 

This intentionally creates a new array instance for each position in the main array. An important difference here is the use of the { ... } block, which is executed once for each position in the new array. In short, it will be:

 b = Array.new(2) { [ ] }.each {|i| i.push("b") 
+4
source

From ruby ​​documentation:

 new(size=0, obj=nil) new(array) new(size) {|index| block } 

Returns a new array. In the first form, the new array is empty. The second is created with copies of size obj (that is, size references for the same object). The third form creates a copy of the array passed as a parameter (the array is generated by calling to_ary on the parameter). In the last form, an array of a given size is created.

So in the array a you are creating, you have two references to the same array, so push works on both of them. That is, you press "a" on the same array twice. In the array b that you create, you are actually creating a new array for each element.

+1
source

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


All Articles