如何使用自定义对象从数组中删除重复项

时间:2013-11-04 19:48:04

标签: ruby arrays object union

当我在包含自定义对象的两个数组上调用first_array | second_array时:

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

它返回[co1, co2, co3, co2, co3, co4]。它不会删除重复项。我试图在结果上调用uniq,但它也没有用。我该怎么办?

更新

这是自定义对象:

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

当我创建几个Task类型的实例并将它们放入两个不同的数组时,当我尝试调用'|'时对它们没有任何反应只会返回包含第一个和第二个数组元素的数组,而不会删除重复项。

6 个答案:

答案 0 :(得分:3)

如果您没有实现正确的相等方法,那么如果两个对象不同,那么自身的编程语言就无法识别。 在ruby的情况下你需要实现eql?并在您的类定义中使用哈希,因为这些是Array类用于检查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

例如:

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

从Array中删除b只留下一个对象

希望这有帮助!

答案 1 :(得分:2)

uniq方法可以采用一个块来定义比较对象的内容。例如:

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

答案 2 :(得分:0)

如果您查看Array#|运算符,则表示它使用eql? - 方法,Object上的方法与==方法相同。您可以通过mixin在Comparable - 模块中定义,然后实现<=> - 方法,然后您将免费获得许多比较方法。

<=>运算符非常易于实现:

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

答案 3 :(得分:0)

关于你的'更新',这就是你正在做的事情:

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>] 

然后建议e = [a, b, a]?如果是这样,那就是问题,因为a不再指向#<Task:0x007f8d988f1b78>。你只能说e => [#<Task:0x007f8d988f1b78>, b, a]

答案 4 :(得分:0)

我冒昧地重写你的类并添加需要覆盖的方法以便使用uniq(hash和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

答案 5 :(得分:0)

我尝试了上面fsaravia的解决方案,但对我来说并没有用。我尝试过Ruby 2.3.1和Ruby 2.4.0。

我发现的解决方案与fsaravia发布的解决方案非常相似,只需稍加调整即可。所以这就是:

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

请注意,我已经删除了我的示例中的@。它不会影响解决方案本身。只是,IMO,没有任何理由直接访问实例变量,因为设置了一个读取器方法。

所以...我真正改变的是在eql?方法中找到的,我用hash代替name。就是这样!