ruby 数组的 Uniq 无法工作
我有一个对象 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 并且您可以看到有两个“安圭拉” ”
I have a array of my object Country which has the attributes "code" and "name"
The array could have a country in it more than once so I want to distinct the array.
This is my countries class
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
and my request for the array which is called from another class
def countries_ive_drunk
(had_drinks.map {|drink| drink.beer.country }).uniq
end
If I throw the array I can see the structure is:
[
#<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">
]
This is the same, whether or not I do .uniq and you can see there is two "Anguilla"
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
至少早在 1.9.3 版本中,Array#uniq 就会像 uniq_by 一样占用一个块。 uniq_by 现已弃用。
At least as early as 1.9.3, Array#uniq would take a block just like uniq_by. uniq_by is now deprecated.
正如其他人所指出的,问题在于
uniq
使用hash
来区分国家/地区,并且默认情况下,Object#hash
对于所有国家/地区都是不同的对象。如果两个对象返回相同的hash
值,它还会使用eql?
来确定它们是否是 eql。最好的解决办法就是首先让你的班级正确!
现在,您可以对国家/地区数组进行排序,使用国家/地区作为哈希值的键,比较它们等等...
我已经包含了上面的代码来展示它的一般情况,但在您的情况下,如果您愿意,您可以免费获得所有这些子类
Struct(:code, :name)
...As pointed out by others, the problem is that
uniq
useshash
to distinguish between countries and that by default,Object#hash
is different for all objects. It will also useeql?
in case two objects return the samehash
value, to be sure if they are eql or not.The best solution is to make your class correct in the first place!
Now you can sort arrays of Countries, use countries as key for hashes, compare them, etc...
I've included the above code to show how it's done in general, but in your case, you get all this for free if you subclass
Struct(:code, :name)
...如果数组中的对象的
#hash
值重复,则Array#uniq
会将其视为重复,但此代码中的情况并非如此。您需要使用不同的方法来完成预期的任务,如下所示:Objects in array are considered duplicate by
Array#uniq
if their#hash
values are duplicate, which is not the case in this code. You need to use different approach to do what intended, like this:这归结为平等意味着什么?什么时候一个对象是另一个对象的复制品? ==、eql 的默认实现?只需比较 ruby object_id 这就是为什么你没有得到你想要的结果。
你可以实现 ==, eql 吗?并以对您的班级有意义的方式进行哈希处理,例如通过比较国家/地区的代码。
另一种方法是使用
uniq_by
。这是对 Array 的主动支持补充,但 mongoid 无论如何都依赖于主动支持,因此您不会添加依赖项。将使用国家/地区代码来区分它们。您可以将其缩短为
This boils down to what does equality mean? When is an object a duplicate of another? The default implementations of ==, eql? just compare the ruby object_id which is why you don't get the results you want.
You could implement ==, eql? and hash in a way that makes sense for your class, for example by comparing the countries' codes.
An alternative is to use
uniq_by
. This is an active support addition toArray
, but mongoid depends on active support anyway, so you wouldn't be adding a dependency.Would use countries' codes to uniq them. You can shorten that to
数组中的每个元素都是单独的类实例。
id 是唯一的。
Each element in the array is separate class instance.
The ids are unique.
Array#uniq 认为这些是不同的对象(Country 类的不同实例),因为对象的 id 不同。
显然你需要改变你的策略。
Array#uniq thinks these are different objects (different instances of Country class), because the objects' ids are different.
Obviously you need to change your strategy.