Ruby, multidimensional arrays, and mutability

Hercules Lemke Merscher - Dec 12 '22 - - Dev Community

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]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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]]
Enter fullscreen mode Exit fullscreen mode

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]]
Enter fullscreen mode Exit fullscreen mode

Image description

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]]
Enter fullscreen mode Exit fullscreen mode

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).


If you liked this post, consider subscribing to my newsletter Bit Maybe Wise.

You can also follow me on Twitter and Mastodon.


Photo by Joanna Kosinska on Unsplash

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .