Flatten a Ruby Array Without Using Built-In 'Flatten' Method

Flatten a Ruby Array without using built-in 'flatten' method

class Array
def flattify
each_with_object([]) do |element, flattened|
flattened.push *(element.is_a?(Array) ? element.flattify : element)
end
end
end

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


A non-monkey patching Array version:

def flattify(array)
array.each_with_object([]) do |element, flattened|
flattened.push *(element.is_a?(Array) ? flattify(element) : element)
end
end

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

One-step flattening of an array in ruby

You got an error cause you missed a ? in ternary statement:

element.kind_of?(Array) ? result.concat(element) : result<<element

For your goal you can use Array#flatten with an argument:

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

Understanding flattening an array in Ruby

confused with what #each_with_object does

You may have a better time understanding #each_with_object if you look at #inject first. #each_with_object is similar to #inject. Examples from http://blog.krishnaswamy.in/blog/2012/02/04/ruby-inject-vs-each-with-object/, included below:

#using inject
[[:tom,25],[:jerry,15]].inject({}) do |result, name_and_age|
name, age = name_and_age
result[name] = age
result
end

=> {:tom=>25, :jerry=>15}

#using each_with_object
[[:tom,25],[:jerry,15]].each_with_object({}) do |name_and_age, result|
name, age = name_and_age
result[name] = age
end

=> {:tom=>25, :jerry=>15}

See this Gist for example tests: https://gist.github.com/cupakromer/3371003

In depth article: http://engineering-blog.alphasights.com/tap-inject-and-each_with_object/


UPDATE

would #inject as opposed to #each_with_object work in this flattening code?

Yes, see below. I've illustratively refactored your flattening code to use #inject. Additionally, I removed the dependency on the "splat" operator (http://ruby-doc.org/core-2.3.1/doc/syntax/calling_methods_rdoc.html#label-Array+to+Arguments+Conversion)

# Flattens nested array; uses `Enumerable#inject`
# @see http://ruby-doc.org/core-2.3.1/Enumerable.html#method-i-inject
# @param arg [Array] contains objects of any type including any amount of nested arrays.
# @raise [StandardError] if arg is not Array class
# @return [Array] flat array comprised of elements from arg.
# @example
# flattify([nil, [1, [:two, [3.0], {4=>5}], "6"]]) #=> [nil, 1, :two, 3.0, {4=>5}, "6"]
def flattify(arg)
raise "arg is not Array" unless arg.is_a?(Array)

# variable ret_var used here to illustrate method's return in verbose fasion
# supplied [] used as initial value for flattened_array
ret_var = arg.inject([]) do |flattened_array, element|
# check if element class is Array
if element.is_a?(Array)
# Array#concat because flattify returns Array
# same as: a = a + b
# same as: a += b
flattened_array.concat(
# recursively call flattify with element as arg
# element is an Array
flattify(element)
)
else
# Array#push because element is not an Array
# same as: a << b
flattened_array.push(element)
end

# used in next iteration as value for first arg above in: "|flattened_array, element|"
# OR returned on last iteration, becoming value of ret_var above
flattened_array
end

# explicit return for illustrative purposes
return ret_var
end

UPDATE 2

may [I] ask why the splat operator is used here? I am still a bit
confused on that. It seems the code is [looping] each time and pushing
it in the flattened array, whats the point of the *?

flattened.push *(element.is_a?(Array) ? flattify(element) : element)

The above block is a "ternary operation" (see: https://en.wikipedia.org/wiki/Ternary_operation), which is explained here: https://stackoverflow.com/a/4252945/1076207 like so:

if_this_is_a_true_value ? then_the_result_is_this : else_it_is_this

Compare the flattify examples with each other:

# each_with_object
flattened.push *(flattify(element))
# inject
flattened_array.concat(flattify(element))

Here the * splat operator (see: https://stackoverflow.com/search?q=%5Bruby%5D+splat) is doing the same thing as Array#concat. However, the splat allows flattened.push to accept either of the two possible types the ternary operation returns: 1) an Array; or 2) whatever element is. For illustration, notice how the splat operator prevents nesting:

# each_with_object with splat
flattened = [1,2,3]
flattened.push *([4,5,6]) # => [1, 2, 3, 4, 5, 6]
flattened.push *(7) # => [1, 2, 3, 4, 5, 6, 7]

# each_with_object without splat
flattened = [1,2,3]
flattened.push ([4,5,6]) # => [1, 2, 3, [4, 5, 6]]
flattened.push (7) # => [1, 2, 3, [4, 5, 6], 7]

Conversely, Array#concat will only accept an array. If the same ternary operation was used and returned an element, it would cause an error:

# inject
flattened_array = [1,2,3]
flattened_array.concat([4,5,6]) # => [1, 2, 3, 4, 5, 6]
flattened_array.concat(7) # => TypeError: no implicit conversion of Fixnum into Array

In summary, both versions of flattify achieve the same result. However, #each_with_object uses #push, a ternary operation and a splat operator; while #inject uses an if/else statement, #concat and #push.


UPDATE 3

When we did each with object([]), the last parameter became an
array.

Yes. It becomes an array and continues to be that same array throughout the iterations until it's passed back.

So with inject the first one becomes an array?

Yes. The first one becomes the passed in array, but only for the first iteration, then it's replaced by the result of the code block for each subsequent iteration.

Sample Image

how does our code know if element is defined as an int and
flattened_Array is an array?

element.is_a?(Array) # => true or false

When element is Array class this method returns true, and if not returns false. false means that it's anything but an array including int.

For more info, see: http://ruby-doc.org/core-2.3.1/Object.html#method-i-is_a-3F

How can I rewrite this flatten method without using class level variables?

The problem with using a class variable is that the same one is visible to all the instances, and new_flatten is an operation for an instance. You should refactor your new_flatten algorithm so it doesn't rely on what is, in effect, a "global" variable for the method calls.

For example, you can use a local variable and Ruby's facility to append arrays with +:

class Array
def new_flatten
a = []
self.each do |element|
if element.is_a? Array
a += element.new_flatten
else
a << element
end
end
a
end
end

Also, as some tweaks of style, when doing an if-else, it's often clearer to state the positive logic first. And, you don't need the elsif in this case since the else is the complement of the if. So rather than:

if not something
do_stuff
elsif opposite_of_not_something
do_other_stuff
end

It's clearer to say:

if something
do_stuff
else
do_other_stuff
end

Algorithm for Ruby's flatten() method implemented in Java

You can write a method that will flatten many kinds of Object into a List<Integer> no matter how deeply nested. The method below will work for int[], int[][], Integer, Integer[], Integer[][], List<Integer>, List<List<Integer>>, List<int[]> etc. (You could easily make it return an int[] instead).

public static List<Integer> flatten(Object object) {
List<Integer> list = new ArrayList<>();
helper(object, list);
return list;
}

private static void helper(Object object, List<Integer> list) {
if (object instanceof Integer) {
list.add((Integer) object);
} else if (object instanceof int[]) {
for (int a : (int[]) object)
list.add(a);
} else if (object instanceof Object[]) {
for (Object obj : (Object[]) object)
helper(obj, list);
} else if (object instanceof Iterable) {
for (Object obj : (Iterable) object)
helper(obj, list);
} else {
throw new IllegalArgumentException();
}
}

However, this kind of thing is a really terrible idea. If you find yourself writing a load of instanceof checks, and then doing different things based on the result, you've most likely made some poor design decisions.

flatten ruby hash into array with keys removed

There is a built-in hash method: h.values

How to extract an element of an array and insert it in another while respecting its original index

There is. You can use Array#zip in conjunction with Array#flatten:

b.zip(a).map(&:flatten)
#=> [["a", "b", "x"], ["c", "d", "y"], ["e", "f", "z"]]

Reverse an array without using a loop in ruby

You can treat array as a stack and pop the elements from the end:

def reverse(array)
rev = []
rev << array.pop until array.empty?
rev
end

or if you don't like modifying objects, use more functional-like reduce:

def reverse(array)
array.reduce([]) {|acc, x| [x] + acc}
end

Cary mentioned in the comment about the performance. The functional approach might not be the fastest way, so if you really want to do it fast, create a buffor array and just add the items from the end to begin:

def reverse(array)
reversed = Array.new(array.count)
array.each_with_index do |item, index|
reversed[-(index + 1)] = item
end
reversed
end


Related Topics



Leave a reply



Submit