Python: 排序函数在存在nan时失效

53 投票
8 回答
16176 浏览
提问于 2025-04-16 07:25

sorted([2, float('nan'), 1]) 这个代码的结果是 [2, nan, 1]

(至少在 Activestate 的 Python 3.1 版本中是这样。)

我知道 nan 是个奇怪的东西,所以如果它在排序结果中出现一些随机的位置,我也不会感到惊讶。但它还搞乱了容器中非 nan 数字的排序,这就真的让人意外了。

我问过一个 相关的问题,关于 max 函数,基于那个问题我明白了为什么 sort 会这样工作。但这应该算是个bug吗?

文档里只说“返回一个新的排序列表 [...]”,没有具体说明任何细节。

编辑:我现在同意这并不违反 IEEE 标准。不过,从常识来看,我觉得这算是个bug。即使是微软,虽然不常承认错误,也承认了这个问题是个bug,并在最新版本中修复了它: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)

我怀疑这样做的性能会比语言默认处理要差,但至少它能工作(前提是我没有引入其他bug)。

8 个回答

8

这个问题在于,如果列表里有一个叫做 NAN 的东西,就没有正确的排序顺序。因为一个序列 a1, a2, a3, ..., an 只有在 a1 <= a2 <= a3 <= ... <= an 的情况下才算是排好序的。如果这些值中有一个是 NAN,那么排序的规则就不成立了,因为对于所有的 a 来说,a <= NANNAN <= a 这两个条件都是不成立的。

10

我不太确定这个问题是什么,但可以试试下面这个解决方法:

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 的值去掉。

18

之前的回答很有用,但可能没有清楚地解释问题的根源。

在任何编程语言中,排序是根据某种比较规则对输入的值进行排列的。比如说,使用小于号(operator <)来比较,只有当小于号能合理地对输入值进行排序时,才能使用。

但是,对于浮点数和小于号来说,这个规则并不适用:“NaN(不是一个数字)是无序的:它既不等于任何东西,也不大于或小于任何东西,包括它自己。”(这句话来自GNU C手册,但适用于所有现代的IEEE754标准的浮点数)

所以,可能的解决方案有:

  1. 先去掉NaN,这样输入的值就能通过小于号(<)或其他排序函数来明确排序。
  2. 定义一个自定义的比较函数(也叫谓词),为NaN定义一个排序规则,比如说小于任何数字,或者大于任何数字。

这两种方法在任何语言中都可以使用。

具体到Python,如果你不太在意性能,或者在特定情况下去掉NaN是你想要的效果,我会更倾向于先去掉NaN。

否则,你可以在旧版本的Python中通过“cmp”使用合适的谓词函数,或者通过这个和functools.cmp_to_key()来实现。后者自然比先去掉NaN要麻烦一些。而且在定义这个谓词函数时,要小心避免性能变得更差。

撰写回答