Christopher Byrd Christopher Byrd - 5 months ago 7
Ruby Question

Why does using keep_if in Ruby skip over the first element in an array?

def unique(arr)
return arr.keep_if { |x| arr.count(x) == 1 }
end

print unique([2, 5, 5, 4, 22, 8, 2, 8])
#=> [4, 22, 2]


The value 2 appears twice in the array, but using the following method incorrectly returns it. Why does that happen, and what can I do to fix it?

Answer

Unfortunately, this is due to some hidden behavior in how keep_if works. To illustrate this behavior, we can make use of the lowest-hanging fruit in our debugging orchard, good ol' puts:

def unique(arr)
  return arr.keep_if { |x| 
    puts x, arr.join(',')
    arr.count(x) == 1
  }
end

print unique([2, 5, 5, 4, 22, 8, 2, 8])

This gives us the following as output:

2
2,5,5,4,22,8,2,8
5
2,5,5,4,22,8,2,8
5
2,5,5,4,22,8,2,8
4
2,5,5,4,22,8,2,8
22
4,5,5,4,22,8,2,8
8
4,22,5,4,22,8,2,8
2
4,22,5,4,22,8,2,8
8
4,22,2,4,22,8,2,8
[4, 22, 2]

Look carefully at exactly what happens whenever the method discovers a new value it wants to keep: it stores that value in one of the early indexes in the array, overwriting what's already there. The next time it finds a value it wants to keep, it places it in the next spot, and so on.

This means that the first time keep_if looks at 2, it sees two of them and so decides to skip it. But it then sees a 4 that it wants to keep, and overwrites the first 2. Thus, the second time it sees a 2, it decides to keep it.

Comments