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现已弃用。