2009-06-07 14 views
5

he implementado un algoritmo de ordenación de una cadena personalizada que representa ya sea datos de distancia de la pista & eventos de campo de tiempo o. A continuación se muestra el formatoOrdenar una cadena que podría contener un tiempo o distancia

'10: 03.00 - Cualquiera de diez minutos y tres segundos o 10 pies, tres pulgadas

El resultado de eso es que para los eventos de campo, de banda o salto más largo sería el primer elemento mientras que para correr eventos, el mejor tiempo sería el primero. A continuación se muestra el código que estoy usando actualmente para eventos de campo. No publiqué el running_event_sort ya que es la misma lógica con el mayor/menor que el intercambio. Mientras funciona, parece demasiado complejo y necesita ser refactorizado. Estoy abierto a sugerencias. Cualquier ayuda sería genial.

event_participants.sort!{ |a, b| Participant.field_event_sort(a, b) } 

class Participant 
def self.field_event_sort(a, b) 
    a_parts = a.time_distance.scan(/'([\d]*):([\d]*).([\d]*)/) 
    b_parts = b.time_distance.scan(/'([\d]*):([\d]*).([\d]*)/) 

    if(a_parts.empty? || b_parts.empty?) 
    0 
    elsif a_parts[0][0] == b_parts[0][0] 
    if a_parts[0][1] == b_parts[0][1] 
     if a_parts[0][2] > b_parts[0][2] 
     -1 
     elsif a_parts[0][2] < b_parts[0][2] 
     1 
     else 
     0 
     end 
    elsif a_parts[0][1] > b_parts[0][1] 
     -1 
    else 
     1 
    end 
    elsif a_parts[0][0] > b_parts[0][0] 
    -1 
    else 
    1 
    end 
end 
end 

Respuesta

4

Esta es una situación en la que #sort_by podría simplificar enormemente su código:

event_participants = event_participants.sort_by do |s| 
    if s =~ /'(\d+):(\d+)\.(\d+)/ 
     [ $1, $2, $3 ].map { |digits| digits.to_i } 
    else 
     [] 
    end 
end.reverse 

Aquí, analizar los momentos relevantes en una matriz de enteros, y utilizar los como una clave de clasificación para los datos. Las comparaciones de matrices se realizan entrada por entrada, siendo la primera la más significativa, por lo que esto funciona bien.

Una cosa que no debes hacer es convertir los dígitos en enteros, lo que probablemente quieras hacer. De lo contrario, tendrá problemas con "100" < "2" #=> true. Es por eso que agregué el paso #map.

Además, en su expresión regular, los corchetes alrededor de \d son innecesarios, aunque desea evitar el período para que no coincida con todos los caracteres.

Una de las formas en que el código que di no coincide con el código que proporcionó es en una situación en la que una línea no contiene ninguna distancia. Su código se compara como igual a las líneas que rodean (que se puede meter en problemas si el algoritmo de ordenación asume la igualdad es transitiva Es decir a == b, b == c implica a ==c, que no es el caso de su código:., Por ejemplo a = "'10:00.1", b = "frog", c="'9:99:9")

#sort_by ordena en orden ascendente, por lo que la llamada a #reverse la cambiará en orden descendente. #sort_by también tiene la ventaja de solo analizar los valores de comparación una vez, mientras que su algoritmo tendrá que analizar cada línea para cada comparación.

+0

Gracias por la ayuda. Necesito agregar algunas validaciones para asegurarme de que el campo de tiempo/distancia esté en el formato correcto. Necesito leer sobre el método .map para entender lo que realmente está haciendo. –

+0

#map toma un Enumerable (en este caso, una matriz) y ejecuta el bloque dado en cada elemento, devolviendo una matriz de resultados (en el mismo orden, en este caso) – rampion

0

Su rutina se ve bien, pero solo podría eliminar el '' ',': 'y'. ' y trata el resultado como una cadena numérica. En otras palabras, 10 '5 "se convertiría en 1005 y 10' 4" sería 1004. 1005 es claramente más de 1004.

Dado que los elementos de la orden más alta están a la izquierda, se ordenarán naturalmente. Esto también funciona con el tiempo por las mismas razones.

+0

Me gusta la idea de multiplicar los valores en lugar de simplemente eliminar los,, y. ya que maneja errores comunes. Sin la multiplicación 10'04 "= 1004, 10'4" = 104. – RichH

+1

Sí, pero en este orden "10 '" <=> "9'" == -1, por lo que piensa que "9" es un valor más alto, y lo ordenará primero. – rampion

0

¿Por qué no

a_val = a_parts[0][0].to_i * 10000 + a_parts[0][1].to_i * 100 + a_parts[0][2].to_i 
b_val = b_parts[0][0].to_i * 10000 + b_parts[0][1].to_i * 100 + b_parts[0][2].to_i 
a_val <=> b_val 

Los números no tienen sentido para restar, etc, pero entonces deberían solucionarlo bien.

Es posible que desee comprobar [1] y [2] siempre dos dígitos en la expresión regular.

+0

Solo necesita ser * 1000, pero esto hace lo mismo que yo dije entonces * debe * ser una buena respuesta! :) – Craig

+0

Tiene que ser de dos órdenes de magnitud entre * 10000 y * 100, ya que podría tener hasta 59 segundos o 11 pulgadas. Las grandes mentes piensan igual (¡simplemente tecleo unos segundos más lentamente que tú!) :) – RichH

+1

Sí, esta es una mala idea, ya que estos valores son todos todavía cuerdas. No se han convertido en enteros, por lo que multiplicarlos por cualquier cosa simplemente replicará la cadena. – rampion

0

No sé rubí Pero aquí hay algunos seudo código de c-como que refactors esto un poco.

/// In c, I would probably shorten this with the ? operator. 
int compareIntegers(a, b) { 
    int result = 0; 
    if (a < b) { 
     result = -1; 
    } else if (a > b) { 
     result = 1; 
    } 
    return result; 
} 

int compareValues(a, b) { 
    int result = 0; 
    if (!/* check for empty*/) { 
     int majorA = /* part before first colon */ 
     int majorB = /* part before first colon */ 
     int minorA = /* part after first colon */ 
     int minorB = /* part after first colon */ 

     /// In c, I would probably shorten this with the ? operator. 
     result = compareIntegers(majorA, majorB); 
     if (result == 0) { 
      result = compareIntegers(minorA, minorB); 
     } 
    } 
    return result; 
} 
+0

También podría usar la sugerencia de Craig para combinar majorA, y minorA en un valor, y solo llamar compareIntegers una vez. Además, si la parte que "se vuelve parte antes del primer colon" devuelve 0 cuando no hay dos puntos, puede evitar la guardia "if (! Empty)". –

1

En lugar de implementar el género de esta manera, tal vez debería tener un modelo de tiempo de pista y FieldDistance. No es necesario que persistan: el modelo Participante podría crearlos desde time_distance cuando se carguen.

lo que probablemente va a querer ser capaz de obtener la diferencia entre dos valores, validar los valores, así ordenar los valores en el futuro. El modelo facilitaría agregar estas características. También haría las pruebas unitarias mucho más fáciles.

que había también el tiempo y la distancia separada en dos campos separados. Tener columnas de doble propósito en la base de datos solo causa dolor en la línea en mi experiencia.

0

Acepto que la conversión a enteros será más simple. También tenga en cuenta que para los enteros

if a > b 
    1 
elsif a < b 
    -1 
else 
    0 

se puede simplificar a a<=>b. Para obtener el uso inverso, -(a <=> b).

0

En este escenario:

Puesto que usted sabe que está trabajando con los pies, pulgadas, y (sea cual sea su tercera unidad de medida es), ¿por qué no crear una suma total de los dos valores que se comparan?

Así que después de estas dos líneas:

a_parts = a.time_distance.scan (/ '([\ d] .): ([\ D]) ([\ d] ) /) b_parts = b.time_distance.scan (/ '([\ d]): ([\ d] ) ([\ d]) /.)

Generar la distancia total para a_parts y b_parts:

totalDistanceA = a_parts [0] [0] .to_i * 12 + a_parts [0] [1] .to_i + b_parts [0] [2] .to_i * (cualquiera que sea su tercera unidad de factor de medida contra el tamaño de una pulgada)

totalDistanceB = b_parts [0] [0] .to_i * 12 + b_parts [0] [1] .to_i + b_parts [0] [2] .to_i * (Sea cual sea su tercera unidad de factor de medida en función del tamaño de una pulgada)

continuación, regrese la comparación de estos dos valores:

totalDistanceA < => totalDistanceB

Tenga en cuenta que usted debe tener la validación ya está haciendo que compruebe si a_parts y b_parts están vacías o no:

a_parts.empty? || b_parts.empty?

Para hacer el escenario de clasificación por tiempo, haga exactamente lo mismo excepto con diferentes factores (por ejemplo, de 60 segundos a un mínimo).

Cuestiones relacionadas