Sorting in Python

In this post I want to show some quick instructions for sorting in Python. Obviously I am talking about Python 3.

Basic sorting of list of basic data types

Let's start with sorting numbers. Let's say you have a list of numbers

list = [5,7,1,3,4,10,2.5]

To get the list sorted, you can call the function sorted like this

sorted_list = sorted(list)
print(sorted_list)
[1, 2.5, 3, 4, 5, 7, 10]

You can also sort the numbers in descending order. Simply by passing the reverse parameter and setting it to true

sorted_list = sorted(list, reverse=True)
print(sorted_list)
[10, 7, 5, 4, 3, 2.5, 1]

You can also do the same with strings as well.

words = ["mountains", "are", "beautiful"]
sorted_words = sorted(words, reverse=True)
print(sorted_words)
['mountains', 'beautiful', 'are']

Sorting of List Objects

Now let's make it more complicated. Let's say you have a list of lists, and you need to sort these lists based on the value in the second index (index = 1)

lists = [[2,10], [1,3], [0,5]]

You can achieve by passing by passing a lambda to the key

sorted(lists, key=lambda x: x[1])
[[1, 3], [0, 5], [2, 10]]

But if you want to achieve it without writing your own lambda, the operator module already provides you with such lambda(s)

from operator import itemgetter
sorted(lists, key=itemgetter(1))
[[1, 3], [0, 5], [2, 10]]

Sorting of objects

Now let's make it more complicated, and try to sort class objects. Let's go ahead and define a Student class that has a name, and scores math and english

class Student:
    def __init__(self, name, math, english):
        self.name = name
        self.math = math
        self.english = english
    def __repr__(self):
        return self.name

then let's create two objects of this class

students = [Student("omar", 10, 20), Student("john", 20, 5)]

To sort the students based on some attribute, we can use the operator module attrgetter.

from operator import attrgetter
sorted_math = sorted(students, key=attrgetter("math"))
print(sorted_math)
[omar, john]

The same thing can be done for both the english and name attributes

sorted_english = sorted(students, key=attrgetter("english"))
print(sorted_english)
[john, omar]

sorted_name = sorted(students, key=attrgetter("name"))
print(sorted_name)
[john, omar]

Advanced Sorting in Python

Now let's make the requirements much harder. Let's say that you want to sort a list of Student objects, to satisfy these requirements: 1. Students with the name omar should appear first. 2. Students without the name omar should appear afterwards. 3. Students with name omar should be sorted according to their math grade in descending order. 4. Other students are sorted according to their name, then math grade in ascending order. Name has more priority.

So the sorted list should look something like this

[ < students with name omar sorted in descending order according to math grade > , < students with name != omar sorted in ascending order according to their (name, math) grade >]

I hope you don't hate me for this. To achieve this, we have to write our own comparing algorithm, that will be fed into the sorting algorithm. The algorithm (function) will be used to compare two objects. The sorting method sorted will take care of the rest.

Let's start by importing functools module. Because we need to use its cmp_to_key method. Then lets redefine the Student class, because we need to show the math grade now in the string representation of each student object.

import functools

class Student:
    def __init__(self, name, math, english):
        self.name = name
        self.math = math
        self.english = english
    def __repr__(self):
        return f"{self.name}/{self.math}"

Now we need to write our own comparison method. It goes like this

def my_compare_function(student1, student2): 
    if student1.name == student2.name:
        if student1.name == "omar":
            return 1 if student1.math < student2.math else (0 if student1.math == student2.math else -1)
        else:
            return -1 if student1.math < student2.math else (0 if student1.math == student2.math else 1)
    else:
        if student1.name == "omar":
          return -1
        elif student2.name == "omar":
          return 1
        else:
          if student1.name < student2.name:
            return -1
          else:
            return 1

This method always takes two objects to compare. In our case two student objects. It needs to return -1 if student1 should appear before student2. 1 if student2 should appear first. It needs to return 0 if they are equal. In our case the name and the math grade are equal. To preserve the order of the student objects in the list, and not reorder them.

Finally, we need to pass this comparison method to the functools.cmp_to_key method, and sort the students.

students = [Student("omar", 10, 20), Student("john", 20, 5), Student("omar", 1, 20), Student("omar", 2, 20), Student("zoey", 10, 20), Student("hany", 20, 4), Student("hany", 3, 10)]
print(sorted(students, key=functools.cmp_to_key(my_compare_function)))

which should print

[omar/10, omar/2, omar/1, hany/3, hany/20, john/20, zoey/10]

As expected.

I hope this was useful. Write me on Twitter @OmarQunsul if something is not clear. I would love to hear back from you.

References


Homepage