Classificação por várias condições em rubi
-
26-09-2019 - |
Pergunta
Eu tenho uma coleção de objetos de postagem e quero ser capaz de classificá -los com base nessas condições:
- Primeiro, por categoria (notícias, eventos, laboratórios, portfólio, etc.)
- Então, por data, se data ou por posição, se um índice específico foi definido para isso
Algumas postagens terão datas (notícias e eventos), outros terão posições explícitas (laboratórios e portfólio).
Eu quero poder ligar posts.sort!
, então eu substituí <=>
, mas estou procurando a maneira mais eficaz de classificar por essas condições. Abaixo está um método pseudo:
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
Parece que eu teria que classificar dois momentos separados, em vez de amontoar tudo nesse método. Algo como sort_by_category
, então sort!
. Qual é a maneira mais rubi de fazer isso?
Solução
Você deve sempre classificar os mesmos critérios para garantir uma ordem significativa. Se comparar dois nil
datas, é bom que o position
julgará a ordem, mas se comparar um nil
data com uma data definida, você deve decidir qual é o primeiro, independentemente da posição (por exemplo, mapeando nil
para um dia no passado).
Caso contrário, imagine o seguinte:
a.date = nil ; a.position = 1
b.date = Time.now - 1.day ; b.position = 2
c.date = Time.now ; c.position = 0
Por seus critérios originais, você teria: a <b <c <a. Então, qual é o menor ??
Você também quer fazer o tipo de uma vez. Para o seu <=>
implementação, uso #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 você usar seus critérios de comparação apenas uma vez, ou se esse critério não for universal e, portanto, não quiser definir <=>
, você poderia usar sort
com um bloco:
post_ary.sort{|a, b| (a.category <=> ...).non_zero? || ... }
Melhor ainda, existe sort_by
e sort_by!
que você pode usar para construir uma matriz para o que comparar em que prioridade:
post_ary.sort_by{|a| [a.category, a.date || AGES_AGO, a.position] }
Além de ser mais curto, usando sort_by
Tem a vantagem de que você só pode obter um critério bem ordenado.
Notas:
sort_by!
foi introduzido no Ruby 1.9.2. Você poderequire 'backports/1.9.2/array/sort_by'
para usá -lo com rubis mais velhos.- Estou assumindo isso
Post
não é uma subclasse deActiveRecord::Base
(Nesse caso, você deseja que o tipo seja feito pelo servidor DB).
Outras dicas
Como alternativa, você pode fazer o tipo de uma vez em uma matriz, o único Gotcha está lidando com o caso em que um dos atributos é nulo, embora isso ainda possa ser tratado se você soubesse o conjunto de dados selecionando o Nil Guard apropriado. Além disso, não está claro no seu código PSUDO se as comparações de data e posição estiverem listadas em uma ordem prioritária ou uma ou outra (ou seja, a data de uso, se existe para ambos os outros usam a posição). A primeira solução assume uso, categoria, seguida de data, seguida pela posição
def <=>(other)
[self.category, self.date, self.position] <=> [other.category, other.date, other.position]
end
Segundo assume sua data ou posição
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