From e7c45052aa2c9c30fc6a6d8f40e7e53a19f361ab Mon Sep 17 00:00:00 2001 From: shubh Date: Tue, 31 Mar 2026 12:02:28 +0530 Subject: [PATCH 1/5] add: median of medians algorithm in maths --- maths/median_of_medians.py | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 maths/median_of_medians.py diff --git a/maths/median_of_medians.py b/maths/median_of_medians.py new file mode 100644 index 000000000000..2bfd87d945f5 --- /dev/null +++ b/maths/median_of_medians.py @@ -0,0 +1,71 @@ +""" +Median of Medians Algorithm +Guarantees O(n) worst-case time complexity for finding the k-th smallest element. +Reference: https://en.wikipedia.org/wiki/Median_of_medians +""" + + +def partition(arr: list, pivot: int) -> tuple: + """Partition array into elements less than, equal to, and greater than pivot.""" + low = [x for x in arr if x < pivot] + high = [x for x in arr if x > pivot] + equal = [x for x in arr if x == pivot] + return low, equal, high + + +def median_of_medians(arr: list, k: int) -> int: + """ + Find the k-th smallest element in an unsorted list using Median of Medians. + + Args: + arr: List of comparable elements + k: 1-based index of the desired smallest element + + Returns: + The k-th smallest element in arr + + Raises: + ValueError: If k is out of range + + Examples: + >>> median_of_medians([3, 1, 4, 1, 5, 9, 2, 6], 3) + 2 + >>> median_of_medians([7, 2, 10, 5], 1) + 2 + >>> median_of_medians([1, 2, 3, 4, 5], 5) + 5 + """ + if not 1 <= k <= len(arr): + raise ValueError(f"k={k} is out of range for array of length {len(arr)}") + + # Base case + if len(arr) <= 5: + return sorted(arr)[k - 1] + + # Step 1: Divide into chunks of 5 and find median of each chunk + chunks = [arr[i : i + 5] for i in range(0, len(arr), 5)] + medians = [sorted(chunk)[len(chunk) // 2] for chunk in chunks] + + # Step 2: Recursively find median of medians + pivot = median_of_medians(medians, len(medians) // 2 + 1) + + # Step 3: Partition around pivot + low, equal, high = partition(arr, pivot) + + # Step 4: Recurse into the correct partition + if k <= len(low): + return median_of_medians(low, k) + elif k <= len(low) + len(equal): + return pivot + else: + return median_of_medians(high, k - len(low) - len(equal)) + + +if __name__ == "__main__": + import doctest + doctest.testmod() + + sample = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5] + print(f"Array: {sample}") + for i in range(1, len(sample) + 1): + print(f" {i}-th smallest: {median_of_medians(sample, i)}") \ No newline at end of file From b6f8f1610deb3903ab668150f531b60036f3f301 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 06:49:22 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- maths/median_of_medians.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/maths/median_of_medians.py b/maths/median_of_medians.py index 2bfd87d945f5..6a2ad0d6b05a 100644 --- a/maths/median_of_medians.py +++ b/maths/median_of_medians.py @@ -63,9 +63,10 @@ def median_of_medians(arr: list, k: int) -> int: if __name__ == "__main__": import doctest + doctest.testmod() - + sample = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5] print(f"Array: {sample}") for i in range(1, len(sample) + 1): - print(f" {i}-th smallest: {median_of_medians(sample, i)}") \ No newline at end of file + print(f" {i}-th smallest: {median_of_medians(sample, i)}") From 8f54ac182c02e6dcf0bbb8e7cb5c098e67e9538b Mon Sep 17 00:00:00 2001 From: shubh Date: Tue, 31 Mar 2026 12:20:49 +0530 Subject: [PATCH 3/5] fix: add doctest to partition and rename k to rank --- maths/median_of_medians.py | 63 ++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/maths/median_of_medians.py b/maths/median_of_medians.py index 6a2ad0d6b05a..5d10e1cd1020 100644 --- a/maths/median_of_medians.py +++ b/maths/median_of_medians.py @@ -5,68 +5,63 @@ """ -def partition(arr: list, pivot: int) -> tuple: - """Partition array into elements less than, equal to, and greater than pivot.""" +def partition(arr: list, pivot: int) -> tuple[list, list, list]: + """ + Partition array into elements less than, equal to, and greater than pivot. + + >>> partition([3, 1, 4, 1, 5], 3) + ([1, 1], [3], [4, 5]) + >>> partition([7, 7, 7], 7) + ([], [7, 7, 7], []) + """ low = [x for x in arr if x < pivot] - high = [x for x in arr if x > pivot] equal = [x for x in arr if x == pivot] + high = [x for x in arr if x > pivot] return low, equal, high -def median_of_medians(arr: list, k: int) -> int: +def median_of_medians(arr: list, rank: int) -> int: """ Find the k-th smallest element in an unsorted list using Median of Medians. Args: arr: List of comparable elements - k: 1-based index of the desired smallest element + rank: 1-based index of the desired smallest element Returns: The k-th smallest element in arr Raises: - ValueError: If k is out of range - - Examples: - >>> median_of_medians([3, 1, 4, 1, 5, 9, 2, 6], 3) - 2 - >>> median_of_medians([7, 2, 10, 5], 1) - 2 - >>> median_of_medians([1, 2, 3, 4, 5], 5) - 5 + ValueError: If rank is out of range + + >>> median_of_medians([3, 1, 4, 1, 5, 9, 2, 6], 3) + 2 + >>> median_of_medians([7, 2, 10, 5], 1) + 2 + >>> median_of_medians([1, 2, 3, 4, 5], 5) + 5 """ - if not 1 <= k <= len(arr): - raise ValueError(f"k={k} is out of range for array of length {len(arr)}") + if not 1 <= rank <= len(arr): + raise ValueError(f"rank={rank} is out of range for array of length {len(arr)}") - # Base case if len(arr) <= 5: - return sorted(arr)[k - 1] + return sorted(arr)[rank - 1] - # Step 1: Divide into chunks of 5 and find median of each chunk - chunks = [arr[i : i + 5] for i in range(0, len(arr), 5)] + chunks = [arr[i: i + 5] for i in range(0, len(arr), 5)] medians = [sorted(chunk)[len(chunk) // 2] for chunk in chunks] - # Step 2: Recursively find median of medians pivot = median_of_medians(medians, len(medians) // 2 + 1) - # Step 3: Partition around pivot low, equal, high = partition(arr, pivot) - # Step 4: Recurse into the correct partition - if k <= len(low): - return median_of_medians(low, k) - elif k <= len(low) + len(equal): + if rank <= len(low): + return median_of_medians(low, rank) + elif rank <= len(low) + len(equal): return pivot else: - return median_of_medians(high, k - len(low) - len(equal)) + return median_of_medians(high, rank - len(low) - len(equal)) if __name__ == "__main__": import doctest - - doctest.testmod() - - sample = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5] - print(f"Array: {sample}") - for i in range(1, len(sample) + 1): - print(f" {i}-th smallest: {median_of_medians(sample, i)}") + doctest.testmod() \ No newline at end of file From 4f4394989c02c250af444ae9d9f643910bb34b04 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 06:55:17 +0000 Subject: [PATCH 4/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- maths/median_of_medians.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/maths/median_of_medians.py b/maths/median_of_medians.py index 5d10e1cd1020..e801dfb06de3 100644 --- a/maths/median_of_medians.py +++ b/maths/median_of_medians.py @@ -47,7 +47,7 @@ def median_of_medians(arr: list, rank: int) -> int: if len(arr) <= 5: return sorted(arr)[rank - 1] - chunks = [arr[i: i + 5] for i in range(0, len(arr), 5)] + chunks = [arr[i : i + 5] for i in range(0, len(arr), 5)] medians = [sorted(chunk)[len(chunk) // 2] for chunk in chunks] pivot = median_of_medians(medians, len(medians) // 2 + 1) @@ -64,4 +64,5 @@ def median_of_medians(arr: list, rank: int) -> int: if __name__ == "__main__": import doctest - doctest.testmod() \ No newline at end of file + + doctest.testmod() From 226a501d21d4ec033fbe3f8299fd30581a73a07d Mon Sep 17 00:00:00 2001 From: shubh Date: Tue, 31 Mar 2026 14:03:15 +0530 Subject: [PATCH 5/5] fix: assign exception message to variable before raising --- maths/median_of_medians.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/maths/median_of_medians.py b/maths/median_of_medians.py index e801dfb06de3..734c47d953fd 100644 --- a/maths/median_of_medians.py +++ b/maths/median_of_medians.py @@ -42,7 +42,8 @@ def median_of_medians(arr: list, rank: int) -> int: 5 """ if not 1 <= rank <= len(arr): - raise ValueError(f"rank={rank} is out of range for array of length {len(arr)}") + msg = f"rank={rank} is out of range for array of length {len(arr)}" + raise ValueError(msg) if len(arr) <= 5: return sorted(arr)[rank - 1]