views:

92

answers:

5

how can I eliminate duplicate elements from an array of ruby objects using an attribute of the object to match identical objects.

with an array of basic types I can use a set..

eg.

array_list = [1, 3, 4 5, 6, 6]
array_list.to_set 
=> [1, 2, 3, 4, 5, 6]

can I adapt this technique to work with object attributes?

thanks

A: 

what about uniq

   a = [ "a", "a", "b", "b", "c" ]
   a.uniq   #=> ["a", "b", "c"]

you can use it on object as well!

Salil
He said object attributes, so I'm guessing the complete object won't be identical.
Beanish
hi.. beanish is right.. need to match object id for instance... can I pass a block into the to_set or uniq method?
Dom
@Dom: It looks like you can pass a block to `to_set`: http://apidock.com/ruby/Enumerable/to_set. Doesn't show what it does exactly, but I'm guessing it would solve your problem.
ryeguy
macek
+1  A: 

If you can write it into your objects to use eql? then you can use uniq.

ezpz
A: 

Hi .. thanks for your responses.. uniq works once I added the following to my object model

def ==(other)
    other.class == self.class &&
    other.id  == self.id
end
alias :eql? :==
Dom
This violates the contract of `Object#hash` and maybe also of `Object#==`.
Jörg W Mittag
+3  A: 

I think you are putting the cart before the horse. You are asking yourself: "How can I get uniq to remove objects which aren't equal?" But what you should be asking yourself, is: "Why aren't those two objects equal, despite the fact that I consider them to be?"

In other words: it seems you are trying to work around the fact that your objects have broken equality semantics, when what you really should do is simply fixing those broken equality semantics.

Here's an example for a Product, where two products are considered equal if they have the same type number:

class Product
  def initialize(type_number)
    self.type_number = type_number
  end

  def ==(other)
    type_number == other.type_number
  end

  def eql?(other)
    other.is_a?(self.class) && type_number.eql?(other.type_number)
  end

  def hash
    type_number.hash
  end

  protected

  attr_reader :type_number

  private

  attr_writer :type_number
end

require 'test/unit'
class TestHashEquality < Test::Unit::TestCase
  def test_that_products_with_equal_type_numbers_are_considered_equal
    assert_equal 2, [Product.new(1), Product.new(2), Product.new(1)].uniq.size
  end
end
Jörg W Mittag
Jörg, what if I want to use use `type_number` to consider my products unique for one use, but use a different field, e.g., `color` for another use? Ideally, it'd be nice to be able to use a block much like is common for comparison methods used for sorting. Any ideas?
macek
Hi. Ideally I want to prevent an object being added to an array of objects if the has already been added (based on matching attributes). I thought sets might do this.
Dom
A: 

Should you be using an Array, or should you be using a Set instead? If order isn't important, then the latter will make it more efficient to check for duplicates.

Andrew Grimm