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