6

When I call first_array | second_array on two arrays that contain custom objects:

first_array = [co1, co2, co3]
second_array =[co2, co3, co4]

it returns [co1, co2, co3, co2, co3, co4]. It doesn't remove the duplicates. I tried to call uniq on the result, but it didn't work either. What should I do?

Update:

This is the custom object:

class Task
    attr_accessor :status, :description, :priority, :tags
    def initiate_task task_line
        @status = task_line.split("|")[0]
        @description = task_line.split("|")[1]
        @priority = task_line.split("|")[2]
        @tags = task_line.split("|")[3].split(",")
        return self
    end

    def <=>(another_task)
        stat_comp = (@status == another_task.status)
        desc_comp = (@description == another_task.description)
        prio_comp = (@priority == another_task.priority)
        tags_comp = (@tags == another_task.tags)
        if(stat_comp&desc_comp&prio_comp&tags_comp) then return 0 end
    end
end

and when I create few instances of Task type and drop them into two different arrays and when I try to call '|' on them nothing happens it just returns array including both first and second array's elements without the duplicates removed.

7
  • what is returning [co1, co2, co3, co2, co3, co4]? Commented Nov 4, 2013 at 19:51
  • 1
    Well, those objects are different objects, it seems. Commented Nov 4, 2013 at 19:51
  • @dax first_array | second_array Sergio they have different memory addresses I guess.It is not like comparing simple data as integers. Commented Nov 4, 2013 at 19:53
  • what kind of objects are they? and you want to compare them based on what? Commented Nov 4, 2013 at 20:05
  • What is your goal - comparing objects or getting uniq values ? Commented Nov 4, 2013 at 20:08

6 Answers 6

5

No programming language for itself can be aware if two objects are different if you don't implement the correct equality methods. In the case of ruby you need to implement eql? and hash in your class definition, as these are the methods that the Array class uses to check for equality as stated on Ruby's Array docs:

def eql?(other_obj)
  # Your comparing code goes here
end

def hash
  #Generates an unique integer based on instance variables
end

For example:

class A

  attr_accessor :name

  def initialize(name)
    @name = name
  end

  def eql?(other)
    @name.eql?(other.name)
  end

  def hash
    @name.hash
  end
end

a = A.new('Peter')
b = A.new('Peter')

arr = [a,b]
puts arr.uniq

Removes b from Array leaving only one object

Hope this helps!

Sign up to request clarification or add additional context in comments.

3 Comments

Is this the operator that I have to implement on the object so I can call union on two arrays that containt that kind of objects?
That is a requirement to make the class enumerable. For the purpose of evaluating ==, defining == will suffice.
Actually sawa's comment made me realize my answer was wrong, Array class needs eql? and hash to be implemented in order to identify unique objects, I've updated my answer
4

The uniq method can take a block that defines what to compare the objects on. For example:

class Task
  attr_accessor :n
  def initialize(n)
    @n = n
  end
end

t1 = Task.new(1)
t2 = Task.new(2)
t3 = Task.new(2)

a = [t1, t2, t3]

a.uniq
#=> [t1, t2, t3] # because all 3 objects are unique

a.uniq { |t| t.n }
#=> [t1, t2]     # as it's comparing on the value of n in the object

1 Comment

This is the best solution. It could be dangerous to override the eql? and the hash methods because all users of the object will be subject to those changes. By defining the block locally you ensure your comparison is not used unintentionally.
1

I tried the solution from fsaravia above and it didn't work out for me. I tried in both Ruby 2.3.1 and Ruby 2.4.0.

The solution I've found is very similar to what fsaravia posted though, with a small tweak. So here it is:

class A
  attr_accessor :name

  def initialize(name)
    @name = name
  end

  def eql?(other)
    hash.eql?(other.hash)
  end

  def hash
    name.hash
  end
end

a = A.new('Peter')
b = A.new('Peter')

arr = [a,b]
puts arr.uniq

Please, don't mind that I've removed the @ in my example. It won't affect the solution per se. It's just that, IMO, there wasn't any reason to access the instance variable directly, given a reader method was set for that reason.

So...what I really changed is found inside the eql? method, where I used hash instead name. That's it!

Comments

0

If you look at the Array#| operator it says that it uses the eql?-method, which on Object is the same as the == method. You can define that by mixin in the Comparable-module, and then implement the <=>-method, then you'll get lots of comparison-methods for free.

The <=> operator is very easy to implement:

def <=>(obj)
    return -1 if this < obj
    return 0 if this == obj
    return 1 if this > obj
end

7 Comments

Do you think that implementing the '==' would be enough to call union operation ot two arrays that contain only custom objects of that type?
Sure, but its just as easy to implement <=> and it gives you all the comparison-methods for free (including ==).
I tried only with the '==' but it didn't work. When I test it like that: co1 == co2 it works perfectly but when I put them into array and try to make union it doesnt remove the duplicates.
It should work if you do as described in the manual (ruby-doc.org/core-2.0.0/Comparable.html) and implement the <=>-method.
I don't know how to define if the one of the objects is less or greater than the other.They are just regular objects that can have equa values of all their properties or not.I don't know how that could help(implementing the <=>)
|
0

Regarding your 'update', is this what you are doing:

a = Task.new # => #<Task:0x007f8d988f1b78> 
b = Task.new # => #<Task:0x007f8d992ea300> 
c = [a,b]    # => [#<Task:0x007f8d988f1b78>, #<Task:0x007f8d992ea300>] 
a = Task.new # => #<Task:0x007f8d992d3e48> 
d = [a]      # => [#<Task:0x007f8d992d3e48>]  
e = c|d      # => [#<Task:0x007f8d988f1b78>, #<Task:0x007f8d992ea300>, \
                   #<Task:0x007f8d992d3e48>] 

and then suggesting that e = [a, b, a]? If so, that's the problem, because a no longer points to #<Task:0x007f8d988f1b78>. All you can say is e => [#<Task:0x007f8d988f1b78>, b, a]

Comments

0

I took the liberty to rewrite your class and add the methods that needs to be overwritten in order to use uniq (hash and eql?).

class Task

    METHODS = [:status, :description, :priority, :tags]
    attr_accessor *METHODS

    def initialize task_line
        @status, @description, @priority, @tags = *task_line.split("|")
        @tags = @tags.split(",")
    end

    def eql? another_task
       METHODS.all?{|m| self.send(m)==another_task.send(m)}
    end

    alias_method :==, :eql? #Strictly not needed for array.uniq

    def hash
      [@status, @description, @priority, @tags].hash
    end

end


x = [Task.new('1|2|3|4'), Task.new('1|2|3|4')]
p x.size #=> 2
p x.uniq.size #=> 1

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.