对包含时间或距离的字符串进行排序

时间:2009-06-07 23:05:33

标签: ruby algorithm sorting refactoring

我已经为自定义字符串实现了一种排序算法,该字符串表示track& amp;的时间或距离数据。实地活动。以下是格式

'10:03.00 - 10分3秒或10英尺3英寸

排序的结果是,对于场事件,最长的投掷或跳跃将是第一个元素,而对于运行事件,最快的时间将是第一个。以下是我目前用于现场活动的代码。我没有发布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 个答案:

答案 0 :(得分:4)

这种情况#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 == bb == c隐含a ==c,这不是您的代码的大小写:例如a = "'10:00.1"b = "frog"c="'9:99:9")。

#sort_by按升序排序,因此对#reverse的调用会将其更改为降序。 #sort_by的优点还在于只解析了一次比较值,而你的算法必须为每次比较解析每一行。

答案 1 :(得分:1)

不是实现这样的排序,也许你应该有TrackTime和FieldDistance模型。他们不一定需要坚持 - 参与者 模型可以在加载时从time_distance创建它们。

您可能希望能够获得两个值之间的差异,以及将来验证值以及排序值。该模型可以轻松添加这些功能。此外,它还可以使单元测试变得更加容易。

我还将时间和距离分成两个单独的字段。在我的经验中,在数据库中使用双重用途的列只会导致痛苦。

答案 2 :(得分:0)

你的日常工作看起来不错,但你可以删除''',':'和'。'并将结果视为数字字符串。换句话说,10'5“将变为1005,10'4”将为1004. 1005显然超过1004.

由于更高阶的元素在左边,它会自然地排序。出于同样的原因,这也适用于时间。

答案 3 :(得分:0)

为什么不

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]总是在正则表达式中的两位数。

答案 4 :(得分:0)

我不知道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;
}

答案 5 :(得分:0)

我同意转换为整数会更简单。另请注意,对于整数

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

可以简化为a<=>b。要反过来使用-(a <=> b)

答案 6 :(得分:0)

在这种情况下:

既然你知道你正在使用英尺,英寸和(无论你的第三个测量单位是什么),为什么不只是创建你要比较的两个值的总和呢?

所以在这两行之后:

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&lt; =&gt; totalDistanceB

请注意,如果a_parts和b_parts为空,您应该保留已经进行检查的验证:

a_parts.empty? || b_parts.empty?

对于按时间进行排序的情况,除了使用不同的因素(例如,60秒到一分钟)之外,执行完全相同的操作。