47

sorted([2, float('nan'), 1])返回[2, nan, 1]

(至少在 Activestate Python 3.1 实现上。)

我知道nan这是一个奇怪的对象,所以如果它出现在排序结果中的随机位置,我不会感到惊讶。但它也弄乱了容器中非 nan 数字的排序,这确实是出乎意料的。

我问了一个关于的相关问题max,并基于此我理解为什么会这样sort工作。但这应该被认为是一个错误吗?

文档只是说“返回一个新的排序列表[...]”而没有指定任何细节。

编辑:我现在同意这不违反 IEEE 标准。但是,我认为,从任何常识的角度来看,这都是一个错误。即使是不经常承认错误的微软,也已经认识到这是一个错误,并在最新版本中修复了它: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)

我怀疑与默认情况下这样做的语言相比,它会导致性能下降,但至少它可以工作(除非我引入了任何错误)。

4

8 回答 8

17

以前的答案很有用,但可能不清楚问题的根源。

在任何语言中,排序都会在输入值的域上应用由比较函数或其他方式定义的给定排序。例如,operator <,当且仅当小于定义了对输入值的合适排序时,可以始终使用小于,aka。

但这对于浮点值和小于特别不正确:“NaN 是无序的:它不等于、大于或小于任何值,包括它自己。” (来自 GNU C 手册的清晰散文,但适用于所有基于现代IEEE754浮点

所以可能的解决方案是:

  1. 首先删除 NaN,使输入域通过 < (或正在使用的其他排序函数)明确定义
  2. 定义一个自定义比较函数(又名谓词),它确实定义了 NaN 的排序,例如小于任何数字或大于任何数字。

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

实际上,考虑到 python,如果您不太关心最快的性能,或者如果删除 NaN 是上下文中所需的行为,我更愿意删除 NaN。

否则,您可以通过旧 python 版本中的“cmp”或通过 this 和 functools.cmp_to_key(). 自然,后者比先删除 NaN 更尴尬。在定义此谓词函数时,需要注意避免性能下降。

于 2011-08-23T17:35:20.177 回答
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或者在排序或其他任何事情之前删除s 。

于 2010-11-21T20:12:55.073 回答
8

list问题是如果包含 a则没有正确的顺序NAN,因为如果 是对序列a1, a2, a3, ..., an进行排序a1 <= a2 <= a3 <= ... <= an。如果这些 a 值中的任何一个是 a ,NAN那么排序的属性就会中断,因为所有a, a <= NAN and NAN <= a的都是false

于 2010-11-21T21:46:04.983 回答
7

假设您想保留 NaN 并将它们排序为最低的“值”,这里有一个解决方法,适用于非唯一 nan唯一 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']
于 2017-05-24T09:25:24.320 回答
5

IEEE754 是在这种情况下定义浮点运算的标准。该标准将操作数(其中至少有一个是 NaN)的比较操作定义为错误。因此,这不是错误。在对阵列进行操作之前,您需要处理 NaN。

于 2010-11-21T20:10:28.090 回答
2

回顾一下问题:

NaN

这总是False为每次比较返回,所以它保持在列表中的位置:

>>> sorted([float('nan'), 0])
[nan, 0]
>>> sorted([0, float('nan')])
[0, nan]

-0.0

这是 == to 0.0,但具有不同的 repr、不同的 json 表示形式和略有不同的数值属性。同样的问题是正零和负零将保持与原始列表中相同的顺序:

>>> sorted([0.0, -0.0])
[0.0, -0.0]
>>> sorted([-0.0, 0.0])
[-0.0, 0.0]

其他解决方案?

@khachik 的解决方案对NaN和的排序行为不一致-inf

>>> key=lambda x: float('-inf') if math.isnan(x) else x
>>> sorted([float('nan'), float('-inf')], key=key)
[nan, -inf]
>>> sorted([float('-inf'), float('nan')], key=key)
[-inf, nan]

解决方案:更复杂的按键功能。

因此,符号和 nans 存在问题。我们可以将它们包含在一个关键函数中:

def stable_float_sort_key(x: float):
    return math.copysign(1, x), math.isnan(x), x

这适用于上面的所有示例:

>>> sorted([float('nan'), 0.0], key=stable_float_sort_key)
[0.0, nan]
>>> sorted([0.0, float('nan')], key=stable_float_sort_key)
[0.0, nan]
>>> sorted([float('nan'), float('-inf')], key=stable_float_sort_key)
[-inf, nan]
>>> sorted([float('-inf'), float('nan')], key=stable_float_sort_key)
[-inf, nan]
>>> sorted([0.0, -0.0], key=stable_float_sort_key)
[-0.0, 0.0]
>>> sorted([-0.0, 0.0], key=stable_float_sort_key)
[-0.0, 0.0]

实际上,您可以编写一个假设检验,表明它在所有浮点数中都是一致的:

import json
from hypothesis import given, settings
from hypothesis import strategies as st

@given(nums=st.lists(st.floats()), random=st.randoms())
@settings(max_examples=10000)
def test_stable_json_sorting(nums, random):
    shuffled = list(nums)
    random.shuffle(shuffled)
    l1 = sorted(nums, key=stable_float_sort_key)
    l2 = sorted(shuffled, key=stable_float_sort_key)
    assert json.dumps(l1) == json.dumps(l2)

然而,它确实有一些奇怪的地方,因为一些 NaN 是负数!例如:

>>> sorted([float('nan'), -0.0, 0.0, float('-nan')], key=stable_float_sort_key)
[-0.0, nan, 0.0, nan]

如果这让您感到困扰,您可以通过切换顺序来解决此问题:

def stable_float_sort_key(x: float):
    return math.isnan(x), math.copysign(1, x), x

这首先对负数进行排序,然后是正数,然后是 NaN。

这有什么意义吗?

当然,其他回答者是正确的,从某种意义上说,这一切都没有意义。NaN 的比较是某种概念上的错误。但是,即使在问题没有“意义”的情况下,您也可能需要不变量,例如将由相同代码生成的浮点数集序列化为完全相同的 JSON 表示,尽管哈希随机化(我的用例)。这更像是 python 代码的正式属性,而不是根据 IEEE 标准有“正确答案”的东西。

于 2021-11-18T08:09:06.917 回答
0

无论标准如何,在许多情况下,用户定义的浮点数和NA值的排序都是有用的。例如,我正在对股票收益进行排序,并希望从高到低NA最后(因为这些无关紧要)。有4种可能的组合

  1. 升序浮点值,NA值最后
  2. 升序浮点值,NA值优先
  3. 降序浮点值,NA最后一个值
  4. 降序浮点值,NA值优先

NA这是一个通过有条件地替换值来覆盖所有场景的函数+/- inf

import math 

def sort_with_na(x, reverse=False, na_last=True):
    """Intelligently sort iterable with NA values

    For reliable behavior with NA values, we should change the NAs to +/- inf
    to guarantee their order rather than relying on the built-in
    ``sorted(reverse=True)`` which will have no effect. To use the ``reverse``
    parameter or other kwargs, use functools.partial in your lambda i.e.

        sorted(iterable, key=partial(sort_with_na, reverse=True, na_last=False))

    :param x: Element to be sorted
    :param bool na_last: Whether NA values should come last or first
    :param bool reverse: Return ascending if ``False`` else descending
    :return bool:
    """
    if not math.isnan(x):
        return -x if reverse else x
    else:
        return float('inf') if na_last else float('-inf')

测试 4 种组合中的每一种

from functools import partial

a = [2, float('nan'), 1]
sorted(a, key=sort_with_na)                                         # Default
sorted(a, key=partial(sort_with_na, reverse=False, na_last=True))   # Ascend, NA last
sorted(a, key=partial(sort_with_na, reverse=False, na_last=False))  # Ascend, NA first
sorted(a, key=partial(sort_with_na, reverse=True, na_last=True))    # Descend, NA last
sorted(a, key=partial(sort_with_na, reverse=True, na_last=False))   # Descend, NA first
于 2020-11-21T16:09:45.373 回答
0

弹性排序涉及比较 2 个项目并返回:更少、相等、更大。

如果cmp(a,b)是“更大”,那么cmp(b,a)一定是“更少”。

如果cmp(a,b)为“零”,则cmp(b,a)必须为“零”。

迄今为止的答案中缺少的是比较 2float的情况,它们都是NAN并保留上述属性。2 NAN 应该比较相等,或者可能基于对其有效载荷的某种一致解释。

替代比较算法将所有 NAN > +inf

if isnan(a)
  if isnan(b)
    return 0 (or maybe compare payloads/bit patterns)
  return 1
if isnan(b) return 1
if a > b return 1
if a < b return -1
return 0
于 2021-03-19T10:49:16.113 回答