I was debugging an issue in an RoR application and came across with below code (where ‘map’ is a two dimensional array of integer). The code attempts to duplicate and append the last element of each sub-array:
map.each { |x| x << x[-1] }
Before this line of code,
(rdb:29) p map
[[1, 2, 1, 3, 4], [1, 2, 2, 3, 4], [1, 2, 2, 3, 4], [1, 2, 2, 3, 4],
[1, 2, 2, 3, 4], [1, 2, 2, 3, 4]]
(rdb:29) p map.class
Array
(rdb:29) p map.first.class
Array
(rdb:29) p map.last.class
Array
After:
(rdb:29) p map
[[1, 2, 1, 3, 4, 4], [1, 2, 2, 3, 4, 4], [1, 2, 2, 3, 4, 4],
[1, 2, 2, 3, 4, 4], [1, 2, 2, 3, 4, 4, 4], [1, 2, 2, 3, 4, 4, 4]]
The problem here is that the last 2 sub-arrays were appended with two integers instead of one, while it is correct for the first 4 sub-arrays. I changed the code to use Array.map instead, it was then working properly:
map = map.map { |x| x + [x[-1]] }
To sum up:
I know the iterating element in the Array.each block is not supposed to be changed. But why is it giving unpredictable results when doing so? The code actually works most of the time, the issue was just seen sometimes. Was it a bug in Ruby or RoR?
Modifying the sub-array within the
eachblock isn’t your problem, there’s nothing wrong with that at all. Your problem is that your outer array,map, sometimes contains multiple references to the same sub-array object.Consider this:
The results will be the same every single time. But if you have this:
Then you will get the extra trailing elements that you were seeing (every single time) because
gotchagets modified twice. Thexin the second case willputsthe same as thexin the first case but they are not the same.Your
Array#mapapproach always works because this:essentially copies
xand then appendsx[-1]to that copy, it never modifiesxat all so thegotchabehavior from above won’t happen.You can’t get away from pointers, not even when they’re called references.