2012-01-08 13 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を実行しているかどうかにかかわらず同じです.2つの「アンギラ」があることがわかります

+0

あなたの質問には関係ありませんが、 'serialize'と' deserialize'はインスタンスメソッドではなくシングルトンメソッドでしょうか? –

答えて

2

#hashの値が重複している場合、配列内のオブジェクトはArray#uniqで重複していると見なされますが、これはこのコードでは当てはまりません。平等はどういう意味

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

ありがとうございます。私は今、全体の代わりに "コード" ..しかし、2番目のマップは何をするのですか...配列内にあるコードごとにCountryオブジェクトを取得しようとしていますが、単純に "Country.Get"をどのように処理するのですか? – Steve

+0

@Steveの2番目のマップは、国オブジェクトを 'code'プロパティに基づいて再作成します。 'Country.get'の実装はあなたの質問にあります。 –

+0

haha​​ yehしかし(なぜ新しいルビーなので、ここで何か愚かなことを言うかもしれません)どうして "Country.get code"ではなく "Country.get(code)" – Steve

0

アレイの各要素は、別々のクラスインスタンスです。

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

idsは一意です。

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

配列#uniqは、オブジェクトのIDが異なるため、これは異なるオブジェクト(Countryクラスの異なるインスタンス)であると考えます。 明らかに戦略を変更する必要があります。

+0

これを単純化するにはどうすればいいですか?私はこれが起こっていることを理解しています。それは、それぞれのhad_drinks =>ビールのために新しいCountryオブジェクトを初期化するからです。 – Steve

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) 
+0

ありがとうこれはまた非常に良い解決策です – Steve

4

にそれを短縮することができ、問題がuniqはデフォルトで国の間とすることを区別するためにhashを使用することで、Object#hashは、すべてのオブジェクトのために異なっています。また、2つのオブジェクトが同じ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)をサブクラス化する場合は、あなたの場合には、あなたが自由のためにこのすべてを取得しています方法を示すために、上記のコードを含めました

...少なくとも、早けれ1.9として

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

最初のパスとして 'hash'を使用し、同じ' hash'を持っている場合は 'eql?'を使用します。 –

+0

@AndrewGrimm:はい、正しいですが、 'eql? 'は重複する' hash'値(衝突)に対して呼び出されます。回答が編集されました –

0

.3、配列#uniqはuniq_byのようにブロックをとるでしょう。 uniq_byは廃止予定です。

関連する問題