Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove only specific duplicates in array using Ruby

Is there a way to remove specific duplicates in an array using Ruby? Example array:

["hello", "removeme", "removeme", "hello", "testing"]

I only want to remove duplicates of "removeme" on that array. The desired output is:

["hello", "removeme", "hello", "testing"]

Is there a method like this to get the desired output? ["hello", "removeme", "removeme", "hello", "testing"].uniq('removeme')

like image 489
oj5th Avatar asked Sep 01 '25 10:09

oj5th


2 Answers

You can use uniq with a block which removes duplicates based on the block's return value:

ary = ["hello", "removeme", "removeme", "hello", "testing"]

ary.uniq { |obj| obj.eql?('removeme') || Object.new }
#=> ["hello", "removeme", "hello", "testing"]

For elements equal to 'removeme' we return true and for everything else ('hello', 'hello', and 'testing'), we return a new object: (note the different object ids)

"hello"    → #<Object:0x00007f9ab08590d8>
"removeme" → true
"removeme" → true
"hello"    → #<Object:0x00007f9ab0858d68> 
"testing"  → #<Object:0x00007f9ab08589f8>

All elements with the same return value are considered duplicates, i.e. uniq will treat 'removeme' as a duplicate and anything else as unique regardless of its actual value. This allows the two identical 'hello' strings to remain.

Instead of Object.new, you could also use the element's index:

ary.enum_for(:uniq).with_index { |obj, i| obj.eql?('removeme') || i }
#=> ["hello", "removeme", "hello", "testing"]

enum_for is needed, because uniq without a block returns a new array instead of an enumerator (which in turn is needed for chaining with_index).

like image 66
Stefan Avatar answered Sep 04 '25 04:09

Stefan


What about rejecting all but the first occurrence of the word, like this.

def uniq_word(array, word)
  return array unless first = array.index(word)
  array.reject.with_index { |elem, index| index > first && elem == word }
end

array = ["hello", "removeme", "removeme", "hello", "testing"]

uniq_word(array, 'removeme')
#=> ["hello", "removeme", "hello", "testing"]

See Array#index, Enumerator#with_index, and Array#reject.

Or you could iterate the aray and copy only the first occurance into a new array:

def uniq_word(array, word)
  found = false

  [].tap do |result|
    array.each do |elem|
      if elem == word && !found
        found = true
        next
      end

      result << elem
    end
  end
end

array = ["hello", "removeme", "removeme", "hello", "testing"]

uniq_word(array, 'removeme')
#=> ["hello", "removeme", "hello", "testing"]

See:

like image 21
spickermann Avatar answered Sep 04 '25 05:09

spickermann