# 5 Best Ways to Sort a List of Tuples by Multiple Keys in Python

π‘ Problem Formulation:

Sometimes in Python, we need to sort a list of tuples based on multiple keys, each possibly with a different sorting order. For example, given a list like `[('Alice', 25, 'C'), ('Bob', 22, 'A'), ('Alice', 30, 'B')]`, we may want to sort primarily by the first element, then by the second element in ascending order, and finally by the third element in descending order to receive `[('Alice', 25, 'C'), ('Alice', 30, 'B'), ('Bob', 22, 'A')]`.

## Method 1: Using the sorted() Function with a Custom Sorting Key

One of the most flexible approaches to sort a list of tuples is using the `sorted()` function and specifying a custom sorting key via a lambda function that returns a tuple representing the multiple sorting keys. Python sorts based on the order of elements within the key tuple. This method provides clear sorting precedence, which is both customizable and readable.

Here’s an example:

```tuples_list = [('Alice', 25, 'C'), ('Bob', 22, 'A'), ('Alice', 30, 'B')]
sorted_list = sorted(tuples_list, key=lambda x: (x[0], x[1], -ord(x[2])))
print(sorted_list)```

Output:

`[('Alice', 25, 'C'), ('Alice', 30, 'B'), ('Bob', 22, 'A')]`

This code snippet sorts the list of tuples first by the first element in each tuple, then by the second element, and finally in reverse order by the third element using the ASCII value (obtained via the `ord()` function).

## Method 2: The itemgetter Function from operator Module

The `itemgetter` function from the `operator` module creates a callable that serves as the key function for the `sorted()`. It’s typically more efficient than a lambda and can simplify the code when sorting by multiple keys.

Here’s an example:

```from operator import itemgetter
tuples_list = [('Alice', 25, 'C'), ('Bob', 22, 'A'), ('Alice', 30, 'B')]
sorted_list = sorted(tuples_list, key=itemgetter(0, 1, 2))
print(sorted_list)```

Output:

`[('Alice', 25, 'C'), ('Alice', 30, 'B'), ('Bob', 22, 'A')]`

This code uses `itemgetter()` to fetch the first, second, and third elements as a sorting key. Keep in mind that unlike lambda functions, using `itemgetter()` doesn’t allow to easily change the sorting order for individual keys.

## Method 3: In-Place Sorting Using the list.sort() Method

If you don’t need a new list and want to sort the tuples in place, you can use the `list.sort()` method. It is slightly more efficient than `sorted()` since it doesn’t need to create a new list.

Here’s an example:

```tuples_list = [('Alice', 25, 'C'), ('Bob', 22, 'A'), ('Alice', 30, 'B')]
tuples_list.sort(key=lambda x: (x[0], x[1], -ord(x[2])))
print(tuples_list)```

Output:

`[('Alice', 25, 'C'), ('Alice', 30, 'B'), ('Bob', 22, 'A')]`

Here we have the same sorting logic as in the first method, but we are applying it directly to the list without creating a new sorted list.

## Method 4: Sorting with Multiple Passes

If the sorting criteria are complex or you want to sort by one key and then by another while keeping the previous sort order, you can sort the list multiple times. This is less efficient than other methods but can be more straightforward to understand in some cases.

Here’s an example:

```tuples_list = [('Alice', 25, 'C'), ('Bob', 22, 'A'), ('Alice', 30, 'B')]
tuples_list.sort(key=lambda x: -ord(x[2]))  # Sort by third element descending
tuples_list.sort(key=lambda x: x[1])  # Stable sort by second element
tuples_list.sort(key=lambda x: x[0])  # Stable sort by first element
print(tuples_list)```

Output:

`[('Alice', 25, 'C'), ('Alice', 30, 'B'), ('Bob', 22, 'A')]`

This code snippet employs a multi-pass sorting strategy, where each `sort()` call is stable (it maintains the order of equal elements), ensuring that the final order reflects all sorting keys.

## Bonus One-Liner Method 5: Using the attrgetter Function from operator Module

If the list consists of objects or namedtuples instead of plain tuples, `attrgetter` can be used to sort by multiple attributes. Just like `itemgetter`, it returns a callable that extracts the specified field(s).

Here’s an example:

```from collections import namedtuple
from operator import attrgetter
Person = namedtuple('Person', 'name age grade')
person_list = [Person('Alice', 25, 'C'), Person('Bob', 22, 'A'), Person('Alice', 30, 'B')]
sorted_person_list = sorted(person_list, key=attrgetter('name', 'age', 'grade'))
print(sorted_person_list)```

Output:

`[Person(name='Alice', age=25, grade='C'), Person(name='Alice', age=30, grade='B'), Person(name='Bob', age=22, grade='A')]`

This code sorts a list of namedtuples based on multiple attributes using the `attrgetter` and outputs a sorted version of the original list.

## Summary/Discussion

• Method 1: Using `sorted()` with a lambda function. Strengths: Highly flexible and clear. Weaknesses: Lambda functions can be less efficient than itemgetter for large lists.
• Method 2: Using `sorted()` with `itemgetter`. Strengths: Efficient and concise for simple sorting criteria. Weaknesses: Does not support complex sorting logic where the order needs to be reversed for individual keys.
• Method 3: Using `list.sort()`. Strengths: It sorts the list in place, which can be more efficient. Weaknesses: Modifies the original list, which might not always be desirable.
• Method 4: Multiple passes with `sort()`. Strengths: Conceptually simple and very explicit. Weaknesses: Can be inefficient, especially for large datasets.
• Bonus Method 5: Using `sorted()` with `attrgetter`. Strengths: Ideal for sorting objects and namedtuples. Weaknesses: Only applicable to objects or namedtuples, not regular tuples.