Recreating the Flatten Method in Ruby

An exercise in classes, methods, and recursion

Annee Barrett
6 min readMay 4, 2017

As someone who first started learning how to code in Javascript, “for” loops have a special place in my heart. Iterating over a collection always involved or used a helper method with some long, extremely explicit “for” loop. So when I started learning Ruby, I was immediate skeptical about its immense selection of built in-methods, iterators, and enumerables. How did .each know where to start and stop iterating if I wan’t giving it a counter? Where did .map or .collect hold its working information?

My understanding of how Ruby iterators worked was answered through exercises where I recreated the .each and .map methods using yield, which I’m not going to go into. Instead, I’m going to walk you thorough my solution for a prompt that I encountered when applying to a coding bootcamp: recreating Ruby’s flatten method for arrays.

a flat cat

Ruby’s Flatten

Say you have an array of nested, or multidimensional, arrays, that is, an array in which there are elements that are also arrays:

array = [1, [2, 3, [4, 5]]]

The flatten method will return a one-dimensional array, an array where all the values are on the same level:

array = [1, [2, 3, [4, 5]]]
array.flatten #=> [1, 2, 3, 4, 5]

Additionally, you can call the flatten method with an argument, which will flatten that array by that many levels:

array = [1, [2, 3, [4, 5]]]
array.flatten(1) #=> [1, 2, 3, [4, 5]]

Object Oriented Ruby

Ruby is an object-oriented language. Almost everything in Ruby is an object, and every object has a class and is an instance of that class. Ruby includes a default collection of methods for objects that vary for different classes, which is how we’re able to use .flatten on receivers that are arrays or hashes, (although .flatten works slightly differently on hashes,) and not on those that are numbers or strings. But how do we know what the class of an object is? Of course, there’s a method for that: .class!

arr = [1, [2, 3, [4, 5]]]
arr.class #=> Array
[1, [2, 3, [4, 5]]].class #=> Array

We’ll be using this method in our custom flatten method to determine whether elements of the array are also arrays.

Class Methods

Let’s start by defining the method my flatten for the Array class:

class Array  def my_flatten
# code goes here
end
end

This way, the my_flatten method is accessible to any object that is an instance of the Array class.

Default Arguments

We want our method to flatten an array by a given number of dimensions if an argument is passed, but completely flatten an array if no argument is passed. Therefore, our method must be able to take in an optional argument. By setting the parameter to n = nil, we are telling Ruby that if this argument is not defined when the method is called, the method will pass a default value, which for our purposes will be nil.

Our method should do one thing if n has a value, and another if n does not. I’ve chosen to use a ternary operator that checks the truthiness of n, as if n has a value it will return true, and if n is equal to nil it will return false:

def my_flatten(n = nil)
n ? method_if_n(self, n) : method_if_no_n(self)
end

Because we want our method to operate on the receiver, we use self to refer to the instance on which the method is being called.

Now let’s write our flattening methods!

a collection of flattened cats

Flattening an Array by One Dimension

We’re going to start by defining a helper method that flattens an array by one dimension:

def single_flatten(array)
results = []
array.each do |element|
if element.class == Array
element.each {|value| results << value}
else
results << element
end
end
results
end

This method takes in an array and sets an empty results array. It then looks at each element of the array in question. If the element is an array, it will look at each element within that array, and append the nested element’s value to the results array using the shovel operator. If the element is not an array, it will append the value of that element into the the results array. Once every element in the array has either had itself or its components appended into the results, the method returns the result array, which is equivalent to the original array flattened by one dimension:

my_array = [1, [2, 3, [4, 5]]]
single_flatten(my_array) #=> [1, 2, 3, [4, 5]]

Flattening an Array n Times

But what if we want to flatten an array a given number of times? We can call single_flatten within a while loop with a counter:

def multiple_flatten(array, n)
count = 0
arr = array
while count < n do
arr = single_flatten(arr)
count += 1
end
arr
end

First, we set the count to zero and the variable arr to our initial array. If the counter hasn’t reached the number of dimensions we’ve specified wanting to reduce our array by, arr will be passed into single_flatten. Every time the block in the while loop executes, arr is reassigned to the result of single_flatten, and the counter is incremented by one. Once the counter is equal to the number of dimensions we want to reduce by, the final return value of the method is arr, the last result of the single_flatten.

my_array = [1, [2, 3, [4, [5, 6]]]]
multiple_flatten(my_array, 2) #=> [1, 2, 3, 4, [5, 6]]

Flattening an Array with Recursion

Our last helper method is the recursive flatten:

def recursive_flatten(array, results = [])
array.each do |element|
if element.class == Array
recursive_flatten(element, results)
else
results << element
end
end
results
end

Like the single flatten, it checks if an element is an array, and if not, appends the value of the element to a results array. However, if the element is an array, the method calls itself pulling in this element and the existing results array. The method will keep calling itself on the elements of elements until all the elements are not arrays and can be pushed into the results array.

Because we want the method to be completely recursive, we’re setting results to an optional argument that defaults to an empty array. If we set the results array to be empty within the method, it will reset the results array every time the function is called. The first time we call recursive_flatten, we call it without the results argument. This way, it will create our initially empty results array that will then be filled passed around and into the other recursive_flatten methods and never be reset back to being empty!

We can now reduce a multi-dimensional array to one dimension, regardless of how many dimensions it starts with:

my_array = [1, [2, 3, [4, 5]]]
recursive_flatten(my_array) #=> [1, 2, 3, 4, 5]

Putting It All Together

Calling the multiple_flatten and recursive_flatten methods into your ternary operator and inserting the helper methods into the class definition, we get:

class Array  def my_flatten(n = nil)
n ? multiple_flatten(self, n) : recursive_flatten(self)
end
private def recursive_flatten(array, results = [])
array.each do |element|
if element.class == Array
recursive_flatten(element, results)
else
results << element
end
end
results
end
def multiple_flatten(array, n)
count = 0
arr = array
while count < n do
arr = single_flatten(arr)
count += 1
end
arr
end
def single_flatten(array)
results = []
array.each do |element|
if element.class == Array
element.each {|value| results << value}
else
results << element
end
end
results
end
end

Because the helper methods should never need to be called with an explicit receiver, I’ve chosen to make the helper methods private.

And now, after running this code in your environment, you’ll be able to flatten objects that are array with .my_flatten!

*squish*

Happy flattening~

--

--