Home > database >  Why is uniq needed in this problem? (Ruby)
Why is uniq needed in this problem? (Ruby)

Time:09-25

New to coding, and trying to study independently. Working on a problem on Ruby, where we create a method (filter_out!) that mutates the original array to remove true elements when the array is passed through a proc. The only stipulation is that we aren't allowed to use Array#reject!.

Can anyone explain to me why

arr.uniq.each { |ele| arr.delete(ele) if prc.call(ele)}

works and

arr.each { |ele| arr.delete(ele) if prc.call(ele)}

does not? Here are two example problems:

arr_2 = [1, 7, 3, 5 ]

filter_out!(arr_2) { |x| x.odd? }

p arr_2     # []

arr_3 = [10, 6, 3, 2, 5 ]

filter_out!(arr_3) { |x| x.even? }

p arr_3     # [3, 5]

I've looked it up and understand #uniq removes duplicate values, right?

Any insight would be greatly appreciated-thank you!

edit: Looked into it more, is it because uniq will use the return value of the proc for comparison? Still not sure why this is needed for some numbers, but not all.

CodePudding user response:

Your assumption

I've looked it up and understand #uniq removes duplicate values, right?

is correct.

See https://apidock.com/ruby/Array/uniq

uniq() public
Returns a new array by removing duplicate values in self.
If a block is given, it will use the return value of the block for comparison.

In your first example

arr_2 = [1, 7, 3, 5 ]
filter_out!(arr_2) { |x| x.odd? }
p arr_2     # []

you are removing odd values, the result is correct. No even values left.

In your second example

arr_3 = [10, 6, 3, 2, 5 ]
filter_out!(arr_3) { |x| x.even? }
p arr_3     # [3, 5]

you are removing even values, the odd values from your source array remain in the result. Also correct.

Or do I miss anything?

CodePudding user response:

The behaviour you have witnessed has nothing to do with the fact that a proc is present or that uniq is invoked. Suppose:

arr = [true, false, true]
arr.each do |x| puts "x=#{x}, arr = #{arr}"
                arr.delete(x)
                puts "arr = #{arr}"
end 
  #=> [false]

The following is printed:

x=true, arr = [true, false, true]
arr = [false]

You were perhaps expecting to see an empty array returned, not [false].

As you see, only arr[0] #=> true was passed to each's block. That caused arr.delete(x) to delete both instances of true from arr, causing arr to then equal [false]. Ruby then attempts to pass arr[1] to the block but finds that arr has only one element so she concludes she is finished and returns arr #=> [false].

It is bad practice to invoke a method on a receiver that alters the receiver in a block.

Had arr = [true, true, true] the return value (an empty array) would be correct, but only because all elements are removed when the first element of arr is passed to the block. This is analogous to the example in the question of removing all odd elements of [1, 7, 3, 5 ].

The same behaviour results when we first invoke uniq:

a = arr.uniq
  #=> [true, false]
a.each do |x| puts "x=#{x}, a = #{a}"
              a.delete(x)
              puts "a = #{a}"
end
  #=> [false]

The following is printed:

x=true, a = [true, false]
a = [false]
  •  Tags:  
  • ruby
  • Related