3

There are two methods below; both are the same except one clones the input whereas the other does not.

Method 1

arr = [1,2,3,1,2,3]

def remove_smallest(array)
  new = array
  new.reject! {|i| i <= new.min}
  new
end

remove_smallest(arr)
#=> [2,3,2,3]
arr
#=> [2,3,2,3]

Method 2

arr = [1,2,3,1,2,3]

def remove_smallest(array)
  new = array.clone
  new.reject! {|i| i <= new.min}
  new
end

remove_smallest(arr)
#=> [2,3,2,3]
arr
#=> [1,2,3,1,2,3]

Without the clone, the method will mutate the original input even if I perform all operations on a copy of the original array.

Why is an explicit clone method needed to avoid this mutation?

sawa
  • 165,429
  • 45
  • 277
  • 381
Richard Jarram
  • 899
  • 11
  • 22
  • 4
    Use `reject` instead. The `!` (bang) refers to destructive methods. – Sebastián Palma Mar 20 '19 at 03:10
  • 6
    `new = array` does not make a copy of the array. It makes a copy of the _reference_ to the array. There is still only one array. I suggest you run both snippets through [this visualizer](http://pythontutor.com/ruby.html), which should show you what is happening. – Amadan Mar 20 '19 at 03:31
  • 2
    @Sabastian has answered your question, but here are a couple more points. You don't need a variable `new` and you don't need `new` by itself as the last line. All you need for the body of the method is this: `array.reject {|i| i <= array.min}`. As that's the last statement of method it's return value becomes the return value of the method. As `array.min` is within the block it is execute `array.size` times, which is inefficient. Also, `<=` is the same as `==` and the former is confusing. Instead, write `n = array.min` followed by `array.reject {|i| i == n }` – Cary Swoveland Mar 20 '19 at 03:32
  • 1
    maybe have a look here for more insight https://stackoverflow.com/questions/1872110/is-ruby-pass-by-reference-or-by-value – lacostenycoder Mar 20 '19 at 05:28
  • 2
    @CarySwoveland Sebastian hasn't answered the question. Amadan has. – sawa Mar 20 '19 at 07:08
  • @sawa, yes. By way of explanation, I started writing my comment before Amadan posted his comment and was focused on the somewhat extraneous points I made. – Cary Swoveland Mar 20 '19 at 14:20
  • 1
    One could write `def remove_smallest(array); array - [array.min]; end`. Like [Array#reject](http://ruby-doc.org/core-2.5.1/Array.html#method-i-reject), [Array#-](http://ruby-doc.org/core-2.5.1/Array.html#method-i-2D) is order-preserving. – Cary Swoveland Mar 20 '19 at 17:18

1 Answers1

10

[...] will mutate the original input even if I perform all operations on a copy of the original array.

You don't perform the operations on a copy. When doing

new = array

it doesn't result in a copy operation. Instead, the assignment makes new simply refer to the same object array is referring to. It therefore doesn't matter if you invoke new.reject! or array.reject! because reject! is sent to the same receiver.

Why is an explicit .clone method needed to avoid this mutation?

Because clone performs the copy operation you've assumed for =. From the docs:

Produces a shallow copy of obj [...]

Another way to avoid this mutation is to use a non-mutating method instead:

def remove_smallest(array)
  array.reject {|i| i <= array.min }
end

or – to avoid re-calculating the minimum on each step:

def remove_smallest(array)
  min = array.min
  array.reject {|i| i <= min }
end

You can also use == instead of <= because min is already the smallest possible value.

Alternatively, there's Array#-:

def remove_smallest(array)
  array - [array.min]
end
Stefan
  • 109,145
  • 14
  • 143
  • 218