Ruby, multidimensional arrays, and mutability
Ruby, multidimensional arrays, mutability, and the advent of code
As I progress doing the advent of code, the challenges start becoming more interesting and difficult, and eventually, a multidimensional array is needed.
Ruby is famous for its succinct syntax, but there are some subtleties to pay attention to.
A shorthand
Given we need to instantiate an array, and the size (or minimum size) is known, we can allocate the memory upfront by specifying how many items we want:
irb> my_array = Array.new(6)
=> [nil, nil, nil, nil, nil, nil]
All of them will be nil, but we can provide a default value as well:
irb> my_array = Array.new(6, 42)
=> [42, 42, 42, 42, 42, 42]
Things start to become interesting (not to say disastrous) when we pass an object as the default value.
“But in Ruby, everything is an object!”, you might be wondering, and you’re totally right. However, numbers in Ruby are immutable objects.
The multidimensional array
Given that Array.new can receive its size and a default value, we can think of using the expressiveness and succinctness of Ruby to allow us to generate a multidimensional array as a one-liner:
irb> my_array = Array.new(6, Array.new(2, 42))
=> [[42, 42], [42, 42], [42, 42], [42, 42], [42, 42], [42, 42]]
It’s phenomenal, but let’s see what happens in practice:
irb> my_array[0][0] = 0
=> 0
irb> my_array
=> [[0, 42], [0, 42], [0, 42], [0, 42], [0, 42], [0, 42]]
Not obvious, but expected!
It is one of such things we should not be surprised by, still, it may catch us off-guard.
When the syntax Array.new(6, Array.new(2, 42)) is used, and the default parameter received is a mutable object, every single item of the array is populated with a reference for the same object, therefore changing one value implies it reflecting everywhere there’s a reference to it.
Fear not! There’s an easy fix for that:
irb> my_array = Array.new(6) { Array.new(2, 42) }
=> [[42, 42], [42, 42], [42, 42], [42, 42], [42, 42], [42, 42]]
irb> my_array[0][0] = 0
=> 0
irb> my_array
=> [[0, 42], [42, 42], [42, 42], [42, 42], [42, 42], [42, 42]]
By using the block syntax, the Array.new will run the block for every entry to generate a default value, thus populating the entries with different objects.
If you got curious about the advent of code challenge, you can check out my solution here (spoiler alert, be advised).