提问人:OnDraganov 提问时间:8/18/2023 更新时间:8/19/2023 访问量:137
如何在 Python 3 中使用“isclose”比较而不是绝对相等 (==) 对带有浮点数的元组进行排序?
How to sort tuples with floats using "isclose" comparisons rather than absolute equality (==) in Python 3?
问:
我想主要按浮点数对对列表排序,其次按整数排序。问题在于比较浮点数是否相等——在许多情况下,我们得到的是任意比较,而不是求助于次阶。
例
my_list = [
(0, 1.2),
(2, 0.07076863960397783),
(1, 0.07076863960397785),
(4, 0.02)
]
我想主要按浮点值排序,其次按整数排序。
运行返回以下结果:sorted(my_list, key=lambda x: (x[1], x[0]))
[(4, 0.02), (2, 0.07076863960397783), (1, 0.07076863960397785), (0, 1.2)]
但是,由于两个浮点数非常接近,因此它们实际上应该被认为是相等的,因此所需的顺序是这样的:
[(4, 0.02), (1, 0.07076863960397785), (2, 0.07076863960397783), (0, 1.2)]
如何合并或进入排序?math.isclose
np.isclose
我的尝试
我目前的方法是将浮点数包装到一个类中,该类使用 numpy 的 .isclose
class FloatComparisonWrapper:
def __init__(self, num):
self.value = num
def __lt__(self, other):
return self.__ne__(other) and self.value < other.value
def __gt__(self, other):
return self.__ne__(other) and self.value > other.value
def __eq__(self, other):
return np.isclose(self.value, other.value)
def __ne__(self, other):
return not self.__eq__(other)
def __le__(self, other):
return self.__eq__(other) or self.value <= other.value
def __ge__(self, other):
return self.__eq__(other) or self.value >= other.value
然后给出正确的顺序。sorted(my_list, key=lambda x: (FloatComparisonWrapper(x[1]), x[0]))
这是一个好的解决方案吗?是pythonic吗?这是最好的解决方案吗?有没有更快的方法可以做到这一点?
答:
稍微多一点的 Pythonic 是利用functools.total_ordering
来减少以下内容:FloatComparisonWrapper
@functools.total_ordering
class FloatComparisonWrapper:
def __init__(self, num):
self.value = num
def __le__(self, other):
return self.__eq__(other) or self.value < other.value
def __eq__(self, other):
return np.isclose(self.value, other.value)
这个解决方案是好的还是最好的是一个见仁见智的问题,它是否足够快并不重要。
评论
__lt__
not __eq__(...)
__le__
这是一个好的解决方案吗?
不。
OP的方法在排序方面存在缺陷。
由于两个浮标非常接近,因此它们确实应该被认为是相等的
考虑是否所有值都几乎接近同一浮点值。有些货币对是“相等的”,有些则不是。 OP 的目标失败了,因为比较没有形成总订单。
从C的角度来看,这可能适用于Python,因为我认为这个问题与语言无关。
这不起作用,因为 、 必须形成一致的顺序。compare(a, b)
compare(b,c)
compare(a,c)
当相同的对象(由大小字节组成,无论它们在数组中的当前位置如何)多次传递给比较函数时,结果应彼此一致。也就是说,对于 qsort,它们将定义数组的总排序,...C1723 § 7.24.5 4.
考虑
(2, float_before(x)), // Lowest value that "is close" to x.
(1, x),
(0, float_after(x)), // Greatest value that "is close" to x.
即使 和 是连续的“相等”值,如 OP 所建议的,(以及 ,),排序将得出不同的(并且可能是不确定的:例如无限循环)结果,具体取决于比较的应用顺序,因为不相等。float_before(x)
x
x
float_after(x)
compare(float_before(x), float_after(x))
除非一阶值相等,否则不应应用二次比较。几乎相等是不够的,也可能是真的,但事实并非如此。a ≈ b
b ≈ c
a ≈ c
当 OP 几乎相等时,并且由于它们的主比较是相等的,但次要的比较使它们大于有序的,并暗示 .然而,由于不接近,并且主要排序是 。这与.a > b
b > c
a > c
a
c
a < c
a > c
有没有更快的方法可以做到这一点?
我建议放弃“两个浮点数非常接近,以至于它们真的应该被认为是相等的”方法进行主要比较。只需进行值比较即可。在尝试使其更快之前,请正确使用功能。isclose()
// Pseudo code
if (primary_compare_as_equal(a,b))
return secondary compare(a,b)
return primary_compare(a,b)
评论
您可以使用 functools 来实现与 math.isclose 的比较:cmp_to_key
from functools import cmp_to_key
from math import isclose
my_list.sort(key=cmp_to_key(lambda a,b: not isclose(a[1],b[1]) and a[1]-b[1]
or a[0]-b[0]))
print(my_list)
[(4, 0.02), (1, 0.07076863960397785), (2, 0.07076863960397783), (0, 1.2)]
如果你要进行比较类,我建议基于它并尽量减少间接(函数调用级别)的数量:float
from math import isclose
class FloatCompare(float):
def __lt__(self,other):
return float.__lt__(self,other) and not isclose(self,other)
my_list.sort(key=lambda x:(FloatCompare(x[1]),x[0]))
print(my_list)
[(4, 0.02), (1, 0.07076863960397785), (2, 0.07076863960397783), (0, 1.2)]
请注意,您可以定义所有比较方法,但 sort() 实际上只使用 __lt__
(list.sort)
评论
cmp_to_key
(a[0]>b[0]) - (a[0]<b[0])
评论
sorted(my_list, key=lambda t: (round(t[1],6),t[0]))
round(0.123456789123, 6)
0.123457000001
round
sorted(my_list, key=lambda t: (int(t[1]*1e6),t[0]))
isclose
round
isclose