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?

Foi útil?

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ê pode require 'backports/1.9.2/array/sort_by' para usá -lo com rubis mais velhos.
  • Estou assumindo isso Post não é uma subclasse de ActiveRecord::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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top