2012-01-08 70 views
2

我有我的目标国家的阵列,其具有的属性“代码”和“名”红宝石阵列的UNIQ无法工作

该阵列可以有一个国家在它不止一次,所以我想不同的阵列。

这是我的国家类

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

与你的问题没有关系,但'serialize'和'deserialize'可能应该是singleton方法而不是实例方法,不是? – 2012-01-08 18:16:10

回答

2

如果数组中的对象的#hash值重复,则该数组中的对象被认为是重复的Array#uniq,但在此代码中并非如此。你需要使用不同的方法来做什么意图,像这样:

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

谢谢你这个作品..我明白, ..但是第二张地图是干什么的......当我试图为数组中的每个代码获取一个Country对象时,我会阅读它,但如何简单地做“Country.Get”工作? – Steve 2012-01-08 14:59:42

+0

@Steve第二张地图根据他们的'code'属性重新创建Country对象。 'Country.get'的实现在你的问题中。 – 2012-01-08 15:03:19

+0

哈哈,但(对红宝石很新,所以我可能会在这里说一些愚蠢的)为什么它是“Country.get代码”而不是“Country.get(代码)” – Steve 2012-01-08 15:17:55

0

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

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

该ID是唯一的。

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

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

+0

那么我怎么能简化这个呢?我知道这是因为它将为每个had_drinks =>启动一个新的Country对象 – Steve 2012-01-08 14:56:05

2

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

你可以实现==,eql?并以一种对您的班级有意义的方式散列,例如通过比较各国的代码。

另一种方法是使用uniq_by。这是对Array的有效支持,但mongoid依赖于主动支持,因此您不会添加依赖项。

some_list_of_countries.uniq_by {|c| c.code} 

将使用国家的代码uniq他们。您可以缩短,要

some_list_of_countries.uniq_by(&:code) 
+0

谢谢这也是一个很好的解决方案 – Steve 2012-01-08 15:29:31

4

正如其他人所指出的,问题是,uniq使用hash默认情况下国家之间和区分,Object#hash是所有的对象不同。如果两个对象返回相同的hash值,那么它也将使用eql?,以确定它们是否是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

它使用'散列'作为第一遍,然后'eql?',如果他们有相同的'散列',对吧? – 2012-08-15 02:33:29

+0

@AndrewGrimm:是的,这是正确的,'eql?'将被调用任何重复的'hash'值(碰撞)。回答编辑 – 2012-08-15 04:06:39

0

至少早在1.9 .3,Array#uniq会像uniq_by一样使用块。 uniq_by现在已弃用。