Uniq的ruby数组无法正常工作

时间:2012-01-08 14:33:04

标签: ruby arrays distinct

我有一个对象Country的数组,其中包含“code”和“name”

属性

数组中可能有多个国家/地区,所以我想区分数组。

这是我的国家/地区

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

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

[
#<Country:0x5e3b4c8 @name="Belarus", @code="BY">, 
#<Country:0x5e396e0 @name="Britain (UK)", @code="GB">, 
#<Country:0x5e3f350 @name="Czech Republic", @code="CZ">, 
#<Country:0x5e3d730 @name="Germany", @code="DE">, 
#<Country:0x5e43778 @name="United States", @code="US">, 
#<Country:0x5e42398 @name="England", @code="eng">, 
#<Country:0x5e40f70 @name="Aaland Islands", @code="AX">, 
#<Country:0x5e47978 @name="England", @code="eng">, 
#<Country:0x5e46358 @name="Portugal", @code="PT">, 
#<Country:0x5e44d38 @name="Georgia", @code="GE">, 
#<Country:0x5e4b668 @name="Germany", @code="DE">, 
#<Country:0x5e4a2a0 @name="Anguilla", @code="AI">, 
#<Country:0x5e48c98 @name="Anguilla", @code="AI">
]

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

6 个答案:

答案 0 :(得分:5)

正如其他人所指出的,问题是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

答案 1 :(得分:2)

如果Array#uniq#hash重复,那么数组中的对象被认为是def countries_ive_drunk had_drinks.map {|drink| drink.beer.country.code } .uniq .map { |code| Country.get code} end 的副本,而在此代码中并非如此。您需要使用不同的方法来执行预期的操作,例如:

{{1}}

答案 2 :(得分:2)

这归结为平等意味着什么?对象何时与另一个对象重复? ==,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)

答案 3 :(得分:0)

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

#<Country:0x5e4a2a0 @name="Anguilla", @code="AI"> 
#<Country:0x5e48c98 @name="Anguilla", @code="AI">

ids是独一无二的。

答案 4 :(得分:0)

#<Country:0x5e4a2a0 @name="Anguilla", @code="AI">, 
#<Country:0x5e48c98 @name="Anguilla", @code="AI">

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

答案 5 :(得分:0)

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