Сортировка строки, которая может содержать время или расстояние - PullRequest
5 голосов
/ 08 июня 2009

Я реализовал алгоритм сортировки для пользовательской строки, которая представляет данные времени или расстояния для событий легкой атлетики. Ниже приведен формат

'10: 03.00 - Либо десять минут и три секунды, либо 10 футов, три дюйма

Результатом сортировки является то, что для полевых событий самый длинный бросок или прыжок будет первым элементом, тогда как для запуска событий самое быстрое время будет первым. Ниже приведен код, который я сейчас использую для событий на местах. Я не опубликовал running_event_sort, так как это та же логика с заменой больше / меньше чем. Хотя это работает, оно кажется слишком сложным и нуждается в рефакторинге. Я открыт для предложений. Любая помощь будет отличной.

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

Ответы [ 7 ]

4 голосов
/ 08 июня 2009

Это ситуация, когда #sort_by может значительно упростить ваш код:

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

Здесь я анализирую релевантные времена в массив целых чисел и использую их в качестве ключа сортировки данных. Сравнения массивов выполняются поэтапно, причем первое является наиболее значимым, поэтому это хорошо работает.

Одна вещь, которую вы не делаете, это преобразовывает цифры в целые числа, что вы, скорее всего, хотите сделать. В противном случае у вас будут проблемы с "100" < "2" #=> true. Вот почему я добавил шаг #map.

Кроме того, в вашем регулярном выражении квадратные скобки вокруг \d не нужны, хотя вы хотите избежать точки, чтобы она не соответствовала всем символам.

Один из способов, которым код, который я дал, не совпадает с кодом, который вы дали, - это ситуация, когда линия не содержит никаких расстояний. Ваш код будет сравнивать их как равные окружающим строкам (что может привести к неприятностям, если алгоритм сортировки предполагает, что равенство транзитивно. То есть a == b, b == c подразумевает a ==c, что не относится к вашему коду: для пример a = "'10:00.1", b = "frog", c="'9:99:9").

#sort_by сортируется в порядке возрастания, поэтому вызов #reverse изменит его в порядке убывания. #sort_by также имеет то преимущество, что анализирует только значения сравнения один раз, тогда как ваш алгоритм должен будет анализировать каждую строку для каждого сравнения.

1 голос
/ 08 июня 2009

Вместо того, чтобы реализовывать подобную сортировку, возможно, у вас должны быть модели TrackTime и FieldDistance. Они не обязательно должны быть сохранены - Участник модель может создавать их из time_distance при загрузке.

Возможно, вы захотите получить разницу между двумя значениями, проверить значения, а также отсортировать значения в будущем. Модель позволит легко добавить эти функции. Также это значительно упростит юнит-тестирование.

Я бы также разделил время и расстояние на два отдельных поля. Наличие столбцов двойного назначения в базе данных только причиняет боль в моем опыте.

0 голосов
/ 08 июня 2009

В этом сценарии:

Поскольку вы знаете, что работаете в футах, дюймах и (какой бы ни была ваша третья единица измерения), почему бы просто не создать общую сумму двух значений, которые вы сравниваете?

Итак, после этих двух строк:

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

Генерация общего расстояния для a_parts и b_parts:

totalDistanceA = a_parts [0] [0] .to_i * 12 + a_parts [0] [1] .to_i + b_parts [0] [2] .to_i * (независимо от того, какая у вас третья единица измерения для размера дюйм)

totalDistanceB = b_parts [0] [0] .to_i * 12 + b_parts [0] [1] .to_i + b_parts [0] [2] .to_i * (независимо от того, какая у вас третья единица измерения, зависит от размера дюйм)

Затем верните сравнение этих двух значений:

totalDistanceA <=> totalDistanceB

Обратите внимание, что вы должны сохранить уже выполненную валидацию, которая проверяет, являются ли a_parts и b_parts пустыми:

a_parts.empty? || b_parts.empty

Для выполнения сценария сортировки по времени выполните одно и то же, за исключением разных факторов (например, от 60 секунд до минуты).

0 голосов
/ 08 июня 2009

Я согласен, что преобразование в целые числа сделает проще. Также обратите внимание, что для целых чисел

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

можно упростить до a<=>b. Чтобы получить обратное, используйте -(a <=> b).

0 голосов
/ 08 июня 2009

Я не знаю ruby, но вот какой-то c-подобный псевдокод, который немного его реорганизует.

/// 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 голосов
/ 08 июня 2009

Почему бы не сделать

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

Числа не имеют смысла вычитать и т. Д., Но они должны сортироваться нормально.

Вы можете проверить, чтобы [1] и [2] всегда были двумя цифрами в регулярном выражении.

0 голосов
/ 08 июня 2009

Ваша рутина выглядит хорошо, но вы можете просто удалить '' ',': 'и'. ' и обработать результат как числовую строку. Другими словами, 10 '5 "станет 1005, а 10' 4" будет 1004. 1005 явно больше 1004.

Так как элементы высшего порядка находятся слева, он будет сортироваться естественным образом. Это также работает со временем по тем же причинам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...