Reverse Second Sort Characteristic In Python Sorted()
Solution 1:
Put a minus sign in front of the second sort characteristic:
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)
Solution 2:
You can do two sorts, least significant first. Python sorting is stable, so the order determined by the first sort will hold when you do the second.
for c insorted(sorted(l, key = lambda t:math.sqrt((0-t[1][0])**2 + (0-t[1][1])**2)), key = lambda t:t[0], reverse=True):
This method works even when the keys aren't numeric.
Solution 3:
You already have several good answers, but I thought you might appreciate another one. ;)
The answer by Paul Cornelius shows the simple way to do this: just negate one of the numbers produced by your key function. However, as Mark Ransom mentioned, you can only do that with numeric values, but fortunately Python's TimSort is stable. So you can sort a list several times on multiple criteria and each subsequent sort won't disturb the items that are equal according to the current sort key function. It's a little less efficient to sort in multiple passes, so it's better to use Paul's technique when you can. OTOH, TimSort is very efficient at processing partially sorted lists, so when doing multi-pass sorts the additional passes will usually run fairly quickly.
You can create your l
list a little more efficiently by using a list comprehension. But even if you don't want to use a list comp it would be better to loop directly over the values of ps
rather than using a range
- it's more efficient and also more general, since it works if the keys aren't a contiguous range. The values may not be in the numeric order of the keys (although they will be in Python 3.6+), but that doesn't matter here since we're going to sort l
anyway.
Here's a demo:
importmathps= {
1:(1, 1), 2:(3, 2), 3:(3, -3), 4:(-3, 4),
5:(-2, -2), 6:(3, 3), 7:(1, -1),
}
l= []
fortinps.values():l.append((math.atan2(t[1],t[0]),t))for t in l:print(t)
output
(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))
Using a list comp we can build l
in one line instead of three lines:
l = [(math.atan2(t[1], t[0]), t) for t in ps.values()]
We can shorten that slightly by using extended slicing to reverse t
and the *
"splat" operator to unpack the reversed t
:
l = [(math.atan2(*t[::-1]), t) for t in ps.values()]
Your expression
math.sqrt((0-t[1][0])**2 + (0-t[1][1])**2)
is overly verbose. (0-x)**2
equals x**2
, so we can rewrite that expression as
math.sqrt(t[1][0]**2 + t[1][1]**2)
However, there's an even better way. The math
module has a hypot
function, which MSeifert has used. hypot(x, y)
calculates the length of the hypotenuse of a right triangle with sides x
and y
, so your expression could be written as
math.hypot(t[1][0], t[1][1])
or using tuple unpacking,
math.hypot(*t[1])
Putting it all together:
importmathps= {
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)fortinps.values()]
l.sort(key=lambdat:(-t[0],math.hypot(*t[1])))for t in l:print(t)
output
(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))
If you only want to sort the tuples and don't need to keep the angles, you can do it like this:
l = sorted(ps.values(), key=lambda t: (-math.atan2(*t[::-1]), math.hypot(*t)))
print(l)
output
[(-3, 4), (1, 1), (3, 3), (3, 2), (1, -1), (3, -3), (-2, -2)]
Solution 4:
If your key
-function get's longish and/or complicated you can also use a custom class that compares like you want. It's a bit slower but it can be more readable, especially because you can comment the code:
import math
classAngle_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:
returnTrueelif self.angle == other.angle: # break tiesreturn self.distance > other.distance
else:
returnFalse
And this can be applied on the list:
>>>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)]
But you could also use it as key
-function:
>>>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)]
Post a Comment for "Reverse Second Sort Characteristic In Python Sorted()"