Array element modification behaves wrong

I have an array filled with default strings, and I'm trying to replace a part of the characters in the default string at a random position.

If I do something like this, I will have all elements in the array changed:

arr = ["*"] * 10
arr[0][0..2] = "aaa"
arr 
# => ["aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa"]

But if I initialize an array in a different way, it works:

(0..10).each.map {|i| arr[i] = "*"}
arr[0][0..2] = "aaa"
arr
# => ["aaa", "*", "*", "*", "*", "*", "*", "*", "*", "*", "*"]

Some more, initialize and all elements same:

str = "*"
(0..10).each.map {|i| arr[i] = str}
arr[0][0..2] = "aaa"
arr
# => ["aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa"]

Instead i did this to initialize it with unique elements:

str = "*"
(0..10).each.map {|i| arr[i] = "#{str}" }
arr[0][0..2] = "aaa"
arr
# => ["aaa", "*", "*", "*", "*", "*", "*", "*", "*", "*"]

What is the background for such behavior?

Answers


Arrays store references to objects. When you initialize an array that way, you get an array with ten references to the same string. Then you modify the string.

arr = ['*']*3
# => ["*", "*", "*"]
arr.map &:object_id
# => [70305424624600, 70305424624600, 70305424624600]

For contrast, this way ruby allocates a new string for each element:

Array.new(3){ '*' }.map &:object_id
# => [70184497001120, 70184497001060, 70184497001000]

When you do arr=["*"]*10 you're putting the exact same String object into all array slots. Whereas, (0..10).each.map { |i| arr[i] = "*" } is creating a new String object for each element in the array.

Illustrated with the following code:

(0..10).each.map { |i| arr[i] = "*" }
arr[0].equal? arr[1] # Check if first and second elements point to same Object
# => false
arr = ["*"] * 10
arr[0].equal? arr[1]
# => true

arr = ["*"]*10

This statement create an array of 10 elements, but all these elements are not unique and refer to the same instance of the string "*" that you created before filling array. I mean that your code is the same as:

a = "*"
arr = [a, a, a, a, a, a, a, a, a, a]
#=> ["*", "*", "*", "*", "*", "*", "*", "*", "*", "*"]

arr.map(&:object_id)
#=> [15424420, 15424420, 15424420, 15424420, 15424420, 15424420, 15424420, 15424420, 15424420, 15424420]

All elements of array refer to the same string instance, so when you change any element value in your array then you will actually change the value of a variable, so as a result you will get the same array filled with a variable, but since its value were changed to "aaa" then the output will look like:

["aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa"]

In the second example you fill in each element of array with new instance of "*" string, so when you change single array value it will affect only this concrete element of array and all others will be the same, because they refer to the different allocated objects in memory.

arr = (0..10).each.map { |i| arr[i] = "*" }
#=> ["*", "*", "*", "*", "*", "*", "*", "*", "*", "*"]
arr.map(&:object_id)
#=> [14451520, 14451500, 14451480, 14451440, 14451420, 14451320, 14451300, 14451280, 14451260, 14451240, 14451160]

Need Your Help

Displaying JSON Data On A Website

php jquery css json

Looking for the best and most simple way to embed and style the data returned from a JSON call, on another web page. Ideally I would like to do this with some sort of simple embed code that someone...

Custom URL scheme for Pinterest on iOS Safari to open board URL?

html ios pinterest url-scheme

I can't for the life of me find out how to simply open a Pinterest board (by launching the APP if it's installed) through an anchor within HTML.