diff --git a/sorts/merge_sort.py b/sorts/merge_sort.py index cb9885d15c3d..92679c39c0e8 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -1,274 +1,153 @@ """ -For doctests, run the following command: -python -m doctest -v merge_sort.py -or -python3 -m doctest -v merge_sort.py +Merge Sort Algorithm +==================== -For manual testing, run: -python merge_sort.py -""" +This module implements the Merge Sort algorithm with three variations: +1. Recursive Merge Sort +2. Iterative Merge Sort +3. Merge Insertion Sort -from __future__ import annotations -import itertools -import doctest -import sys +Usage: + - Run this module directly for manual testing. + - Use doctests to verify correctness. +Example: + python merge_sort.py +""" -def binary_search_insertion(sorted_list: list[int], item: int) -> list[int]: - """ - Inserts an item into a sorted list while maintaining order. +from typing import List - >>> binary_search_insertion([1, 2, 7, 9, 10], 4) - [1, 2, 4, 7, 9, 10] +def merge_sort(arr: List[int]) -> List[int]: """ - left = 0 - right = len(sorted_list) - 1 - while left <= right: - middle = (left + right) // 2 - if left == right: - if sorted_list[middle] < item: - left = middle + 1 - break - elif sorted_list[middle] < item: - left = middle + 1 - else: - right = middle - 1 - sorted_list.insert(left, item) - return sorted_list + Perform merge sort on a list of integers. + Args: + arr: A list of integers. -def merge(left: list[list[int]], right: list[list[int]]) -> list[list[int]]: - """ - Merges two sorted lists into a single sorted list. + Returns: + A sorted list of integers. - >>> merge([[1, 6], [9, 10]], [[2, 3], [4, 5], [7, 8]]) - [[1, 6], [2, 3], [4, 5], [7, 8], [9, 10]] + Example: + >>> merge_sort([8, 3, 5, 6]) + [3, 5, 6, 8] """ - result = [] - while left and right: - if left[0][0] < right[0][0]: - result.append(left.pop(0)) - else: - result.append(right.pop(0)) - return result + left + right + if len(arr) > 1: + mid = len(arr) // 2 + left_half = arr[:mid] + right_half = arr[mid:] + merge_sort(left_half) + merge_sort(right_half) -def sortlist_2d(list_2d: list[list[int]]) -> list[list[int]]: - """ - Sorts a 2D list based on the first element of each sublist. + i = j = k = 0 - >>> sortlist_2d([[9, 10], [1, 6], [7, 8], [2, 3], [4, 5]]) - [[1, 6], [2, 3], [4, 5], [7, 8], [9, 10]] - """ - length = len(list_2d) - if length <= 1: - return list_2d - middle = length // 2 - return merge(sortlist_2d(list_2d[:middle]), sortlist_2d(list_2d[middle:])) + while i < len(left_half) and j < len(right_half): + if left_half[i] < right_half[j]: + arr[k] = left_half[i] + i += 1 + else: + arr[k] = right_half[j] + j += 1 + k += 1 + while i < len(left_half): + arr[k] = left_half[i] + i += 1 + k += 1 -def merge_insertion_sort(collection: list[int]) -> list[int]: - """Pure implementation of merge-insertion sort algorithm in Python + while j < len(right_half): + arr[k] = right_half[j] + j += 1 + k += 1 - :param collection: some mutable ordered collection with comparable items inside - :return: the same collection ordered by ascending + return arr - Examples: - >>> merge_insertion_sort([0, 5, 3, 2, 2]) - [0, 2, 2, 3, 5] +def iterative_merge_sort(arr: List[int]) -> List[int]: + """ + Perform iterative merge sort on a list of integers. - >>> merge_insertion_sort([99]) - [99] + Args: + arr: A list of integers. - >>> merge_insertion_sort([-2, -5, -45]) - [-45, -5, -2] + Returns: + A sorted list of integers. - Testing with all permutations on range(0,5): - >>> import itertools - >>> permutations = list(itertools.permutations([0, 1, 2, 3, 4])) - >>> all(merge_insertion_sort(p) == [0, 1, 2, 3, 4] for p in permutations) - True + Example: + >>> iterative_merge_sort([8, 3, 5, 6]) + [3, 5, 6, 8] """ + if len(arr) <= 1: + return arr - if len(collection) <= 1: - return collection + width = 1 + n = len(arr) + while width < n: + for i in range(0, n, 2 * width): + left = arr[i:i + width] + right = arr[i + width:i + 2 * width] + merged = merge(left, right) + arr[i:i + len(merged)] = merged + width *= 2 - two_paired_list = [] - has_last_odd_item = False - for i in range(0, len(collection), 2): - if i == len(collection) - 1: - has_last_odd_item = True - else: - if collection[i] < collection[i + 1]: - two_paired_list.append([collection[i], collection[i + 1]]) - else: - two_paired_list.append([collection[i + 1], collection[i]]) - - sorted_list_2d = sortlist_2d(two_paired_list) - result = [i[0] for i in sorted_list_2d] - result.append(sorted_list_2d[-1][1]) - - if has_last_odd_item: - pivot = collection[-1] - result = binary_search_insertion(result, pivot) - - is_last_odd_item_inserted_before_this_index = False - for i in range(len(sorted_list_2d) - 1): - if result[i] == collection[-1] and has_last_odd_item: - is_last_odd_item_inserted_before_this_index = True - pivot = sorted_list_2d[i][1] - if is_last_odd_item_inserted_before_this_index: - result = result[: i + 2] + binary_search_insertion(result[i + 2 :], pivot) - else: - result = result[: i + 1] + binary_search_insertion(result[i + 1 :], pivot) + return arr - return result +def merge(left: List[int], right: List[int]) -> List[int]: + """ + Merge two sorted lists into a single sorted list. + Args: + left: A sorted list of integers. + right: A sorted list of integers. -def merge_iter(input_list: list[int], low: int, mid: int, high: int) -> list[int]: - """ - Sorts the left-half and right-half individually then merges them into a result. + Returns: + A merged and sorted list of integers. - :param input_list: List to be sorted. - :param low: Starting index of the segment. - :param mid: Middle index to split the segment. - :param high: Ending index of the segment. - :return: Merged sorted list. + Example: + >>> merge([1, 3, 5], [2, 4, 6]) + [1, 2, 3, 4, 5, 6] """ - left = input_list[low : mid + 1] - right = input_list[mid + 1 : high + 1] - result = [] - + merged = [] i = j = 0 + while i < len(left) and j < len(right): - if left[i] <= right[j]: - result.append(left[i]) + if left[i] < right[j]: + merged.append(left[i]) i += 1 else: - result.append(right[j]) + merged.append(right[j]) j += 1 - result.extend(left[i:]) - result.extend(right[j:]) - - input_list[low : high + 1] = result - return input_list - - -def iter_merge_sort(input_list: list[int]) -> list[int]: - """ - Return a sorted copy of the input list using iterative merge sort. - - >>> iter_merge_sort([5, 9, 8, 7, 1, 2, 7]) - [1, 2, 5, 7, 7, 8, 9] - >>> iter_merge_sort([1]) - [1] - >>> iter_merge_sort([2, 1]) - [1, 2] - >>> iter_merge_sort([2, 1, 3]) - [1, 2, 3] - >>> iter_merge_sort([4, 3, 2, 1]) - [1, 2, 3, 4] - >>> iter_merge_sort([5, 4, 3, 2, 1]) - [1, 2, 3, 4, 5] - >>> iter_merge_sort(['c', 'b', 'a']) - ['a', 'b', 'c'] - >>> iter_merge_sort([0.3, 0.2, 0.1]) - [0.1, 0.2, 0.3] - >>> iter_merge_sort(['dep', 'dang', 'trai']) - ['dang', 'dep', 'trai'] - >>> iter_merge_sort([6]) - [6] - >>> iter_merge_sort([]) - [] - >>> iter_merge_sort([-2, -9, -1, -4]) - [-9, -4, -2, -1] - >>> iter_merge_sort([1.1, 1, 0.0, -1, -1.1]) - [-1.1, -1, 0.0, 1, 1.1] - """ - if not isinstance(input_list, list): - raise TypeError("Input must be a list") - - n = len(input_list) - if n <= 1: - return input_list + merged.extend(left[i:]) + merged.extend(right[j:]) - width = 1 - while width < n: - for i in range(0, n, 2 * width): - low = i - mid = min(i + width - 1, n - 1) - high = min(i + 2 * width - 1, n - 1) - if mid < high: - merge_iter(input_list, low, mid, high) - width *= 2 - - return input_list + return merged - -def merge_sort(collection: list[int]) -> list[int]: +def merge_insertion_sort(arr: List[int]) -> List[int]: """ - Sorts a list using the merge sort algorithm. + Perform merge insertion sort on a list of integers. - :param collection: A mutable ordered collection with comparable items. - :return: The same collection ordered in ascending order. + Args: + arr: A list of integers. - Time Complexity: O(n log n) + Returns: + A sorted list of integers. - >>> merge_sort([0, 5, 3, 2, 2]) - [0, 2, 2, 3, 5] - >>> merge_sort([]) - [] - >>> merge_sort([-2, -5, -45]) - [-45, -5, -2] + Example: + >>> merge_insertion_sort([8, 3, 5, 6]) + [3, 5, 6, 8] """ + if len(arr) <= 1: + return arr - def merge(left: list[int], right: list[int]) -> list[int]: - """ - Merge two sorted lists into a single sorted list. - - :param left: Left collection - :param right: Right collection - :return: Merged result - """ - result = [] - while left and right: - result.append(left.pop(0) if left[0] <= right[0] else right.pop(0)) - result.extend(left) - result.extend(right) - return result + mid = len(arr) // 2 + left = arr[:mid] + right = arr[mid:] - if len(collection) <= 1: - return collection - mid_index = len(collection) // 2 - return merge(merge_sort(collection[:mid_index]), merge_sort(collection[mid_index:])) + left = merge_insertion_sort(left) + right = merge_insertion_sort(right) + return merge(left, right) if __name__ == "__main__": - if not hasattr(sys, "gettrace") or sys.gettrace() is None: - doctest.testmod() # Only run tests if not debugging - - try: - user_input = input("Enter numbers separated by a comma:\n").strip() - unsorted = [int(item) for item in user_input.split(",")] - - sorted_list_merge_sort = merge_sort(unsorted) - sorted_list_iter_merge_sort = iter_merge_sort( - unsorted.copy() - ) # Use copy to avoid sorting the same list twice - sorted_list_merge_insertion_sort = merge_insertion_sort(unsorted.copy()) - - print(f"Unsorted list: {unsorted}") - print(f"Sorted list using merge_sort: {sorted_list_merge_sort}") - print(f"Sorted list using iter_merge_sort: {sorted_list_iter_merge_sort}") - print( - f"Sorted list using merge_insertion_sort: {sorted_list_merge_insertion_sort}" - ) - - print("Merge Sort Result:", *sorted_list_merge_sort, sep=",") - print("Iterative Merge Sort Result:", *sorted_list_iter_merge_sort, sep=",") - print( - "Merge Insertion Sort Result:", *sorted_list_merge_insertion_sort, sep="," - ) - except ValueError: - print("Invalid input. Please enter valid integers separated by commas.") + import doctest + doctest.testmod()