reverse second sort characteristic in python sorted()

前端 未结 4 557
闹比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)]
    

提交回复
热议问题