You can view the full .flatten method challenge here.
Flatten Challenge
The flatten method is a handy tool to compress nested arrays into one, flat array without losing any of the data. It works like this:
> [[1, 3], [5, [7, 9]]].flatten
=> [1, 3, 5, 7, 9]
Instead of relying on the .flatten method in Ruby, we'll build our own recursively.
TDD
I'll start by building a test.
require 'minitest' require 'minitest/autorun' require 'minitest/pride' require '../flatten' class FlattenTest < Minitest::Test def test_combine_nested_arrays all = [1, 1, 2, 3, 4, 5] start_array = [1, [1,2],[3,[4,5]]] assert_equal all, CustomArray.new.combine_nested_arrays(start_array) end
I'm always skeptical of tests that run correctly the first time, so I prefer to run each test, expecting it to fail, before I proceed.
Now it's time to start building the method.
My first thought is that I'll need to iterate through each nested array and flatten it before proceeding to the next nested array.
Let's set up the class file.
require 'pry' class CustomFlattenMethod def need_a_method end end
The most efficient way I can think to do this would be to pull the data out of the nested array and shovel it into another array.
I'll call the flatten method `combine_nested_arrays`.
The Method
I'll walk through it step-by-step below.
attr_reader :all def combine_nested_arrays(arr, all=[]) arr.each do |item| if item.class != Array all << item else combine_nested_arrays(item, all) end end all end
I set the variable `all` to default to an empty array. That way the method will be able to receive one or two variables, both arrays. One to flatten and another (the default `all`) to push our data into.
def combine_nested_arrays(arr, all=[])
Then we want to iterate through the array containing the nested arrays and pull out the data from each nested array.
arr.each do |item|
However, what happens if one of the items in the array isn't an array?
For example, what if our array looked like this:
> [2, [1, 3], [5, [7, 9]]]
The first number, 2, isn't an array, it's just an integer. I'll set up an if/then statement to manage this situation.
arr.each do |item| if item.class != Array all << item else combine_nested_arrays(item, all) end end
Now if the item being altered within our iteration isn't an array, it will automatically be shoveled into our new array, `all`. It doesn't matter if it's an integer, symbol or string.
Recursive Magic
If the item is an array, magic happens.
Not really, but we do recursively solve the problem. An array is sent back into our method, `combine_nested_arrays`.
A nested array will be sent back through the method until the item being item in the iteration is no longer an array.
If the first item in the original array was an array, the parameter `all` will continue to be it's default `all = []` when it's sent through the method again.
However, if the first item is not an array, and `all` is no longer an empty array, that array will be passed into the method along with `arr`.
More Tests
Because I'm a fan of tests, I wrote some more, along with a few edge cases, to confirm my assumptions were correct.
Below is my full test suite.
require 'minitest' require 'minitest/autorun' require 'minitest/pride' require '../flatten' class FlattenTest < Minitest::Test def test_combine_nested_arrays all = [1, 1, 2, 3, 4, 5] start_array = [1, [1,2],[3,[4,5]]] assert_equal all, CustomArray.new.combine_nested_arrays(start_array) end def test_combine_nested_arrays_start_with_array all = [1, 2, 3, 4, 5] start_array = [[1,2],[3,[4,5]]] assert_equal all, CustomArray.new.combine_nested_arrays(start_array) end def test_combine_nested_arrays_start_with_array_nested_nested all = [1, 2, 3, 4, 3, 4, 5] start_array = [[1, 2, [3, 4]],[3,[4,5]]] assert_equal all, CustomArray.new.combine_nested_arrays(start_array) end def test_combine_nested_arrays_differet_type_data all = [1, "2", 3, 4, 3, 4, 5] start_array = [[1, "2", [3, 4]],[3,[4,5]]] assert_equal all, CustomArray.new.combine_nested_arrays(start_array) end end
Want to play with the code? View this repository on Github.