反向 python sorted() 中的第二个排序特征



我正在尝试对列表进行排序,但无法弄清楚如何颠倒第二个排序特征的顺序。

import math
ps = {1:(1,1),2:(3,2),3:(3,-3),4:(-3,4),5:(-2,-2),6:(3,3),7:(1,-1)} 
l = []
for x in range(1,8):
l.append((math.atan2(ps[x][1],ps[x][0]),ps[x]))
for c in sorted(l, key = lambda t:(t[0], math.sqrt((0-t[1][0])**2 + (0-t[1][1])**2)),reverse = True):
print(c)

第一个排序特征是按角度排序,如果角度相等,则第二个排序特征是按距原点的距离排序。有谁知道如何做到这一点。提前感谢您的帮助。

在第二个排序特征前面放一个减号:

for c in sorted(l, key = lambda t:(t[0], -math.sqrt((0-t[1][0])**2 + (0-t[1][1])**2)),reverse = True):
print(c)

您可以执行两种排序,首先最不重要。Python 排序是稳定的,因此当您执行第二次排序时,由第一个排序确定的顺序将保持不变。

for c in sorted(sorted(l, key = lambda t:math.sqrt((0-t[1][0])**2 + (0-t[1][1])**2)), key = lambda t:t[0], reverse=True):

即使键不是数字,此方法也有效。

你已经有几个很好的答案,但我想你可能会喜欢另一个。 ;)

保罗·科尼利厄斯(Paul Cornelius)的答案显示了做到这一点的简单方法:只需否定关键函数产生的一个数字。然而,正如Mark Ransom所提到的,你只能用数值来做到这一点,但幸运的是,Python的TimSort是稳定的。因此,您可以根据多个条件对列表进行多次排序,并且每次后续排序都不会干扰根据当前排序键功能相等的项目。在多个传递中排序的效率会低一些,因此最好尽可能使用 Paul 的技术。OTOH,TimSort在处理部分排序的列表方面非常有效,因此在进行多通道排序时,额外的传递通常会运行得相当快。

您可以使用列表推导式更有效地创建l列表。但是,即使您不想使用列表组合,最好直接遍历ps的值,而不是使用range- 它更有效,也更通用,因为如果键不是连续范围,它就可以工作。这些值可能不是键的数字顺序(尽管它们将在 Python 3.6+ 中),但这在这里无关紧要,因为我们无论如何都要对l进行排序。

下面是一个演示:

import math
ps = {
1: (1, 1), 2: (3, 2), 3: (3, -3), 4: (-3, 4), 
5: (-2, -2), 6: (3, 3), 7: (1, -1),
}
l = []
for t in ps.values():
l.append((math.atan2(t[1], t[0]), t))
for t in l:
print(t)

输出

(0.7853981633974483, (1, 1))
(0.5880026035475675, (3, 2))
(-0.7853981633974483, (3, -3))
(2.214297435588181, (-3, 4))
(-2.356194490192345, (-2, -2))
(0.7853981633974483, (3, 3))
(-0.7853981633974483, (1, -1))

使用列表合成,我们可以在一行而不是三行中构建l

l = [(math.atan2(t[1], t[0]), t) for t in ps.values()]

我们可以通过使用扩展切片来反转t,并使用*"splat"运算符来解压缩反转t

l = [(math.atan2(*t[::-1]), t) for t in ps.values()]

你的表达

math.sqrt((0-t[1][0])**2 + (0-t[1][1])**2)

过于冗长。(0-x)**2等于x**2,所以我们可以将该表达式重写为

math.sqrt(t[1][0]**2 + t[1][1]**2)

但是,还有更好的方法。math模块具有 MSeifert 使用的hypot函数。hypot(x, y)计算边xy的直角三角形斜边的长度,所以你的表达式可以写成

math.hypot(t[1][0], t[1][1])

或使用元组解包,

math.hypot(*t[1])

把所有的东西放在一起:

import math
ps = {
1: (1, 1), 2: (3, 2), 3: (3, -3), 4: (-3, 4), 
5: (-2, -2), 6: (3, 3), 7: (1, -1),
}
l = [(math.atan2(*t[::-1]), t) for t in ps.values()]
l.sort(key=lambda t: (-t[0], math.hypot(*t[1])))
for t in l:
print(t)

输出

(2.214297435588181, (-3, 4))
(0.7853981633974483, (1, 1))
(0.7853981633974483, (3, 3))
(0.5880026035475675, (3, 2))
(-0.7853981633974483, (1, -1))
(-0.7853981633974483, (3, -3))
(-2.356194490192345, (-2, -2))

如果你只想对元组进行排序,而不需要保留角度,你可以这样做:

l = sorted(ps.values(), key=lambda t: (-math.atan2(*t[::-1]), math.hypot(*t)))
print(l)

输出

[(-3, 4), (1, 1), (3, 3), (3, 2), (1, -1), (3, -3), (-2, -2)]

如果你的key函数变得冗长和/或复杂,你也可以使用一个自定义类来比较你想要的。它有点慢,但可以更具可读性,特别是因为您可以注释代码:

import math
class Angle_Distance(object):
def __init__(self, x, y):
self.x = x
self.y = y
self.angle = math.atan2(self.y, self.x)
self.distance = math.hypot(self.x, self.y)
def __repr__(self):
return '{self.__class__.__name__}(x={self.x}, y={self.y})'.format(self=self)
def __eq__(self, other):
# this method is not strictly necessary, but makes the class more generally useful.
return self.x == other.x and self.y == other.y
def __lt__(self, other):
if self.angle < other.angle:
return True
elif self.angle == other.angle:  # break ties
return self.distance > other.distance
else:
return False

这可以应用于列表:

>>> ps = {1:(1,1),2:(3,2),3:(3,-3),4:(-3,4),5:(-2,-2),6:(3,3),7:(1,-1)} 
>>> l = [Angle_Distance(i, j) for i, j in ps.values()]
>>> sorted(l, reverse=True)
[Angle_Distance(x=-3, y=4),
Angle_Distance(x=1, y=1),
Angle_Distance(x=3, y=3),
Angle_Distance(x=3, y=2),
Angle_Distance(x=1, y=-1),
Angle_Distance(x=3, y=-3),
Angle_Distance(x=-2, y=-2)]

但您也可以将其用作key函数:

>>> ps = {1:(1,1),2:(3,2),3:(3,-3),4:(-3,4),5:(-2,-2),6:(3,3),7:(1,-1)}
>>> sorted(ps.values(), key=lambda x: Angle_Distance(x[0], x[1]), reverse=True)
[(-3, 4), (1, 1), (3, 3), (3, 2), (1, -1), (3, -3), (-2, -2)]

相关内容

最新更新