reverse second sort characteristic in python sorted()

前端 未结 4 558
闹比i
闹比i 2021-01-21 03:15

I\'m trying to sort a list and I can\'t figure out to reverse the order of a second sorting characteristic.

import math
ps = {1:(1,1),2:(3,2),3:(3,-3),4:(-3,4),         


        
相关标签:
4条回答
  • 2021-01-21 03:54

    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:

    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)
    

    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:

    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)
    

    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)]
    
    0 讨论(0)
  • 2021-01-21 04:00

    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
    
    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
    

    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)]
    
    0 讨论(0)
  • 2021-01-21 04:10

    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)
    
    0 讨论(0)
  • 2021-01-21 04:13

    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 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):
    

    This method works even when the keys aren't numeric.

    0 讨论(0)
提交回复
热议问题