Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 94 additions & 14 deletions sorts/recursive_quick_sort.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,102 @@
def quick_sort(data: list) -> list:
"""
>>> for data in ([2, 1, 0], [2.2, 1.1, 0], "quick_sort"):
... quick_sort(data) == sorted(data)
"""
A pure Python implementation of the recursive quick sort algorithm

This variant of quicksort picks the first element of the collection as the
pivot and recursively sorts the two sub-collections that fall on either
side of that pivot. It returns a new list with the same items in
non-decreasing order; the input list is never modified (for inputs of
length less than two the very same list object is returned as-is).

For doctests run following command:
python3 -m doctest -v recursive_quick_sort.py

For manual testing run:
python3 recursive_quick_sort.py
"""

from typing import Any, Protocol, TypeVar


class Comparable(Protocol):
def __lt__(self, other: Any, /) -> bool: ...


T = TypeVar("T", bound=Comparable)


def recursive_quick_sort[T: Comparable](collection: list[T]) -> list[T]:
"""A pure Python implementation of the recursive quick sort algorithm.

The first element of ``collection`` is used as the pivot: every
remaining item that is less than or equal to the pivot goes into
the left sub-collection, every greater item into the right one, and
the two sub-collections are then sorted recursively.

Complexity Analysis:
Time Complexity:
- Best Case: O(n log n) when the pivot splits evenly
- Average Case: O(n log n)
- Worst Case: O(n^2) when the first element is always an
extreme of the remaining items (e.g. an already sorted or
reverse sorted collection)
Space Complexity:
- O(n) for the sub-collections built at every recursion level
(the algorithm does not mutate the input)

:param collection: some mutable ordered collection with mutually
comparable items inside
:return: a list with the same items ordered in ascending order

Examples:
>>> recursive_quick_sort([0, 5, 3, 2, 2])
[0, 2, 2, 3, 5]
>>> recursive_quick_sort([])
[]
>>> recursive_quick_sort([5])
[5]
>>> recursive_quick_sort([-2, 5, 0, -45])
[-45, -2, 0, 5]
>>> recursive_quick_sort([2, 1, 0]) == sorted([2, 1, 0])
True
>>> recursive_quick_sort([2.2, 1.1, 0.0]) == sorted([2.2, 1.1, 0.0])
True
>>> recursive_quick_sort([2.5, -1, 0.0]) == sorted([2.5, -1, 0.0])
True
>>> recursive_quick_sort(['d', 'a', 'b', 'c']) == sorted(['d', 'a', 'b', 'c'])
True
>>> recursive_quick_sort(['z', 'a', 'y', 'b', 'x', 'c'])
['a', 'b', 'c', 'x', 'y', 'z']
>>> import random
>>> collection = random.sample(range(-50, 50), 100)
>>> recursive_quick_sort(collection) == sorted(collection)
True
>>> import string
>>> collection = random.choices(string.ascii_letters + string.digits, k=100)
>>> recursive_quick_sort(collection) == sorted(collection)
True
>>> recursive_quick_sort([1, "a"]) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
TypeError: '<=' not supported between instances of 'str' and 'int'
"""
if len(data) <= 1:
return data
else:
return [
*quick_sort([e for e in data[1:] if e <= data[0]]),
data[0],
*quick_sort([e for e in data[1:] if e > data[0]]),
]
if len(collection) <= 1:
return collection
return [
*recursive_quick_sort(
[item for item in collection[1:] if item <= collection[0]]
),
collection[0],
*recursive_quick_sort(
[item for item in collection[1:] if item > collection[0]]
),
]


if __name__ == "__main__":
import doctest
from doctest import testmod

testmod()

doctest.testmod()
user_input = input("Enter numbers separated by a comma:\n").strip()
unsorted = [int(item) for item in user_input.split(",")]
print(f"{recursive_quick_sort(unsorted) = }")
3 changes: 3 additions & 0 deletions tests/test_sorts.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from sorts.quick_sort_3_partition import three_way_radix_quicksort
from sorts.recursive_insertion_sort import rec_insertion_sort
from sorts.recursive_mergesort_array import merge
from sorts.recursive_quick_sort import recursive_quick_sort
from sorts.reverse_selection import reverse_selection_sort
from sorts.reversort import reversort
from sorts.selection_sort import selection_sort
Expand Down Expand Up @@ -117,6 +118,7 @@ def test_intro_sort_heap_fallback_preserves_surrounding_items(max_depth: int) ->
pancake_sort,
patience_sort,
quick_sort,
recursive_quick_sort,
reverse_selection_sort,
reversort,
selection_sort,
Expand Down Expand Up @@ -196,6 +198,7 @@ def test_rec_insertion_sort(case) -> None:
odd_even_transposition,
pancake_sort,
patience_sort,
recursive_quick_sort,
reverse_selection_sort,
reversort,
selection_sort,
Expand Down
2 changes: 1 addition & 1 deletion web_programming/crypto_price_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def crypto_price(coin: str = "bitcoin") -> float:
url = f"https://api.coingecko.com/api/v3/simple/price?ids={coin}&vs_currencies=usd"
try:
json_response = httpx2.get(url, timeout=10).raise_for_status().json()
except httpx2.RequestError, ValueError, KeyError:
except httpx2.HTTPStatusError, httpx2.RequestError, ValueError, KeyError:
return 0.0
return float(json_response.get(coin, {}).get("usd", 0.0))

Expand Down
Loading