Skip to content

Instantly share code, notes, and snippets.

@rossta
Last active July 19, 2016 15:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rossta/66c784b99e6174c93b4b913376e964c2 to your computer and use it in GitHub Desktop.
Save rossta/66c784b99e6174c93b4b913376e964c2 to your computer and use it in GitHub Desktop.
Custom Ruby module to demonstrate flattening of an Array with using Array#flatten

Flatten

A Ruby module to demonstrate flattening of a nested array without using Array#flatten:

Usage

Flatten.flatten([1, [2, [3]]])
=> [1, 2, 3]

Tests

Code was developed using Ruby 2.3.1. To run tests, run bundle install and bundle exec ruby flatten_test.rb

Going Further

For an alternative usage, I also extracted the logic of this custom flatten implementation as a Ruby refinement.

module Flatten
# Returns new one-dimensional array from nested array, recursively
#
# @param array [Array] the array to be flattened
#
# @example
# Flatten.flatten([1, [2, [3]]])
# => [1, 2, 3]
#
# @return [Array]
#
def self.flatten(array)
Flattener.new(array).flatten
end
# @private class to handle custom array flattening logic without using Array#flatten
#
class Flattener
def initialize(array)
@array = array
end
def flatten
raise ArgumentError, "Argument #{@array.inspect} is not an array" unless @array.kind_of?(Array)
recursively_flatten(@array)
end
private
# @private handles array flattening recursively while detecting cycles
#
def recursively_flatten(array, visited = {})
array.each_with_object([]) do |element, new_array|
case element
when Array
object_id = element.object_id
raise ArgumentError, "Cannot flatten array with cycles" if visited[object_id]
visited[object_id] = true
new_array.concat recursively_flatten(element, visited)
else
new_array << element
end
end
end
end
end
require 'minitest/autorun'
require 'minitest/pride'
require_relative './flatten'
class TestFlatten < Minitest::Test
def test_flattens_nested_array_to_one_dimension
assert_equal [1, 2, 3, 4, 5, 1, 2, 3, 4, 5], Flatten.flatten([[1,2,[3]], [4, 5], [], [1, [2, [3, 4, [5]]]]])
end
def test_empty
assert_equal [], Flatten.flatten([])
end
def test_already_flat
assert_equal [3, 3, 3], Flatten.flatten([3, 3, 3])
end
def test_does_not_modify_array
array = [1, [2]]
Flatten.flatten(array)
assert_equal [1, [2]], array
end
def test_raises_error_on_non_arrays
assert_raises(ArgumentError) { Flatten.flatten(nil) }
assert_raises(ArgumentError) { Flatten.flatten("string") }
assert_raises(ArgumentError) { Flatten.flatten(12345) }
assert_raises(ArgumentError) { Flatten.flatten({}) }
end
def test_raises_error_array_with_cycles
array = [1]
array << array
assert_raises(ArgumentError) { Flatten.flatten(array) }
array_1 = [1]
array_2 = [2]
array_1 << array_2
array_2 << array_1
assert_raises(ArgumentError) { Flatten.flatten(array) }
end
end
source "https://rubygems.org"
gem "minitest"
GEM
remote: https://rubygems.org/
specs:
minitest (5.9.0)
PLATFORMS
ruby
DEPENDENCIES
minitest
BUNDLED WITH
1.12.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment