Ruby: Сортировка многомерного массива, который может содержать nil, иногда терпит неудачу

Я нашел этот пример в неудачном тесте в одном из моих проектов. Почему это работает:

[[1,2,3], [2,3,4], [1,1,nil]].sort
#=> [[1, 1, nil], [1, 2, 3], [2, 3, 4]]

Но это не так:

[[1,2,3], [nil,3,4], [1,1,nil]].sort
#=> ERROR: ArgumentError: comparison of Array with Array failed

Протестированные версии Ruby: 2.0.0, 1.9.3.

Ответ 1

Он не работает, потому что он перебирает nil. Причина, по которой первый тестовый пример не сбой, заключается в том, что сравнение 1, 1 происходит с 1, 2. Не проверять nil, поскольку это не нужно.

Ниже приведен пример ниже, потому что он должен сначала пройти nil. Попробуйте в irb:

[1, nil, 2].sort
ArgumentError: comparison of Fixnum with nil failed

# in this example 1,2 comes before 2,1 so no need to continue in sort
# and nil is never reached
[ [2,1,3,5], [1,2,nil,6] ].sort
 => [[1, 2, nil, 6], [2, 1, 3, 5]]

Ответ 2

Это из-за сравнения между массивами для их сортировки, это элемент для элемента:

В первом случае [[1,2,3], [2,3,4], [1,1,nil]].sort алгоритм сравнивает:

1 (of the first array) < 2 (of the second array)
1 (of the first array) = 1 (of the third array)

Тогда, поскольку существуют два элемента, мы должны сравнить второй элемент:

2 (of the first array) > 1 (of the third array)

Затем сравнение заканчивается, нет необходимости сравнивать 3-й элемент.

Теперь, во втором случае [[1,2,3], [nil,3,4], [1,1,nil]].sort, сравнение первого элемента массивов содержит nil, и это вызовет ошибку.

Например, это вызовет ошибку:

[[1,2,3], [1,nil,4], [3,1,3]].sort

Это не будет:

[[2,2,3], [1,nil,4], [3,1,3]].sort

Ответ 3

Это связано с тем, что метод #sort использует методы сортировки QSort, и в следующих случаях процедура сортировки достигает не последнего значения массива. В вашем случае [1,1,nil] это nil. И он достигает nil в случае 1,nil,4, потому что в последнем случае метод #<=> возвращает nil. Чтобы избежать ошибки, вам нужно переопределить метод Array #<=> или использовать сложный блок:

[[1,2,3], [nil,3,4], [1,1,nil]].sort do| x, y |
   x <=> y || 1
end