2013-08-29 4 views
5

Il metodo <=> deve restituire -1, 0 o 1 per "minore di", "uguale a" e "maggiore di", rispettivamente. Per alcuni tipi di oggetti ordinabili, è normale basare l'ordinamento su più proprietà. I seguenti lavori, ma penso che sembra goffo:C'è una bella sintassi per il confronto complesso?

class LeagueStats 
    attr_accessor :points, :goal_diff 
    def initialize pts, gd 
    @points = pts 
    @goal_diff = gd 
    end 
    def <=> other 
    compare_pts = points <=> other.points 
    return compare_pts unless compare_pts == 0 
    goal_diff <=> other.goal_diff 
    end 
end 

Provarlo:

[ 
    LeagueStats.new(10, 7), 
    LeagueStats.new(10, 5), 
    LeagueStats.new(9, 6) 
].sort 
# => [ 
#  #<LS @points=9, @goal_diff=6>, 
#  #<LS @points=10, @goal_diff=5>, 
#  #<LS @points=10, @goal_diff=7> 
# ] 

Perl tratta 0 come un valore falso, che permette il confronto complessi con differenti sintassi:

{ 
    return ($self->points <=> $other->points) || 
     ($self->goal_diff <=> $other->goal_diff); 
} 

Trovo la caduta attraverso 0 tramite l'operatore || semplice da leggere ed elegante. Una cosa che mi piace dell'uso di || è il cortocircuito dei calcoli una volta che il confronto ha un valore.

Non riesco a trovare nulla di simile in Ruby. Ci sono modi migliori per costruire lo stesso complesso di confronti (o qualsiasi altra cosa che preleva il primo elemento diverso da zero), idealmente senza dover calcolare tutti i valori in anticipo?

risposta

7

Oltre alla risposta di Sawa, il risultato può anche dovrebbe essere valutato sul posto, al fine di restituire -1 , 0 o +1:

class LeagueStats 
    def <=> other 
    [points, goal_diff] <=> [other.points, other.goal_diff] 
    end 
end 

Questo lavoro è se di Array#<=>:

Gli array vengono confrontati in modo "element-wise"; i primi due elementi che non sono uguali determineranno il valore di ritorno per l'intero confronto .

Dopo l'implementazione <=>, è possibile includere Comparable e ottenere <, <=, ==, >=, > e between? gratuitamente.

3

È possibile utilizzare Enumerable#sort_by invece di sort:

[LeagueStats.new(10,7),LeagueStats.new(10,5),LeagueStats.new(9,6)].sort_by { |ls| 
    [ls.points, ls.goal_diff] 
} 
# => [#<LeagueStats:0x00000001a806c8 @points=9, @goal_diff=6>, 
#  #<LeagueStats:0x00000001a806f0 @points=10, @goal_diff=5>, 
#  #<LeagueStats:0x00000001a80718 @points=10, @goal_diff=7>] 

class LeagueStats 
    ... 

    def to_a 
    [points, goal_diff] 
    end 

    def <=> other 
    to_a <=> other.to_a 
    end 
end 
+0

Questo è bello, ma mi piacerebbe che LeagueStats abbia un ordinamento inerente, è quello per cui sono. –

+0

@NeilSlater, OK, ho aggiunto un altro codice derivato dalla risposta di Sawa. – falsetru