在 python sorted() 中反转第二个排序特征
reverse second sort characteristic in 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):
即使键不是数字,此方法也适用。
如果您的 key
-函数变得过长 and/or 复杂,您也可以使用自定义 class 进行比较。它有点慢,但它可以更具可读性,特别是因为你可以评论代码:
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)]
您已经有几个很好的答案,但我想您可能会喜欢另一个答案。 ;)
Paul Cornelius 的回答显示了执行此操作的简单方法:只需取反键函数生成的数字之一。然而,正如 Mark Ransom 提到的,你只能用数值来做到这一点,但幸运的是 Python 的 TimSort 是稳定的。因此,您可以根据多个条件对列表进行多次排序,并且每次后续排序都不会干扰根据当前排序键函数相等的项目。多次传球排序效率稍低,所以最好尽可能使用保罗的技术。 OTOH,TimSort 在处理部分排序的列表时非常有效,因此在进行多遍排序时,额外的遍数通常 运行 相当快。
您可以使用列表理解更有效地创建 l
列表。但是,即使您不想使用 list comp,最好直接遍历 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
模块有一个 hypot
函数,MSeifert 使用过。 hypot(x, y)
计算边长 x
和 y
的直角三角形的斜边长度,因此您的表达式可以写成
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)]
我正在尝试对列表进行排序,但我无法弄清楚如何反转第二个排序特征的顺序。
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):
即使键不是数字,此方法也适用。
如果您的 key
-函数变得过长 and/or 复杂,您也可以使用自定义 class 进行比较。它有点慢,但它可以更具可读性,特别是因为你可以评论代码:
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)]
您已经有几个很好的答案,但我想您可能会喜欢另一个答案。 ;)
Paul Cornelius 的回答显示了执行此操作的简单方法:只需取反键函数生成的数字之一。然而,正如 Mark Ransom 提到的,你只能用数值来做到这一点,但幸运的是 Python 的 TimSort 是稳定的。因此,您可以根据多个条件对列表进行多次排序,并且每次后续排序都不会干扰根据当前排序键函数相等的项目。多次传球排序效率稍低,所以最好尽可能使用保罗的技术。 OTOH,TimSort 在处理部分排序的列表时非常有效,因此在进行多遍排序时,额外的遍数通常 运行 相当快。
您可以使用列表理解更有效地创建 l
列表。但是,即使您不想使用 list comp,最好直接遍历 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
模块有一个 hypot
函数,MSeifert 使用过。 hypot(x, y)
计算边长 x
和 y
的直角三角形的斜边长度,因此您的表达式可以写成
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)]