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

Subscribe to my newsletter and never miss my upcoming articles

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!

#python#tutorial
 
Share this
Proudly part of