Uniq的ruby数组无法正常工作

我有一个我的对象Country的数组,它具有“代码”和“名称”的属性

该arrays可能不止一次有一个国家,所以我想区分数组。

这是我的国家级

class Country include Mongoid::Fields::Serializable attr_accessor :name, :code FILTERS = ["Afghanistan","Brunei","Iran", "Kuwait", "Libya", "Saudi Arabia", "Sudan", "Yemen", "Britain (UK)", "Antarctica", "Bonaire Sint Eustatius & Saba", "British Indian Ocean Territory", "Cocos (Keeling) Islands", "St Barthelemy", "St Martin (French part)", "Svalbard & Jan Mayen","Vatican City"] EXTRAS = { 'eng' => 'England', 'wal' => 'Wales', 'sco' => 'Scotland', 'nlr' => 'Northern Ireland' } def initialize(name, code) @name = name @code = code end def deserialize(object) return nil unless object Country.new(object['name'], object['code']) end def serialize(country) {:name => country.name, :code => country.code} end def self.all add_extras(filter(TZInfo::Country.all.map{|country| to_country country})).sort! {|c1, c2| c1.name  c2.name} end def self.get(code) begin to_country TZInfo::Country.get(code) rescue TZInfo::InvalidCountryCode => e 'InvalidCountryCode' unless EXTRAS.has_key? code Country.new EXTRAS[code], code end end def self.get_by_name(name) all.select {|country| country.name.downcase == name.downcase}.first end def self.filter(countries) countries.reject {|country| FILTERS.include?(country.name)} end def self.add_extras(countries) countries + EXTRAS.map{|k,v| Country.new v, k} end private def self.to_country(country) Country.new country.name, country.code end end 

和我对从另一个类调用的数组的请求

  def countries_ive_drunk (had_drinks.map {|drink| drink.beer.country }).uniq end 

如果我抛出数组,我可以看到结构是:

 [ #, #, #, #, #, #, #, #, #, #, #, #, # ] 

这是一样的,无论我是否.uniq,你可以看到有两个“安圭拉”

如果#hash值为重复,则Array#uniq中的数组中的对象被认为是重复的,但在此代码中并非如此。 您需要使用不同的方法来执行预期的操作,如下所示:

 def countries_ive_drunk had_drinks.map {|drink| drink.beer.country.code } .uniq .map { |code| Country.get code} end 

正如其他人所指出的,问题是uniq使用hash来区分国家,默认情况下, Object#hash对所有对象都不同。 它还会用eql? 如果两个对象返回相同的hash值,以确定它们是否为eql。

最好的解决方案是让您的课程正确!

 class Country # ... your previous code, plus: include Comparable def <=>(other) return nil unless other.is_a?(Country) (code <=> other.code).nonzero? || (name <=> other.name) # or less fancy: # [code, name] <=> [other.code, other.name] end def hash [name, code].hash end alias eql? == end Country.new("Canada", "CA").eql?(Country.new("Canada", "CA")) # => true 

现在你可以对国家数组进行排序,使用国家作为哈希的关键,比较它们等等……

我已经包含上面的代码来展示它是如何完成的,但在你的情况下,如果你Struct(:code, :name) ,你可以免费获得所有这些…

 class Country < Stuct(:name, :code) # ... the rest of your code, without the `attr_accessible` nor the `initialize` # as Struct provides these and `hash`, `eql?`, `==`, ... end 

这归结为平等意味着什么? 对象什么时候与另一个对象重复? ==,eql的默认实现? 只是比较ruby object_id,这就是为什么你没有得到你想要的结果。

你可以实现==,eql? 并以对您的课程有意义的方式散列,例如通过比较国家/地区的代码。

另一种方法是使用uniq_by 。 这是对Array一个主动支持,但mongoid仍然依赖于主动支持,所以你不会添加依赖。

 some_list_of_countries.uniq_by {|c| c.code} 

会使用国家的代码来统一它们。 你可以缩短它

  some_list_of_countries.uniq_by(&:code) 

数组中的每个元素都是单独的类实例。

 # # 

ids是独一无二的。

 #, # 

Array #uniq认为这些是不同的对象(Country类的不同实例),因为对象的ID是不同的。 显然你需要改变策略。

至少早在1.9.3,Array #uniq就会像uniq_by一样占用一块。 uniq_by现已弃用。