Swiftorial Logo
Home
Swift Lessons
Tutorials
Learn More
Career
Resources

Python FAQ: Top Questions

44. Explain different ways to sort a list in Python.

Python provides versatile ways to sort lists, offering control over the sorting order, the keys used for comparison, and whether the sorting should be in-place or return a new sorted list. The two primary mechanisms are the `list.sort()` method and the `sorted()` built-in function.

1. `list.sort()` Method:

  • **Type:** It's a method of the `list` object.
  • **Operation:** Sorts the list **in-place**, meaning it modifies the original list directly.
  • **Return Value:** Returns `None`. This is a common Python convention for functions/methods that modify objects in-place.
  • **Use Case:** When you don't need the original order of the list and want to save memory by not creating a new list.

2. `sorted()` Built-in Function:

  • **Type:** It's a built-in function, available globally.
  • **Operation:** Returns a **new sorted list**, leaving the original iterable unchanged.
  • **Input:** Can take any iterable (list, tuple, string, set, dictionary, generator, etc.) as input.
  • **Return Value:** Always returns a new `list`.
  • **Use Case:** When you need to preserve the original iterable, or when sorting non-list iterables.

Common Parameters for Both `sort()` and `sorted()`:

Both `list.sort()` and `sorted()` accept optional keyword arguments to customize the sorting behavior:

  • **`reverse` (boolean):**
    • If `True`, the list is sorted in descending order. Defaults to `False` (ascending).
  • **`key` (function):**
    • Specifies a function of one argument that will be called on each item in the list to extract a comparison key.
    • The items are then sorted based on these comparison keys. This is extremely powerful for custom sorting.
    • Examples: `key=str.lower` (case-insensitive string sort), `key=len` (sort by length), `key=lambda x: x.attribute` (sort by object attribute).

Stability of Sort Algorithms:

Python's Timsort algorithm (used by both `sort()` and `sorted()`) is a **stable** sorting algorithm. This means that if two items have equal comparison keys, their relative order in the original input is preserved in the sorted output.

Comparison Table:

Feature `list.sort()` Method `sorted()` Built-in Function
Target `list` object Any iterable
Modifies Original? Yes (in-place) No (returns new list)
Return Value `None` New `list`
Memory Usage Generally lower (no new list created) Higher (creates a new list)
Parameters `reverse`, `key` `reverse`, `key`
Use Case Sort a list when original order is not needed. Sort any iterable, preserve original, or need sorted copy.

Choosing between `list.sort()` and `sorted()` depends on whether you need to modify the original list or obtain a new sorted list, and what type of iterable you are working with.


# --- Example 1: Using list.sort() (in-place) ---
print("--- Using list.sort() (in-place) ---")

numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(f"Original numbers: {numbers}")

numbers.sort() # Sorts in ascending order by default
print(f"Sorted (ascending): {numbers}")

numbers.sort(reverse=True) # Sorts in descending order
print(f"Sorted (descending): {numbers}")

words = ["banana", "apple", "Cherry", "date"]
print(f"\nOriginal words: {words}")
words.sort() # Case-sensitive sort ('Cherry' comes before 'apple' because 'C' < 'a')
print(f"Sorted (case-sensitive): {words}")

words.sort(key=str.lower) # Case-insensitive sort using key
print(f"Sorted (case-insensitive): {words}")

# list.sort() returns None
result = numbers.sort()
print(f"Result of numbers.sort(): {result}") # Output: None


# --- Example 2: Using sorted() (returns new list) ---
print("\n--- Using sorted() (returns new list) ---")

data = (5, 2, 8, 1, 9, 3) # A tuple
print(f"Original tuple: {data}")

sorted_data_asc = sorted(data)
print(f"Sorted tuple (ascending): {sorted_data_asc}")
print(f"Original tuple remains: {data}") # Original tuple is unchanged

sorted_data_desc = sorted(data, reverse=True)
print(f"Sorted tuple (descending): {sorted_data_desc}")


# Sorting a string (returns list of characters)
my_string = "Python"
sorted_chars = sorted(my_string)
print(f"Sorted characters of '{my_string}': {sorted_chars}")


# --- Example 3: Sorting with a Custom Key Function ---
print("\n--- Sorting with a Custom Key Function ---")

# Sorting a list of strings by length
fruits = ["apple", "banana", "kiwi", "grapefruit", "orange"]
print(f"Original fruits: {fruits}")

sorted_by_length = sorted(fruits, key=len)
print(f"Sorted by length: {sorted_by_length}") # Output: ['kiwi', 'apple', 'banana', 'orange', 'grapefruit']

sorted_by_length_desc = sorted(fruits, key=len, reverse=True)
print(f"Sorted by length (descending): {sorted_by_length_desc}") # Output: ['grapefruit', 'banana', 'orange', 'apple', 'kiwi']

# Sorting a list of dictionaries by a specific key
students = [
    {"name": "Alice", "score": 88, "age": 20},
    {"name": "Bob", "score": 92, "age": 22},
    {"name": "Charlie", "score": 88, "age": 21},
    {"name": "David", "score": 75, "age": 19},
]
print(f"\nOriginal students: {students}")

# Sort by score (ascending)
sorted_by_score = sorted(students, key=lambda s: s["score"])
print(f"Sorted by score: {sorted_by_score}")

# Sort by score (descending), then by age (ascending) for ties (stability)
sorted_by_score_age = sorted(students, key=lambda s: (s["score"], s["age"]), reverse=True)
print(f"Sorted by score (desc), then age (asc for ties): {sorted_by_score_age}")
# Note: Charlie (88, 21) is before Alice (88, 20) in original.
# In sorted_by_score, Alice (88, 20) comes before Charlie (88, 21) due to age (secondary key).
# In sorted_by_score_age, Alice (88, 20) is still before Charlie (88, 21) for 88 score,
# then by age, but reverse=True applies to primary key, secondary key is asc unless specified.
# To make secondary key desc, use: key=lambda s: (s["score"], -s["age"])

# Correct for secondary key sorting in reverse scenario (if ages also descending for ties)
sorted_by_score_age_all_desc = sorted(students, key=lambda s: (s["score"], -s["age"]), reverse=True)
print(f"Sorted by score (desc), then age (desc for ties): {sorted_by_score_age_all_desc}")


# --- Example 4: Stability of Python's Sort ---
print("\n--- Stability of Sort ---")

items = [("apple", 1), ("banana", 2), ("apple", 3), ("orange", 4)]
print(f"Original items: {items}")

# Sort by the fruit name (the first element of the tuple)
# 'apple' with value 1 appears before 'apple' with value 3 in original.
# This relative order will be preserved because Python's sort is stable.
stable_sorted_items = sorted(items, key=lambda x: x[0])
print(f"Stable sorted by name: {stable_sorted_items}")
# Output: [('apple', 1), ('apple', 3), ('banana', 2), ('orange', 4)]
# Notice (apple, 1) still comes before (apple, 3) as in original.
        

Explanation of the Example Code:

  • **Using `list.sort()`:**
    • `numbers.sort()` modifies `numbers` directly. It returns `None`, so assigning its result to a variable (`result = numbers.sort()`) makes `result` equal to `None`.
    • `reverse=True` sorts in descending order.
    • `words.sort()` performs a default lexicographical (case-sensitive) sort.
    • `words.sort(key=str.lower)` uses the `key` argument to sort strings case-insensitively. The `str.lower` function is applied to each word to generate a temporary "key" for comparison, but the original words are still what are sorted.
  • **Using `sorted()`:**
    • `sorted(data)` creates a *new list* from the `data` tuple, leaving the original `data` unchanged. This is crucial when you need to preserve the original collection.
    • It can sort any iterable, as shown by sorting a string, which produces a list of sorted characters.
  • **Sorting with a Custom Key Function:**
    • `key=len` sorts the `fruits` list based on the length of each string, not their alphabetical order.
    • `key=lambda s: s["score"]` sorts the `students` list of dictionaries based on the value of the "score" key in each dictionary. Lambda functions are very common for inline key functions.
    • `key=lambda s: (s["score"], s["age"])` demonstrates sorting by multiple criteria. Python sorts tuples element by element. If scores are equal, it then uses age for comparison. The `reverse=True` applies primarily to the first element of the tuple. To reverse a secondary key, you often need to negate numerical values (e.g., `-s["age"]`).
  • **Stability of Sort:**
    • The `items` list has two tuples with the same primary sorting key (`"apple"`).
    • After sorting by name, the relative order of `('apple', 1)` and `('apple', 3)` remains the same as in the original list, illustrating the stability of Python's Timsort algorithm.

These examples cover the fundamental ways to sort in Python, highlighting the differences between `sort()` and `sorted()` and the power of the `key` argument for custom sorting logic.