8

tengo una colección de Correos objetos y quiero ser capaz de clasificarlos en base a estas condiciones:Clasificación por las condiciones múltiples en Ruby

  • En primer lugar, por categorías (noticias, eventos, laboratorios, cartera, etc. .)
  • a continuación, por fecha, si la fecha, o por la posición, si un índice específico se fija para que

Algunos mensajes tendrán fechas (noticias y eventos), otros tendrán posiciones explícitas (laboratorios, y portafolio).

Deseo poder llamar al posts.sort!, por lo que he reemplazado <=>, pero estoy buscando la manera más efectiva de ordenar por estas condiciones. A continuación se muestra un método de 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 como si en realidad tendría que ordenar dos veces por separado, en lugar de abarrotar todo en que un método. Algo así como sort_by_category, luego sort!. ¿Cuál es la forma más rápida de hacer esto?

Respuesta

12

Siempre debe ordenar por los mismos criterios para asegurar una orden significativa. Si se comparan dos fechas nil, está bien que el position juzgue el pedido, pero si se compara una fecha nil con una fecha determinada, debe decidir qué va primero, independientemente de la posición (por ejemplo, mapeando nil a un día manera en el pasado).

imaginar lo contrario, el siguiente:

a.date = nil     ; a.position = 1 
b.date = Time.now - 1.day  ; b.position = 2 
c.date = Time.now    ; c.position = 0 

Por sus criterios originales, que tendría: una < b < c < a. Entonces, ¿cuál es el más pequeño?

También desea hacer el tipo a la vez. Para su aplicación <=>, utilice #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 

Si utiliza sus criterios de comparación sólo una vez, o si ese criterio no es universal y por lo tanto no desea definir <=>, podría utilizar sort con un bloque:

post_ary.sort{|a, b| (a.category <=> ...).non_zero? || ... } 

Mejor aún, hay sort_by y sort_by! que se puede utilizar para construir una matriz para lo que comparar en el que la prioridad:

post_ary.sort_by{|a| [a.category, a.date || AGES_AGO, a.position] } 

Además de ser más corto, usar sort_by tiene la ventaja de que solo puede obtener un criterio bien ordenado.

Notas:

  • sort_by! se introdujo en Ruby 1.9.2. Puede require 'backports/1.9.2/array/sort_by' para usarlo con rubíes anteriores.
  • Supongo que Post no es una subclase de ActiveRecord::Base (en cuyo caso, desea que el servidor db haga la clasificación).
+0

Gracias, yo no era consciente de '# distinto de cero numérico?'. ¿No es un poco extraño que un '' '-método devuelva valores no booleanos? –

+0

@Mladen: Sí, pero bastante útil. Otro ejemplo en el que puedes esperar un 'verdadero/falso':' String

+0

Esto es un poco engañoso: 'post_ary.sort_by {| a, b | (a.category <=> ...)} ' sort_by no toma un bloque con dos argumentos. En cambio, para problemas de clasificación más complejos, debe devolver una matriz. Es decir: 'post_ary.sort_by {| a | [a.category, a.date, a.position]} ' – Timo

3

Alternativamente, podría hacer el ordenamiento de una sola vez en una matriz, el único problema es que uno de los atributos es nulo, aunque eso aún podría manejarse si supiera el conjunto de datos seleccionando protector nil apropiado. Tampoco está claro desde su código de psuedo si las comparaciones de fecha y posición se enumeran en un orden de prioridad o en uno u otro (es decir, use la fecha si existe para ambas posiciones de uso). La primera solución supone el uso, la categoría, seguido de la fecha, seguido por la posición

def <=>(other) 
    [self.category, self.date, self.position] <=> [other.category, other.date, other.position] 
end 

segundo asume que es la fecha o la posición

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

Ah, se había olvidado del 'nil' de fechas. Este orden de clasificación no está bien ordenado (ver mi respuesta actualizada). –

+0

Para mi aprendizaje, ¿a qué te refieres con que no está bien ordenado? – naven87

+2

Para una orden fundada, siempre se cumple lo siguiente: 'a

Cuestiones relacionadas