Sorting in Python
By: Bassel Haidar
Tags: Python, Functional Programming, Sorting

Photo by Max Panamá on Unsplash

Sorting is a common activity in the life of software developers . In a standard Python installation, developers have two options to sort their data, either by using the function sorted() or the method sort().

On the surface they seem to be quite similar but upon closer inspection there are some stark differences in terms of the data types they can be used on, the return value they produce, and whether the sorted data is permanently modified or not. Additionally, for the uninitiated, they may produce some unexpected results.

Let’s take a look at sorted() through a series of code examples using numerical data first.

# sorts a list of integers

numbers = [56, 90, 31, 1, 17, 100]

sorted(numbers) [1, 17, 31, 56, 90, 100] # notice when numbers is called after being sorted, it returns the # list of integers in their original order

numbers

[56, 90, 31, 1, 17, 100]

Sorted() is a built-in function. It provides an ordered list as a return value. The sorting is in ascending order by default, and the sorted data is not saved meaning the sorting is not in-place.

# to keep the list in sorted order, assign the sorted return value # to numbers_sorted

numbers_sorted = sorted(numbers)

numbers_sorted

[1, 17, 31, 56, 90, 100] # similarly, sorted() can be used on tuples and sets and its return # value is assigned to numbers_tuple_sorted and numbers_set_sorted

numbers_tuple = (56, 90, 31, 1, 17, 100)

numbers_set = {50, 12, 12, 9, 7, 0}

numbers_tuple_sorted = sorted(numbers_tuple)

numbers_set_sorted = sorted(numbers_set)

Numbers_tuple_sorted and numbers_set_sorted are of type list because sorted() returns an ordered list.

numbers_tuple_sorted

[1, 17, 31, 56, 90, 100] type(numbers_tuple_sorted)

list # by definition a set is an unordered collection data type that is # iterable, mutable, and has no duplicate elements - 12 appears once

numbers_set_sorted

[0, 7, 9, 12, 50] type(numbers_set_sorted)

list

The returned list object can be cast to another data type.

# when casting numbers_tuple_sorted to a tuple, it remains sorted

tuple(numbers_tuple_sorted)

(1, 17, 31, 56, 90, 100) # when casting numbers_set_sorted to a set, we lose the sorting # because a set by definition is an unordered collection

set(numbers_set_sorted)

{0, 7, 9, 12, 50}

Sorted() with strings

Sorted() treats a string like a list. It iterates over each element, sorts each character, including spaces.

string_number = ‘78932’

string_value = ‘Sorting is fun’

sorted_string_number = sorted(string_number)

sorted_string = sorted(string_value)

sorted_string_number

['2', '3', '7', '8', '9'] sorted_string

[' ', ' ', 'S', 'f', 'g', 'i', 'i', 'n', 'n', 'o', 'r', 's', 't', 'u'] # using split() to clean up the output

sorted_string = sorted(string_value.split())

sorted_string ['Sorting', 'fun', 'is'] # put the sorted string together with join()

‘ ‘.join(sorted_string) 'Sorting fun is'

Lists with non-comparable data types can’t be sorted.

mixed_numbers = [201, “10”, 789, “31”, 40, 0]

sorted(mixed_numbers)

--------------------------------------------------------------------

TypeError Traceback (most recent call last)

<ipython-input-68-790f19871423> in <module>()

1 mixed_numbers = [201, "10", 789, "31", 40, 0]

----> 2 sorted(mixed_numbers)



TypeError: '<' not supported between instances of 'str' and 'int'

A combination of integers and strings that are all numbers can be cast to comparable data types by using a list comprehension.

# Each element in mixed_numbers has int() called on it to convert # its str values to int values, allowing us to compare each element # and provide a sorted output without raising an error

sorted([int(x) for x in mixed_numbers])

[0, 10, 31, 40, 201, 789]

Evaluations are implicitly converted by Python to boolean data type.

# The 1 was moved to the last position in the list, all other values # are equal so they retain their original order relative to each # other

similar_values = [False, 0, 1, ‘x’ == ‘B’, 18 <= 0]

sorted(similar_values)

[0, False, False, True, 1] # notice that 1 == 2 is converted to False and all the values are # considered equal and retained their original positions

false_values = [False, 0, 0, 1 == 2, 0, False, False]

sorted(false_values) [False, 0, 0, False, 0, False, False]

When sorting strings, case matters. Sorted() can be used on a list of strings to sort the values in ascending order, which appears alphabetically by default.

names = [‘Brahm’, ‘Helen’, ‘Omer’, ‘Manisha’, ‘Emefa’, ‘Angel’, ‘Nick’]

sorted(names) ['Angel', 'Brahm', 'Emefa', 'Helen', 'Manisha', 'Nick', 'Omer']

Using the built_in function ord(), we can get the integer value of the Unicode code point of a character. If the first letter is the same, then sorted() will use the second character to determine the order all the way to the end of the string.

names_with_case = [‘Brahm’, ‘helen’, ‘Omer’, ‘Manisha’, ‘Emefa’, ‘angel’, ‘nick’]

sorted(names_with_case) ['Brahm', 'Emefa', 'Manisha', 'Omer', 'angel', 'helen', 'nick'] # List comprehension to get the Unicode value the first letter in # each name

[(ord(name[0]), name[0]) for name in sorted(names_with_case)] [(66, 'B'), (69, 'E'), (77, 'M'), (79, 'O'), (97, 'a'), (104, 'h'), (110, 'n')]

Using sorted() with reverse=True changes the default sorting to descending order.

names = [‘Brahm’, ‘Helen’, ‘Omer’, ‘Manisha’, ‘Emefa’, ‘Angel’]

sorted(names, reverse=True)

['Omer', 'Manisha', 'Helen', 'Emefa', 'Brahm', 'Angel']

What makes sorted() so versatile and flexible is the keyword parameter called key

Key expects a function to be passed to it as an argument. The result of the function will ultimately determine the sort order of the object that sorted() is used on.

As mentioned earlier, Python uses a Unicode code points value, meaning every character is mapped to a numeric value (i.e. ‘A’ =65, ‘B’=66, ‘a’=97, ‘b’=98, and ‘ ’ = 32). Notice that capital letters have a lower value than lower case letters. To sort a mixed case strings in alphabetical order, we apply the built-in method str.lower (we could have also used str.upper).

names_with_case = [‘Brahm’, ‘helen’, ‘Omer’, ‘Manisha’, ‘Emefa’, ‘angel’, ‘nick’]

sorted(names_with_case, key=str.lower) ['angel', 'Brahm', 'Emefa', 'helen', 'Manisha', 'nick', 'Omer']

The argument key in sorted() is extremely powerful because almost any function, built-in or user-defined, can be used to manipulate the output order. In the employee_salary_tuple below, we have a list of tuples containing employee names and their corresponding salaries. We used a lambda function to sort them by salary.

# lists are 0 based index in Python, that's why when we use the # lambda function to slice out the salary we access it using index 1

employee_salary_tuple = [(‘Angel’, 120000), (‘Joe’, 80000), (‘Jim’, 40000)]

sorted(employee_salary_tuple, key=lambda employee: employee[1]) [('Jim', 40000), ('Joe', 80000), ('Angel', 120000)]

Suppose we need to sort strings in reverse order. We can accomplish this by creating a function that reverses the word and then use this function as an argument for key in sorted() as shown below.

def reverse_word(word):

return word[::-1] # return word in reverse order words = ['Worldly', 'wiley', 'emotionally', 'Aztec']

sorted(words, key=reverse_word) ['Aztec', 'wiley', 'Worldly', 'emotionally']

Instead of writing a standalone function, we can also use a lambda function.

words = [‘Worldly’, ‘wiley’, ‘emotionally’, ‘Aztec’]

sorted(words, key=lambda x: x[::-1]) ['Aztec', 'wiley', 'Worldly', 'emotionally']

Now, let’s investigate sort()

Sort() is a method of the list class and can only be used on lists.

# calling .sort() like sorted() - calling it like a function instead # of the dot notation for a method will raise an error

values_to_sort = [56, 90, 31, 1, 17, 100]

sort(values_to_sort) --------------------------------------------------------------------

NameError Traceback (most recent call last)

<ipython-input-94-900da950d474> in <module>()

1 values_to_sort = [56, 90, 31, 1, 17, 100]

2 # Try to call .sort() like sorted()

----> 3 sort(values_to_sort)



NameError: name 'sort' is not defined # using sort() on a tuple will also raise an error - it can only be # used on a list tuple_val = (56, 90, 31, 1, 17, 100)

tuple_val.sort() --------------------------------------------------------------------

AttributeError Traceback (most recent call last)

<ipython-input-95-3db46faa9171> in <module>()

1 # Try to use .sort() on a tuple

2 tuple_val = (56, 90, 31, 1, 17, 100)

----> 3 tuple_val.sort()



AttributeError: 'tuple' object has no attribute 'sort'

There is no ordered output in sort(), so the assignment to a new variable only passes a None type.

# Sort the list and assign to new variable

sorted_values = values_to_sort.sort()

print(sorted_values) None

Sort() does the sorting in-place. If we print values_to_sort, we see that it has changed and its original order was not maintained.

print(values_to_sort)

[1, 17, 31, 56, 90, 100]

The same key and reverse arguments in sorted() are also available in sort().

In summary, sort() works only on a list data type, modifies the list it is called on in-place, and has no return value. Sorted() works on a list, tuple, or set. It returns a list that can be assigned to a variable, and does not modify the original list in-place.

References:

Fundakowski, D. (2019, April 23). How to Use sorted() and sort() in Python — Real Python. Retrieved from https://realpython.com/python-sort/