Ordinamento per condizioni multiple in Ruby
-
26-09-2019 - |
Domanda
Ho una raccolta di oggetti Post e voglio poterli ordinare in base a queste condizioni:
- Innanzitutto per categoria (news, eventi, laboratori, portfolio, ecc.)
- Quindi per data, se data, o per posizione, se è stato impostato un indice specifico
Alcuni post avranno date (notizie ed eventi), altri avranno posizioni esplicite (laboratori e portfolio).
Voglio poter chiamare posts.sort!
, quindi ho sovrascritto <=>
, ma sto cercando il modo più efficace per ordinare in base a queste condizioni.Di seguito è riportato uno pseudo metodo:
def <=>(other)
# first, everything is sorted into
# smaller chunks by category
self.category <=> other.category
# then, per category, by date or position
if self.date and other.date
self.date <=> other.date
else
self.position <=> other.position
end
end
Sembra che dovrei effettivamente ordinare due volte separate, piuttosto che stipare tutto in un unico metodo.Qualcosa di simile a sort_by_category
, Poi sort!
.Qual è il modo più rubino per farlo?
Soluzione
Dovresti sempre ordinare in base agli stessi criteri per garantire un ordine significativo.Se si confrontano due nil
date, va bene che il position
giudicherà l'ordine, ma se ne confrontiamo uno nil
date con una data fissa, devi decidere quale inizia per prima, indipendentemente dalla posizione (ad esempio mappando nil
ad un giorno lontano nel passato).
Altrimenti immagina quanto segue:
a.date = nil ; a.position = 1
b.date = Time.now - 1.day ; b.position = 2
c.date = Time.now ; c.position = 0
Secondo i tuoi criteri originali, avresti:un < b < c < a.Allora, qual è il più piccolo??
Anche tu vuoi fare l'ordinamento subito.Per il tuo <=>
implementazione, utilizzo #nonzero?
:
def <=>(other)
return nil unless other.is_a?(Post)
(self.category <=> other.category).nonzero? ||
((self.date || AGES_AGO) <=> (other.date || AGES_AGO)).nonzero? ||
(self.position <=> other.position).nonzero? ||
0
end
Se utilizzi i criteri di confronto solo una volta o se tali criteri non sono universali e quindi non vuoi definirli <=>
, potresti usare sort
con un blocco:
post_ary.sort{|a, b| (a.category <=> ...).non_zero? || ... }
Meglio ancora, c'è sort_by
E sort_by!
che puoi utilizzare per creare un array per cosa confrontare in quale priorità:
post_ary.sort_by{|a| [a.category, a.date || AGES_AGO, a.position] }
Oltre ad essere più breve, utilizzando sort_by
ha il vantaggio che si possono ottenere solo criteri ben ordinati.
Appunti:
sort_by!
è stato introdotto in Ruby 1.9.2.Puoirequire 'backports/1.9.2/array/sort_by'
per usarlo con i rubini più vecchi.- Lo presumo
Post
non è una sottoclasse diActiveRecord::Base
(nel qual caso vorresti che l'ordinamento fosse eseguito dal server db).
Altri suggerimenti
In alternativa potresti eseguire l'ordinamento in un colpo solo in un array, l'unico problema è gestire il caso in cui uno degli attributi è nil, anche se ciò potrebbe comunque essere gestito se conoscessi il set di dati selezionando la guardia nil appropriata.Inoltre non è chiaro dal tuo pseudocodice se i confronti di data e posizione sono elencati in un ordine di priorità o nell'uno o nell'altro (ad es.utilizzare la data se esiste per entrambi, altrimenti utilizzare la posizione).La prima soluzione presuppone uso, categoria, seguito dalla data, seguito dalla posizione
def <=>(other)
[self.category, self.date, self.position] <=> [other.category, other.date, other.position]
end
Il secondo presuppone la data o la posizione
def <=>(other)
if self.date && other.date
[self.category, self.date] <=> [other.category, other.date]
else
[self.category, self.position] <=> [other.category, other.position]
end
end