How to Sort a List of Tuples by Second or Multiple Elements in Python
Lists containing tuples are a common way to structure related data in Python. Often, you need to sort this list based on the value of a specific element within each tuple (like the second element) or based on multiple elements acting as primary and secondary sort keys. Python's sorted()
function and list.sort()
method provide powerful ways to achieve this using the key
argument.
This guide demonstrates how to sort lists of tuples by the second element or multiple elements, in both ascending and descending order.
Sorting Fundamentals: The key
Argument
Both the built-in sorted()
function (which returns a new sorted list) and the list.sort()
method (which sorts the list in-place) accept an optional key
argument.
key
: This argument takes a function. This function is called on each element of the list before comparisons are made. The return value of this key function is then used as the basis for the sorting comparison.
This allows you to sort based on criteria other than the default element-by-element comparison of the tuples.
Sort by the Second Element (Index 1)
To sort based solely on the value at index 1 (the second element) of each tuple.
Using sorted()
with lambda
(Ascending)
Provide a lambda
function to the key
argument that extracts the element at index 1 from each tuple.
data = [('Apple', 50), ('Banana', 20), ('Cherry', 30), ('Date', 20)]
# Sort based on the number (index 1) in ascending order
sorted_data = sorted(data, key=lambda tup: tup[1])
print(f"Original: {data}")
# Output: Original: [('Apple', 50), ('Banana', 20), ('Cherry', 30), ('Date', 20)]
print(f"Sorted by 2nd element (Asc): {sorted_data}")
# Output: Sorted by 2nd element (Asc): [('Banana', 20), ('Date', 20), ('Cherry', 30), ('Apple', 50)]
Output:
Original: [('Apple', 50), ('Banana', 20), ('Cherry', 30), ('Date', 20)]
Sorted by 2nd element (Asc): [('Banana', 20), ('Date', 20), ('Cherry', 30), ('Apple', 50)]
lambda tup: tup[1]
: This anonymous function takes a tupletup
and returns its second element (tup[1]
).sorted()
uses these returned values (20, 20, 30, 50) for comparison.
Python's sort is stable. When keys are equal (like the two 20
s), the original relative order of the elements ('Banana'
then 'Date'
) is preserved.
Using sorted()
with lambda
(Descending)
To sort in descending order (largest to smallest based on the key), add the reverse=True
argument.
data = [('Apple', 50), ('Banana', 20), ('Cherry', 30), ('Date', 20)]
# Sort based on the number (index 1) in descending order
sorted_data_desc = sorted(data, key=lambda tup: tup[1], reverse=True)
print(f"Original: {data}")
# Output: Original: [('Apple', 50), ('Banana', 20), ('Cherry', 30), ('Date', 20)]
print(f"Sorted by 2nd element (Desc): {sorted_data_desc}")
# Output: Sorted by 2nd element (Desc): [('Apple', 50), ('Cherry', 30), ('Banana', 20), ('Date', 20)]
Output:
Original: [('Apple', 50), ('Banana', 20), ('Cherry', 30), ('Date', 20)]
Sorted by 2nd element (Desc): [('Apple', 50), ('Cherry', 30), ('Banana', 20), ('Date', 20)]
Using sorted()
with operator.itemgetter
The operator.itemgetter
function provides a slightly more efficient way to create the key function for accessing elements by index.
from operator import itemgetter # Required import
data = [('Apple', 50), ('Banana', 20), ('Cherry', 30), ('Date', 20)]
# Sort based on index 1 using itemgetter (Ascending)
sorted_data_itemgetter = sorted(data, key=itemgetter(1))
print(f"Sorted by 2nd element (itemgetter Asc): {sorted_data_itemgetter}")
# Output: Sorted by 2nd element (itemgetter Asc): [('Banana', 20), ('Date', 20), ('Cherry', 30), ('Apple', 50)]
# Descending with itemgetter
sorted_data_itemgetter_desc = sorted(data, key=itemgetter(1), reverse=True)
print(f"Sorted by 2nd element (itemgetter Desc): {sorted_data_itemgetter_desc}")
# Output: Sorted by 2nd element (itemgetter Desc): [('Apple', 50), ('Cherry', 30), ('Banana', 20), ('Date', 20)]
Output:
Sorted by 2nd element (itemgetter Asc): [('Banana', 20), ('Date', 20), ('Cherry', 30), ('Apple', 50)]
Sorted by 2nd element (itemgetter Desc): [('Apple', 50), ('Cherry', 30), ('Banana', 20), ('Date', 20)]
key=itemgetter(1)
: Creates a callable that directly fetches the element at index 1. Often preferred for simple index-based keys overlambda
.
Sort by Multiple Elements (Primary, Secondary Keys, etc.)
To sort based on multiple criteria (e.g., sort by the second element, and for ties, sort by the third element), have the key
function return a tuple of the values you want to sort by, in the desired order of priority. Python compares tuples element by element.
Using sorted()
with lambda
Returning a Tuple (Ascending)
The lambda
function should return a tuple like (primary_key_element, secondary_key_element, ...)
.
# List of (ID, Category, Value)
data = [(1, 'A', 100), (2, 'B', 50), (3, 'A', 75), (4, 'C', 50), (5, 'B', 60)]
# Sort primarily by Category (index 1), then secondarily by Value (index 2) - Ascending for both
sorted_multi = sorted(data, key=lambda tup: (tup[1], tup[2]))
print(f"Original: {data}")
# Output: Original: [(1, 'A', 100), (2, 'B', 50), (3, 'A', 75), (4, 'C', 50), (5, 'B', 60)]
print(f"Sorted by Cat (1), then Val (2): {sorted_multi}")
# Output: Sorted by Cat (1), then Val (2): [(3, 'A', 75), (1, 'A', 100), (2, 'B', 50), (5, 'B', 60), (4, 'C', 50)]
Output:
Original: [(1, 'A', 100), (2, 'B', 50), (3, 'A', 75), (4, 'C', 50), (5, 'B', 60)]
Sorted by Cat (1), then Val (2): [(3, 'A', 75), (1, 'A', 100), (2, 'B', 50), (5, 'B', 60), (4, 'C', 50)]
key=lambda tup: (tup[1], tup[2])
: Returns tuples like('A', 75)
,('A', 100)
,('B', 50)
, etc.- Python compares these tuples:
('A', 75)
comes before('A', 100)
because75 < 100
.('A', ...)
comes before('B', ...)
because'A' < 'B'
.
Using sorted()
with lambda
Returning a Tuple (Descending)
Use reverse=True
to reverse the overall sort order based on the tuple comparison.
data = [(1, 'A', 100), (2, 'B', 50), (3, 'A', 75), (4, 'C', 50), (5, 'B', 60)]
# Sort primarily by Category (index 1), then secondarily by Value (index 2) - Descending overall
sorted_multi_desc = sorted(data, key=lambda tup: (tup[1], tup[2]), reverse=True)
print(f"Original: {data}")
# Output: Original: [(1, 'A', 100), (2, 'B', 50), (3, 'A', 75), (4, 'C', 50), (5, 'B', 60)]
print(f"Sorted by Cat (1), then Val (2) DESC: {sorted_multi_desc}")
# Output: Sorted by Cat (1), then Val (2) DESC: [(4, 'C', 50), (5, 'B', 60), (2, 'B', 50), (1, 'A', 100), (3, 'A', 75)]
Output:
Original: [(1, 'A', 100), (2, 'B', 50), (3, 'A', 75), (4, 'C', 50), (5, 'B', 60)]
Sorted by Cat (1), then Val (2) DESC: [(4, 'C', 50), (5, 'B', 60), (2, 'B', 50), (1, 'A', 100), (3, 'A', 75)]
If you need mixed ascending/descending order (e.g., Category ascending, Value descending), you often need multiple sort passes or more complex key functions (like multiplying numeric secondary keys by -1 for reverse numeric sort).
Using sorted()
with operator.itemgetter
itemgetter
can also fetch multiple indices, returning them as a tuple, making it suitable for multi-key sorting.
from operator import itemgetter # Required import
data = [(1, 'A', 100), (2, 'B', 50), (3, 'A', 75), (4, 'C', 50), (5, 'B', 60)]
# Sort by index 1, then index 2 using itemgetter (Ascending)
sorted_multi_itemgetter = sorted(data, key=itemgetter(1, 2))
print(f"Sorted by Cat (1), then Val (2) (itemgetter): {sorted_multi_itemgetter}")
# Output: Sorted by Cat (1), then Val (2) (itemgetter): [(3, 'A', 75), (1, 'A', 100), (2, 'B', 50), (5, 'B', 60), (4, 'C', 50)]
# Descending overall
sorted_multi_itemgetter_desc = sorted(data, key=itemgetter(1, 2), reverse=True)
print(f"Sorted by Cat (1), then Val (2) (itemgetter Desc): {sorted_multi_itemgetter_desc}")
# Output: Sorted by Cat (1), then Val (2) (itemgetter Desc): [(4, 'C', 50), (5, 'B', 60), (2, 'B', 50), (1, 'A', 100), (3, 'A', 75)]
Output:
Sorted by Cat (1), then Val (2) (itemgetter): [(3, 'A', 75), (1, 'A', 100), (2, 'B', 50), (5, 'B', 60), (4, 'C', 50)]
Sorted by Cat (1), then Val (2) (itemgetter Desc): [(4, 'C', 50), (5, 'B', 60), (2, 'B', 50), (1, 'A', 100), (3, 'A', 75)]
key=itemgetter(1, 2)
: Creates a callable that fetches elements at index 1 and 2, returning them as a tuple(element_at_1, element_at_2)
.
In-Place Sorting using list.sort()
If you want to modify the original list directly instead of creating a new sorted list, use the list.sort()
method. It accepts the same key
and reverse
arguments.
from operator import itemgetter
data_to_sort = [(1, 'A', 100), (2, 'B', 50), (3, 'A', 75)]
print(f"Original list (before sort): {data_to_sort}")
# Output: Original list (before sort): [(1, 'A', 100), (2, 'B', 50), (3, 'A', 75)]
# Sort the list in-place by index 1, then 2
data_to_sort.sort(key=itemgetter(1, 2)) # No return value needed, list is modified
print(f"Original list (after sort): {data_to_sort}")
# Output: Original list (after sort): [(3, 'A', 75), (1, 'A', 100), (2, 'B', 50)]
Output:
Original list (before sort): [(1, 'A', 100), (2, 'B', 50), (3, 'A', 75)]
Original list (after sort): [(3, 'A', 75), (1, 'A', 100), (2, 'B', 50)]
list.sort()
modifies the list directly and returnsNone
.
Choosing Between sorted()
and list.sort()
- Use
sorted(my_list, ...)
when you want to keep the original list unchanged and get a new sorted list. - Use
my_list.sort(...)
when you want to modify the original list in-place and don't need to preserve the original order (this can be slightly more memory efficient as it doesn't create a new list).
Conclusion
Python's sorting functions are highly flexible for lists of tuples thanks to the key
argument:
- To sort by the second element (index 1), use
key=lambda t: t[1]
orkey=operator.itemgetter(1)
. - To sort by multiple elements (e.g., index 1 then index 2), use
key=lambda t: (t[1], t[2])
orkey=operator.itemgetter(1, 2)
. The key function should return a tuple representing the sort priority. - Use
reverse=True
with eithersorted()
orlist.sort()
for descending order based on the key. - Choose
sorted()
to get a new list orlist.sort()
to modify the list in-place.
Leveraging the key
argument provides precise control over how lists of tuples are ordered based on their internal elements.