How to sort a dictionary by value in Python
Sorting a dictionary by value is a one-liner. Getting the ties right, and mixing ascending and descending in the same sort, is where it gets interesting.
The answer
ordered = dict(sorted(scores.items(), key=lambda kv: kv[1]))
scores = {'ada': 92, 'alan': 78, 'grace': 92, 'linus': 65}
ascending : {'linus': 65, 'alan': 78, 'ada': 92, 'grace': 92}
descending : reverse=True
Three things are going on. .items() gives (key, value) pairs, the key=
function picks the value out of each pair, and
sorted returns a
list — so dict() around it turns the result back into a mapping:
type : list
[('linus', 65), ('alan', 78), ('ada', 92), ('grace', 92)]
That last step only preserves the order because dictionaries keep insertion
order, which has been guaranteed since Python 3.7. On older versions you would
need collections.OrderedDict.
Often you do not want a dict back at all — a list of pairs is easier to work with, and it can hold duplicates.
operator.itemgetter(1) is a slightly faster and arguably clearer alternative
to the lambda:
key=itemgetter(1) : [('linus', 65), ('alan', 78), ('ada', 92), ('grace', 92)]
Ties
ada and grace both scored 92. Which comes first?
ascending : ['linus', 'alan', 'ada', 'grace']
ada, because it came first in the original dictionary. Python’s sort is
stable: equal elements keep their relative order. That is a guarantee, not
an accident, and you can rely on it.
If you want ties broken explicitly, sort by a tuple:
key=lambda kv: (kv[1], kv[0])
by value, then name : ['linus', 'alan', 'ada', 'grace']
Tuples compare element by element, so this reads as “by value, then by name”.
Mixing directions
Here is the one that is genuinely awkward. You want highest score first, and
names alphabetically within the same score. reverse=True reverses
everything, including the names.
For numbers, negate:
key=lambda kv: (-kv[1], kv[0])
value DESC, name ASC : ['ada', 'grace', 'alan', 'linus']
Scores descending, names ascending within a tie. Negating only works on numbers, of course. For strings the general trick is to sort twice, least significant key first, relying on stability:
rows = sorted(rows, key=itemgetter(0)) # name, ascending
rows = sorted(rows, key=itemgetter(1), reverse=True) # score, descending
The second sort preserves the first sort’s order among equal scores. This works for any combination of directions and types.
The slip worth naming
keys sorted by value : ['linus', 'alan', 'ada', 'grace']
sorted(scores) : ['ada', 'alan', 'grace', 'linus']
sorted(scores) iterates the dict, which yields keys, so you get the keys
sorted alphabetically. It looks plausible and it is not what you asked for.
If you only want the keys in value order, pass the dict’s own lookup as the key function:
sorted(scores, key=scores.get)
When you only want the top few
Sorting everything to look at three items is wasteful.
heapq.nlargest
does it without a full sort:
heapq.nlargest(2, scores, key=scores.get) : ['ada', 'grace']
And if you are counting things,
Counter
has it built in:
Counter('mississippi').most_common(2) : [('i', 4), ('s', 4)]
most_common() is already sorted by count, descending. No key= needed.
Two smaller things
There is no in-place version:
original unchanged : True
Lists have .sort(); dicts do not. sorted() always gives you something new.
And if your key function can fail, it fails loudly, mid-sort:
-> KeyError: 'n'
That is a dict whose values are themselves dicts, one of which is missing the
field being sorted on. .get('n', 0) fixes it:
with .get(..., 0) : [('b', {}), ('a', {'n': 1})]
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).