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

5

我已经实现了一个排序算法,用于自定义字符串的跑道和田赛事件的时间或距离数据。以下是格式:

'10:03.00 - 十分钟零三秒或者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个回答

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 == b,b == c意味着a ==c,而对于你的代码来说并非如此:例如a =“'10:00.1” ,b =“frog”,c ='9:99:9“)。
“#sort_by”按升序排序,因此调用“#reverse”将使其变为降序。 “#sort_by”还具有仅对比较值进行一次解析的优点,而你的算法将不得不为每个比较解析每行。

谢谢你的帮助。我需要添加一些验证来确保时间/距离字段的格式正确。我需要阅读有关.map方法的资料,以了解它的真正作用。 - Chris Williams
#map 接受一个可枚举对象(在本例中为数组),并对每个元素运行给定的块,返回结果数组(顺序相同,在本例中)。 - rampion

1

与其像这样实现排序,也许你应该有一个TrackTime和FieldDistance模型。它们不一定需要被持久化 - 当加载Participant模型时,可以从time_distance创建它们。

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

我还会将时间和距离分开成两个独立的字段。在我的经验中,数据库中具有双重用途的列只会在后续操作中带来麻烦。


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;
}

你也可以使用Craig的建议将majorA和minorA合并为一个值,并仅调用一次compareIntegers。此外,如果“获取第一个冒号前部分”的部分在没有冒号时返回0,则可以避免“if(!empty)”保护。 - Jon Hess

0

你的程序看起来很好,但是你可以删除''', ':'和'.',并将结果视为数字字符串。换句话说,10' 5"将变成1005,10' 4"将变成1004。1005显然比1004大。

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


我喜欢将数值相乘的想法,而不仅仅是删除逗号和句点,因为它可以处理常见的错误。如果没有乘法运算,10'04" = 1004,10'4" = 104。 - RichH
1
是的,但在这个顺序中,“10'” <=> “9'” == -1,因此它认为“9'”是更高的值,并将首先对其进行排序。 - rampion

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]始终是两位数字。


只需要 *1000,但它做了我说的同样的事情,所以它一定是一个好答案! :) - Craig
需要在10000和100之间有两个数量级,因为你可能需要达到59秒或11英寸。伟大的思想总是相似的(我只是打字速度比你慢几秒!:))。 - RichH
1
是的,这个想法不好,因为这些值仍然都是字符串。它们还没有转换成整数,因此将它们乘以任何东西只会复制字符串。 - rampion
rampion:很好的发现,我已经添加了.to_i。 - RichH

0

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

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

可以简化为a<=>b。要得到相反的结果,请使用-(a <=> b)


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 <=> totalDistanceB
请注意,您应该保留您已经进行的验证,检查a_parts和b_parts是否为空:

a_parts.empty? || b_parts.empty?

对于按时间排序的场景,执行完全相同的操作,只是使用不同的因素(例如,60秒为一分钟)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接