Murphy4 Murphy4 - 1 month ago 14
Python Question

How to sort a dictionary by two elements, reversing only one

Consider the following dictionary:

data = {'A':{'total':3},
'B':{'total':5},
'C':{'total':0},
'D':{'total':0},
}


The desired order for above is B, A, C, D. Order by descending total, then by ascending key.

When I call
sorted(data, key=lambda x: (data[x]['total'], x), reverse=True)

I get B,A,D,C because reverse is called on both keys.

Is there an efficient way to solve this?

Answer

Sort on negative total, that'll reverse put the totals in reverse order without having to use reverse=True. Ties are then broken on the key in forward order:

sorted(data, key=lambda x: (-data[x]['total'], x))

Demo:

>>> data = {'A':{'total':3},
...         'B':{'total':5},
...         'C':{'total':0},
...         'D':{'total':0},
...        }
>>> sorted(data, key=lambda x: (-data[x]['total'], x))
['B', 'A', 'C', 'D']

This trick only works for numeric components in a sort key; if you have multiple keys that require a sort direction change that are not numeric, you'd have to do a multi-pass sort (sort multiple times, from last key to first):

# when you can't take advantage of numerical values to reverse on
# you need to sort repeatedly from last key to first.
# Here, sort forward by dict key, then in reverse by total
bykey = sorted(data)
final = sorted(bykey, key=lambda x: data[x]['total'], reverse=True)

This works because the Python sort algorithm is stable; two elements keep their relative positions if the current sort key result is equal for those two elements.