4

I'm having a hard time understanding why this piece of code works :

def flatten(array, result = [])
  array.each do |element|
    if element.is_a? Array
      flatten(element, result)
    else
      result << element
    end
  end
  result
end

In particular, why does it work without having to assign the result of the flatten method call to the result array, like so:

def flatten1(array, result = [])
  array.each do |element|
    if element.is_a? Array
      result = flatten(element, result)
    else
      result << element
    end
  end
  result
end

Both produce the same output:

p flatten [1,2,[3,4,[5,[6]]]]  # [1, 2, 3, 4, 5, 6]
p flatten1 [1,2,[3,4,[5,[6]]]] # [1, 2, 3, 4, 5, 6]
  • [Here's a longer discussion](http://stackoverflow.com/questions/1872110/is-ruby-pass-by-reference-or-by-value#10974116) and there are probably several more on the site, but short answer is that passing the `Array result` to the function is passing the actual object, not a copy of it, and the object is modified by `result << element` within the function, so when passed recursively it's continually modified without reassignment – Michael Berkowski Dec 31 '16 at 13:39

1 Answers1

5

The flatten method destructively modifies its second argument result in line 6 and passes that modified array as an argument to the recursive call in line 4. There is no need to return anything from the method because whatever array you pass as the second element will be destructively modified to have the flattened version of the input array appended:

my_array = [:foo]

flatten([1, [2, [3, [4]]]], my_array)

my_array
#=> [:foo, 1, 2, 3, 4]

It is generally considered bad form to modify objects passed as arguments or to return values by modifying input arguments instead of, you know, just returning it. It looks like the code was written by a C programmer who wanted to use the second argument as an output buffer.

A more idiomatic Ruby version would look something like this:

def flatten(array)
  array.each_with_object([]) do |element, result|
    if element.is_a?(Array)
      result.concat(flatten(element))
    else
      result << element
    end
  end
end

or a purely functional version with no mutation at all:

def flatten(array)
  array.inject([]) do |result, element|
    result + if element.is_a?(Array)
      flatten(element)
    else
      [element]
    end
  end
end
Arturo Herrero
  • 12,772
  • 11
  • 42
  • 73
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653