Ruby数组排序而不改变nils的位置

时间:2016-04-27 06:58:24

标签: arrays ruby sorting

我正在使用一组数组。我需要对行进行排序,排序条件是从左到右连续的每列(从第2列开始)的值。必须将零值推到底部。一旦被推到底部,我需要保留nil列,因为它们没有连续搜索改变它们相对于彼此的位置。从这个数组开始:

sort_array = 
[[297, 100, 101, 235, 253, nil, nil, nil],
 [286, 116, 116, 213, nil, nil, nil, nil],
 [256, 105, 111, 212, 216, 264, nil, nil],
 [276, 108, 111, 204, 207, 257, 259, 367],
 [274,  66,  66, 120, 121, nil, 150, nil],
 [298, 114, 117, 270, 270, nil, nil, nil],
 [296, 127, 130, 259, 264, 324, 332, nil],
 [283, 102, 106, 193, 199, 247, 248, 343]]

我使用以下代码行迭代数组:

(1..sort_array[0].size - 1).each do |i|
  sort_array.sort_by! { |e| [e[i] ? 0 : 1, e[i]] }
end

这个想法是忽略第一列,但是按升序对第二列到第n列进行排序,将nil值推到底部。这给出了以下结果:

  => [[283, 102, 106, 193, 199, 247, 248, 343],
     [276, 108, 111, 204, 207, 257, 259, 367],
     [256, 105, 111, 212, 216, 264, nil, nil],
     [297, 100, 101, 235, 253, nil, nil, nil],
     [296, 127, 130, 259, 264, 324, 332, nil],
     [274,  66,  66, 120, 121, nil, 150, nil],
     [298, 114, 117, 270, 270, nil, nil, nil],
     [286, 116, 116, 213, nil, nil, nil, nil]]

这接近我的需要,但不是很正确。问题是,一旦推到底部的零值不会停留。例如,在对第一个值为247的列进行排序后,该数组将如下所示:

=> [[283, ... 247, ...]
    [276, ... 257, ...]
    [256, ... 264, ...]
    [296, ... 324, ...]
    [297, ... nil, ...]
    [274, ... nil, ...]
    [298, ... nil, ...]
    [286, ... nil, ...]

但是对右边的列进行进一步排序会导致nil值重新排序,这会使排序混乱。除非右边的单元格是非零的,否则需要保留零值,在这种情况下,它应该像往常一样进行排序。

排序完成后,表格应如下所示:

  => [[283, 102, 106, 193, 199, 247, 248, 343],
     [276, 108, 111, 204, 207, 257, 259, 367],
     [274,  66,  66, 120, 121, nil, 150, nil],
     [296, 127, 130, 259, 264, 324, 332, nil],
     [256, 105, 111, 212, 216, 264, nil, nil],
     [297, 100, 101, 235, 253, nil, nil, nil],
     [298, 114, 117, 270, 270, nil, nil, nil],
     [286, 116, 116, 213, nil, nil, nil, nil]]

有人可以帮我写一个能达到这个结果的方法吗?

看到我想要实现的实际例子可能会有所帮助。以下是电子表格的链接:

https://github.com/SplitTime/OpenSplitTime/blob/master/hardrock2015test.xlsx

每一行都是跑步者的努力。当然,跑步者按最终完成时间排名。但是那些没有完成比赛的人会根据他们取得的成就以及他们到达最后一个航路点的时间来排名。

虽然链接的电子表格没有显示,但排序还需要处理nil值和右边的时间数据,表示无论出于何种原因都没有记录的时间。

1 个答案:

答案 0 :(得分:2)

认为您希望按相反的顺序按行倒排第二行(将nil视为Infinity):

arrays = [[297, 100, 101, 235, 253, nil, nil, nil],
          [286, 116, 116, 213, nil, nil, nil, nil],
          [256, 105, 111, 212, 216, 264, nil, nil],
          [276, 108, 111, 204, 207, 257, 259, 367],
          [274,  66,  66, 120, 121, nil, 150, nil],
          [298, 114, 117, 270, 270, nil, nil, nil],
          [296, 127, 130, 259, 264, 324, 332, nil],
          [283, 102, 106, 193, 199, 247, 248, 343]]

arrays.sort_by { |a| a[1..-1].reverse.map { |e| e || Float::INFINITY } }
#=> [[283, 102, 106, 193, 199, 247, 248, 343],
#    [276, 108, 111, 204, 207, 257, 259, 367],
#    [274,  66,  66, 120, 121, nil, 150, nil],
#    [296, 127, 130, 259, 264, 324, 332, nil],
#    [256, 105, 111, 212, 216, 264, nil, nil],
#    [297, 100, 101, 235, 253, nil, nil, nil],
#    [298, 114, 117, 270, 270, nil, nil, nil],
#    [286, 116, 116, 213, nil, nil, nil, nil]]

至少,这会产生预期的结果。

由于第一行由键组成,您可以利用Ruby的array decomposition能力并使用|_, *vs| vs.reverse...而不是|a| a[1..-1].reverse...。在这里,_引用(未使用的)密钥,*vs收集剩余的元素。