miguendes's blog

miguendes's blog

How to Sort a Dict in Descending Order by Value With Python

How to Sort a Dict in Descending Order by Value With Python

Listen to this article

In this post, you will learn how to sort a dictionary in reverse order by value.

Say that you have the following dictionary containing your grades associated with a subject. You want to sort the grades in a descending order - the highest grade will appear first and the lowest last.

For example, you have this:

grades = {"Math": 34, "Science": 12, "English": 89, "Physics": 8}

... And you want this:

{'English': 89, 'Math': 34:, 'Science': 12:, 'Physics': 8}

You can do this is at least 3 different ways.

Using List Comprehension

The quickest way is to iterate over the key-value pairs of your current dict and call sorted passing the dictionary values and setting reversed=True.

If you are using Python 3.7, regular dicts are ordered by default. So let's use it!

>>> grades = {"Math": 34, "Science": 12, "English": 89, "Physics": 8}
>>> grades
{'Math': 34, 'Science': 12, 'English': 89, 'Physics': 8}
>>> value_key_pairs = ((value, key) for (key,value) in grades.items())
>>> sorted_value_key_pairs = sorted(value_key_pairs, reverse=True)
>>> sorted_value_key_pairs
[(89, 'English'), (34, 'Math'), (12, 'Science'), (8, 'Physics')]
>>> {k: v for v, k in sorted_value_key_pairs}
 {'English': 89, 'Math': 34, 'Science': 12, 'Physics': 8}

And Voila! You have your sorted grades dict in a decreasing manner.

What if I have Python 3.6 or lower?

In this case, you can use OrderedDict from the collections module.

>>> from collections import OrderedDict

>>> OrderedDict((k, v) for v, k in sorted_value_key_pairs)
OrderedDict([('English', 89), ('Math', 34), ('Science', 12), ('Physics', 8)])

Using the operator Module

The operator module provides a functional interface to built-in operators like <, >, == and so on.

This module has many useful functions and one of them is the itemgetter. This function returns a callable object that will fetch the item using the __getitem__() method.

In a nutshell, if you do callable = operator.itemgetter(1), and pass a subscriptable object, say ('Physics', 8) to this callable, it will return the equivalent of ('Physics', 8)[1].

For example,

>>> subject_grade_pair = ('Physics', 8)

>>> get_grade = operator.itemgetter(1)

>>> get_grade(subject_grade_pair)
8

>>> subject_grade_pair[1]
8

Now, the question is, how can we use this to sort the grades?

The sorted built-in function expects not only the iterable you want to sort but also a key. This key argument is nothing more than a function of one argument that you can feed each item of the list. And that’s exactly what we need to sort our list!

>>> import operator

# remember, the grade is the second item in the subject - grade pair
>>> sort_by_grade = operator.itemgetter(1)

>>> grades = {"Math": 34, "Science": 12, "English": 89, "Physics": 8}
>>> grades
{'Math': 34, 'Science': 12, 'English': 89, 'Physics': 8}

>>> sorted_value_key_pairs = sorted(grades.items(), key=sort_by_grade, reverse=True)
>>> {k: v for v, k in sorted_value_key_pairs}
 {'English': 89, 'Math': 34, 'Science': 12, 'Physics': 8}

At this point you might wonder:

This sort_by_grade looks like a glorified lambda...

Well, good shout, this brings us to the last section.

Using lambda

So, as we saw in the previous section, operator.itemgetter returns a callable that is equivalent to calling the __getitem__() method on a subscriptable object.

This is remarkably similar to pass a lambda as a key which takes a tuple, say (“Math”, 34), and returns the second item which is the grade.

>>> sort_by_grade_lambda = lambda subject_grade_pair: subject_grade_pair[1]

>>> sorted_value_key_pairs = sorted(grades.items(), key=sort_by_grade_lambda, reverse=True)

>>> {k: v for v, k in sorted_value_key_pairs}
 {'English': 89, 'Math': 34, 'Science': 12, 'Physics': 8}

Sorting Complex Objects

So far we've only dealt with simple objects such as int. What happens if we have a complex object, such as a custom Grade object?

Let's see how it works.

class Grade:
    def __init__(self, grade: int, cutoff: int = 70):
        self.grade = grade
        self.cutoff = cutoff
        self.passed = grade >= cutoff

    def __repr__(self):
        return f"<Grade(grade={self.grade}, cutoff={self.cutoff}, passed={self.passed}>"

Let's try to sort it...

>>> grades = {"Math": Grade(grade=34), "Science": Grade(grade=12), "English": Grade(grade=89), "Physics": Grade(grade=8)}
>>> grades
grades = {"Math": Grade(grade=34), "Science": Grade(grade=12), "English": Grade(grade=89), "Physics": Grade(grade=8)}

>>> value_key_pairs = ((value, key) for (key,value) in grades.items())
>>> sorted_value_key_pairs = sorted(value_key_pairs, reverse=True)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-0c94e26fda4a> in <module>
----> 1 sorted_value_key_pairs = sorted(value_key_pairs, reverse=True)

TypeError: '<' not supported between instances of 'Grade' and 'Grade'

Oops! It didn't work. The reason is that, as the error message says, Grade doesn't implement the __lt__ operator, which makes it impossible to compare them.

To fix that we can either implement the __lt__ method or use the lambda as we used before with an adaptation. Let's see the lambda approach first.

>>> sort_by_grade_lambda = lambda subject_grade_pair: subject_grade_pair[1].grade

>>> sorted_value_key_pairs = sorted(grades.items(), key=sort_by_grade_lambda, reverse=True)

>>> {k: v for v, k in sorted_value_key_pairs}
{<Grade(grade=89, cutoff=70, passed=True>: 'English',
 <Grade(grade=34, cutoff=70, passed=False>: 'Math',
 <Grade(grade=12, cutoff=70, passed=False>: 'Science',
 <Grade(grade=8, cutoff=70, passed=False>: 'Physics'}

Implementing __lt__

Let's see how it looks when we implement the < operator.

class Grade:
    def __init__(self, grade: int, cutoff: int = 70):
        self.grade = grade
        self.cutoff = cutoff
        self.passed = grade >= cutoff

    def __lt__(self, other: "Grade") -> bool:
        return self.grade < other.grade

    def __repr__(self):
        return f"<Grade(grade={self.grade}, cutoff={self.cutoff}, passed={self.passed}>"

>>> grades = {"Math": Grade(grade=34), "Science": Grade(grade=12), "English": Grade(grade=89), "Physics": Grade(grade=8)}

>>> grades
{'Math': <Grade(grade=34, cutoff=70, passed=False>,
 'Science': <Grade(grade=12, cutoff=70, passed=False>,
 'English': <Grade(grade=89, cutoff=70, passed=True>,
 'Physics': <Grade(grade=8, cutoff=70, passed=False>}

>>> value_key_pairs = ((value, key) for (key,value) in grades.items())

>>> sorted_value_key_pairs = sorted(value_key_pairs, reverse=True)

>>> sorted_value_key_pairs
[(<Grade(grade=89, cutoff=70, passed=True>, 'English'),
 (<Grade(grade=34, cutoff=70, passed=False>, 'Math'),
 (<Grade(grade=12, cutoff=70, passed=False>, 'Science'),
 (<Grade(grade=8, cutoff=70, passed=False>, 'Physics')]

>>> {k: v for v, k in sorted_value_key_pairs}
{'English': <Grade(grade=89, cutoff=70, passed=True>,
 'Math': <Grade(grade=34, cutoff=70, passed=False>,
 'Science': <Grade(grade=12, cutoff=70, passed=False>,
 'Physics': <Grade(grade=8, cutoff=70, passed=False>}

And Voila! You have your sorted grades dict in a decreasing manner.

Conclusion

In this post we saw 3 different ways of sorting a dictionary in descending order with Python. I hope you find it useful.

Other posts you may like:

See you next time!

 
Share this
Proudly part of