我有一个对象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,你可以看到有两个“安圭拉”
答案 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现已弃用。