如何从包含自定义对象的数组中删除重复项

6
当我在包含自定义对象的两个数组上调用 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

当我创建几个任务类型的实例并将它们放入两个不同的数组中,然后尝试在它们上面调用'|'时,什么也不会发生,它只会返回包括第一个和第二个数组元素的数组,而不会删除重复项。


返回 [co1, co2, co3, co2, co3, co4] 是什么? - dax
1
那些对象看起来是不同的对象。 - Sergio Tulentsev
它们是什么类型的对象?你想根据什么来比较它们? - PericlesTheo
@MichaelDurrant 我的目标是将一些自定义对象放入两个数组中,当我在这些数组上调用union时,我可以获得真正的并集(没有重复对象)。 - user2128702
你已经在正确的道路上了,但是你还需要实现一个自定义哈希函数,请参考我的答案。 - hirolau
显示剩余2条评论
6个回答

5

如果您没有实现正确的相等性方法,任何编程语言本身都无法意识到两个对象是否不同。对于Ruby来说,您需要在类定义中实现eql?和hash方法,因为这些是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

从数组中移除 b,仅留下一个对象。
希望这可以帮到你!

我需要在对象上实现这个运算符,才能在包含该类型对象的两个数组上调用union函数吗? - user2128702
1
这是使类可枚举的要求。为了评估==,定义==就足够了。 - sawa
其实sawa的评论让我意识到我的答案是错误的,Array类需要实现eql?和hash方法才能识别唯一的对象,我已经更新了我的答案。 - fsaravia

4
< p > 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

这是最佳解决方案。覆盖eql?hash方法可能会很危险,因为对象的所有用户都将受到这些更改的影响。通过在本地定义块,您可以确保您的比较不会被意外使用。 - germs12

1

我尝试了上面 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

请不要介意我在示例中删除了@。这不会影响解决方案本身。只是我认为,鉴于已设置读取器方法,没有任何理由直接访问实例变量。

因此...我真正更改的内容在eql?方法中,我使用了hash而不是name。就是这样!


0

如果你看一下Array#|操作符,它使用了eql?方法,而在Object上,这个方法与==方法相同。你可以通过混入Comparable模块来定义它,然后实现<=>方法,这样你就可以免费获得许多比较方法。

<=>操作符非常容易实现:

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

你认为实现“==”运算符就足以调用包含该类型自定义对象的两个数组的并集操作吗? - user2128702
当然可以,而且实现 <=> 也同样容易,并且它会自动提供所有比较方法(包括 ==)。 - jbr
如果您按照手册(http://ruby-doc.org/core-2.0.0/Comparable.html)中描述的方式实现`<=>`方法,它应该可以正常工作。 - jbr
我不知道如何定义一个对象是否小于或大于另一个对象。它们只是普通的对象,可以具有所有属性的相等值或不具有相等值。我不知道这怎么能帮助实现<=>。 - user2128702
我将更新我的问题,包括运算符重载。 - user2128702
显示剩余2条评论

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]


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接