Python: 排序函数在存在nan时失效
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 个回答
这个问题在于,如果列表里有一个叫做 NAN
的东西,就没有正确的排序顺序。因为一个序列 a1, a2, a3, ..., an
只有在 a1 <= a2 <= a3 <= ... <= an
的情况下才算是排好序的。如果这些值中有一个是 NAN
,那么排序的规则就不成立了,因为对于所有的 a
来说,a <= NAN
和 NAN <= a
这两个条件都是不成立的。
我不太确定这个问题是什么,但可以试试下面这个解决方法:
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
的值去掉。
之前的回答很有用,但可能没有清楚地解释问题的根源。
在任何编程语言中,排序是根据某种比较规则对输入的值进行排列的。比如说,使用小于号(operator <
)来比较,只有当小于号能合理地对输入值进行排序时,才能使用。
但是,对于浮点数和小于号来说,这个规则并不适用:“NaN(不是一个数字)是无序的:它既不等于任何东西,也不大于或小于任何东西,包括它自己。”(这句话来自GNU C手册,但适用于所有现代的IEEE754
标准的浮点数)
所以,可能的解决方案有:
- 先去掉NaN,这样输入的值就能通过小于号(<)或其他排序函数来明确排序。
- 定义一个自定义的比较函数(也叫谓词),为NaN定义一个排序规则,比如说小于任何数字,或者大于任何数字。
这两种方法在任何语言中都可以使用。
具体到Python,如果你不太在意性能,或者在特定情况下去掉NaN是你想要的效果,我会更倾向于先去掉NaN。
否则,你可以在旧版本的Python中通过“cmp”使用合适的谓词函数,或者通过这个和functools.cmp_to_key()
来实现。后者自然比先去掉NaN要麻烦一些。而且在定义这个谓词函数时,要小心避免性能变得更差。