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.
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
Rvm Isnt Setting Environment with Cron
How to Iterate Through This JSON Document Using Ruby
Nokogiri Fails to Install on Os X
Quote All Fields in CSV Output
How to Escape the Vertical Bar When Doing Command Line with Ruby
How Can Same Program in Ruby Accept Input from User as Well as Command Line Arguments
Sorting Numeric Strings in Ruby
Ruby Parenthesis Syntax Exception with I++ ++I
How to Update a Model's Attribute with a Virtual Attribute
How to Deal with a Page Which Fails to Load and Continue Testing in Watir-Webdriver
Ruby on Rails: Create Confirmation View Before Creating the Object
When Should I Use Datetime VS Date, Time Fields in Ruby/Rails
Gem Install JSON Fails with Redifinition of Struct Timezone/Timespec