Подтвердить что ты не робот

Python: функция сортировки прерывается при наличии нан

sorted([2, float('nan'), 1]) возвращает [2, nan, 1]

(по крайней мере, для реализации Activestate Python 3.1.)

Я понимаю, что nan - это странный объект, поэтому я не удивлюсь, если он появится в случайных местах в результате сортировки. Но это также испортило сортировку для не-наном чисел в контейнере, что действительно неожиданно.

Я спросил вопрос о max, и на основании этого я понимаю, почему sort работает так. Но следует ли это считать ошибкой?

Документация просто говорит: "Верните новый отсортированный список [...]" без указания каких-либо деталей.

EDIT: Теперь я согласен, что это не нарушает стандарт IEEE. Тем не менее, я думаю, это ошибка с любой точки зрения здравого смысла. Даже Microsoft, которая нередко признает свои ошибки часто, признала это ошибкой и исправила ее в последней версии: http://connect.microsoft.com/VisualStudio/feedback/details/363379/bug-in-list-double-sort-in-list-which-contains-double-nan.

В любом случае, я получил ответ @khachik:

sorted(list_, key = lambda x : float('-inf') if math.isnan(x) else x)

Я подозреваю, что это приводит к хиту производительности по сравнению с языком, который делает это по умолчанию, но по крайней мере он работает (запрет любых ошибок, которые я представил).

4b9b3361

Ответ 1

Предыдущие ответы полезны, но, возможно, неясны в отношении корня проблемы.

В любом языке сортировка применяет заданное упорядочение, определенное функцией сравнения или каким-либо другим способом, над доменом входных значений. Например, менее чем, a.k.a. operator <, можно использовать всюду в том и только том случае, если меньше, чем подходящий порядок над входными значениями.

Но это специально НЕ верно для значений с плавающей запятой и меньше: "NaN неупорядочен: он не равен, больше или меньше всего, включая самого себя". (Очистить прозу из руководства GNU C,, но применимо ко всем современным плавающей точкой IEEE754)

Таким образом, возможные решения:

  • сначала удалите NaN, сделав корректно определенный входной домен через < (или используемая другая функция сортировки)
  • определить пользовательскую функцию сравнения (предикат a.k.a.), которая делает определить порядок для NaN, например, меньше любого числа или больше чем любое число.

Любой подход может использоваться на любом языке.

Практически, учитывая python, я бы предпочел удалить NaNs, если вам не все равно, о максимальной производительности, или если удаление NaN - это желаемое поведение в контексте.

В противном случае вы можете использовать подходящую предикатную функцию через "cmp" в более старых версиях python или через это и functools.cmp_to_key(). Последнее, конечно, немного неудобно, чем удаление NaNs в первую очередь. При определении этой функции предикатов потребуется осторожность, чтобы избежать ухудшения производительности.

Ответ 2

Проблема заключается в том, что нет правильного порядка, если список содержит NAN, поскольку последовательность a1, a2, a3,..., an сортируется, если a1 <= a2 <= a3 < =... <= an. Если какое-либо из этих значений является NAN, тогда сортированное свойство ломается, так как для всех a, a <= NAN и NAN <= a оба являются ложными.

Ответ 3

Я не уверен в ошибке, но обходной путь может быть следующим:

sorted(
    (2, 1, float('nan')),
    lambda x,y: x is float('nan') and -1 
                or (y is float('nan') and 1
                or cmp(x,y)))

что приводит к:

('nan', 1, 2)

Или удалите nan перед сортировкой или чем-нибудь еще.

Ответ 4

IEEE754 - это стандарт, который определяет операции с плавающей запятой в этом экземпляре. Этот стандарт определяет операцию сравнения операндов, по крайней мере один из которых представляет собой NaN, как ошибку. Следовательно, это не ошибка. Вам нужно иметь дело с NaN, прежде чем работать с массивом.

Ответ 5

Предполагая, что вы хотите сохранить NaNs и упорядочить их как самые низкие "значения", это обходное решение, работающее как с уникальными нано, уникальными numpy nan, численными и не численными объектами:

def is_nan(x):
    return (x is np.nan or x != x)

list_ = [2, float('nan'), 'z', 1, 'a', np.nan, 4, float('nan')]
sorted(list_, key = lambda x : float('-inf') if is_nan(x) else x)
# [nan, nan, nan, 1, 2, 4, 'a', 'z']