I have this list of dicts:
[{'score': '1.9', 'id': 756, 'factors': [1.25, 2.25, 2.5, 2.0, 1.75]}, {'score': '2.0', 'id': 686, 'factors': [2.0, 2.25, 2.75, 1.5, 2.25]}, {'score': '2.0', 'id': 55, 'factors': [1.5, 3.0, 2.5, 1.5, 1.5]}, {'score': '1.9', 'id': 863, 'factors': [1.5, 3.0, 1.5, 2.5, 1.5]}]
I can sort by score with : sorted(l, key=lambda k: k['score'], reverse=True)
. However I have tied scores. How can I sort first by score and then by id asc or desc?
You can sort by a tuple:
sorted(l, key=lambda k: (float(k['score']), k['id']), reverse=True)
This will sort by score
descending, then id
descending. Note that since score
is a string value, it needs to be converted to float
for comparison.
[
{'score': '2.0', 'id': 686, 'factors': [2.0, 2.25, 2.75, 1.5, 2.25]},
{'score': '2.0', 'id': 55, 'factors': [1.5, 3.0, 2.5, 1.5, 1.5]},
{'score': '1.9', 'id': 863, 'factors': [1.5, 3.0, 1.5, 2.5, 1.5]},
{'score': '1.9', 'id': 756, 'factors': [1.25, 2.25, 2.5, 2.0, 1.75]}
]
To sort by id
ascending, use -k['id']
(sorting negated numbers descending is equivalent to sorting the non-negated numbers ascending):
sorted(l, key=lambda k: (float(k['score']), -k['id']), reverse=True)
[
{'score': '2.0', 'id': 55, 'factors': [1.5, 3.0, 2.5, 1.5, 1.5]},
{'score': '2.0', 'id': 686, 'factors': [2.0, 2.25, 2.75, 1.5, 2.25]},
{'score': '1.9', 'id': 756, 'factors': [1.25, 2.25, 2.5, 2.0, 1.75]},
{'score': '1.9', 'id': 863, 'factors': [1.5, 3.0, 1.5, 2.5, 1.5]}
]
To supplement rather than repeat what is stated in the other answers, this question raises an interesting point (although not directly needed in the case in the question) about what happens if you want to sort on two keys, with the primary ascending and the secondary descending, but where neither key is numeric so you cannot employ the trick of negating one of the keys -- i.e. the keys support comparison but not negation.
Suppose you want to sort the following list of dictionaries with primary key 'name'
ascending, and secondary key 'surname'
descending.
l = [{'name': 'Fred', 'surname': 'Jones'}, {'name': 'Fred', 'surname': 'Roberts'},
{'name': 'Diana', 'surname': 'Jackson'}]
Probably the simplest approach is to sort these twice, first on the secondary key, making use of the fact that sort is stable, so the second sort will preserve the order with respect to the secondary key that was achieved in the first sort.
l1 = sorted(l, key=lambda k:k['surname'], reverse=True)
l2 = sorted(l1, key=lambda k:k['name'])
print(l2)
Or the second sorted
could be replaced by doing an in-place sort
on the intermediate list:
l1 = sorted(l, key=lambda k:k['surname'], reverse=True)
l1.sort(key=lambda k:k['name'])
print(l1)
A possible solution to avoid having to sort twice would be to define a comparison function cmp2
returning -1, 0 or 1 as appropriate (note the reversed order of comparison for the secondary key), and then use functools.cmp_to_key
to generate a sort key:
from functools import cmp_to_key
def cmp(a, b):
return (a > b) - (a < b)
def cmp2(k1, k2):
return cmp(k1['name'], k2['name']) or cmp(k2['surname'], k1['surname'])
print(sorted(l, key=cmp_to_key(cmp2)))
Both of the above options will output:
[{'name': 'Diana', 'surname': 'Jackson'}, {'name': 'Fred', 'surname': 'Roberts'}, {'name': 'Fred', 'surname': 'Jones'}]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With