|
1 | 1 | """
|
2 |
| -For doctests, run the following command: |
3 |
| -python -m doctest -v merge_sort.py |
4 |
| -or |
5 |
| -python3 -m doctest -v merge_sort.py |
| 2 | +Merge Sort Algorithm |
| 3 | +==================== |
6 | 4 |
|
7 |
| -For manual testing, run: |
8 |
| -python merge_sort.py |
9 |
| -""" |
| 5 | +This module implements the Merge Sort algorithm with three variations: |
| 6 | +1. Recursive Merge Sort |
| 7 | +2. Iterative Merge Sort |
| 8 | +3. Merge Insertion Sort |
10 | 9 |
|
11 |
| -from __future__ import annotations |
12 |
| -import itertools |
13 |
| -import doctest |
14 |
| -import sys |
| 10 | +Usage: |
| 11 | + - Run this module directly for manual testing. |
| 12 | + - Use doctests to verify correctness. |
15 | 13 |
|
| 14 | +Example: |
| 15 | + python merge_sort.py |
| 16 | +""" |
16 | 17 |
|
17 |
| -def binary_search_insertion(sorted_list: list[int], item: int) -> list[int]: |
18 |
| - """ |
19 |
| - Inserts an item into a sorted list while maintaining order. |
| 18 | +from typing import List |
20 | 19 |
|
21 |
| - >>> binary_search_insertion([1, 2, 7, 9, 10], 4) |
22 |
| - [1, 2, 4, 7, 9, 10] |
| 20 | +def merge_sort(arr: List[int]) -> List[int]: |
23 | 21 | """
|
24 |
| - left = 0 |
25 |
| - right = len(sorted_list) - 1 |
26 |
| - while left <= right: |
27 |
| - middle = (left + right) // 2 |
28 |
| - if left == right: |
29 |
| - if sorted_list[middle] < item: |
30 |
| - left = middle + 1 |
31 |
| - break |
32 |
| - elif sorted_list[middle] < item: |
33 |
| - left = middle + 1 |
34 |
| - else: |
35 |
| - right = middle - 1 |
36 |
| - sorted_list.insert(left, item) |
37 |
| - return sorted_list |
| 22 | + Perform merge sort on a list of integers. |
38 | 23 |
|
| 24 | + Args: |
| 25 | + arr: A list of integers. |
39 | 26 |
|
40 |
| -def merge(left: list[list[int]], right: list[list[int]]) -> list[list[int]]: |
41 |
| - """ |
42 |
| - Merges two sorted lists into a single sorted list. |
| 27 | + Returns: |
| 28 | + A sorted list of integers. |
43 | 29 |
|
44 |
| - >>> merge([[1, 6], [9, 10]], [[2, 3], [4, 5], [7, 8]]) |
45 |
| - [[1, 6], [2, 3], [4, 5], [7, 8], [9, 10]] |
| 30 | + Example: |
| 31 | + >>> merge_sort([8, 3, 5, 6]) |
| 32 | + [3, 5, 6, 8] |
46 | 33 | """
|
47 |
| - result = [] |
48 |
| - while left and right: |
49 |
| - if left[0][0] < right[0][0]: |
50 |
| - result.append(left.pop(0)) |
51 |
| - else: |
52 |
| - result.append(right.pop(0)) |
53 |
| - return result + left + right |
| 34 | + if len(arr) > 1: |
| 35 | + mid = len(arr) // 2 |
| 36 | + left_half = arr[:mid] |
| 37 | + right_half = arr[mid:] |
54 | 38 |
|
| 39 | + merge_sort(left_half) |
| 40 | + merge_sort(right_half) |
55 | 41 |
|
56 |
| -def sortlist_2d(list_2d: list[list[int]]) -> list[list[int]]: |
57 |
| - """ |
58 |
| - Sorts a 2D list based on the first element of each sublist. |
| 42 | + i = j = k = 0 |
59 | 43 |
|
60 |
| - >>> sortlist_2d([[9, 10], [1, 6], [7, 8], [2, 3], [4, 5]]) |
61 |
| - [[1, 6], [2, 3], [4, 5], [7, 8], [9, 10]] |
62 |
| - """ |
63 |
| - length = len(list_2d) |
64 |
| - if length <= 1: |
65 |
| - return list_2d |
66 |
| - middle = length // 2 |
67 |
| - return merge(sortlist_2d(list_2d[:middle]), sortlist_2d(list_2d[middle:])) |
| 44 | + while i < len(left_half) and j < len(right_half): |
| 45 | + if left_half[i] < right_half[j]: |
| 46 | + arr[k] = left_half[i] |
| 47 | + i += 1 |
| 48 | + else: |
| 49 | + arr[k] = right_half[j] |
| 50 | + j += 1 |
| 51 | + k += 1 |
68 | 52 |
|
| 53 | + while i < len(left_half): |
| 54 | + arr[k] = left_half[i] |
| 55 | + i += 1 |
| 56 | + k += 1 |
69 | 57 |
|
70 |
| -def merge_insertion_sort(collection: list[int]) -> list[int]: |
71 |
| - """Pure implementation of merge-insertion sort algorithm in Python |
| 58 | + while j < len(right_half): |
| 59 | + arr[k] = right_half[j] |
| 60 | + j += 1 |
| 61 | + k += 1 |
72 | 62 |
|
73 |
| - :param collection: some mutable ordered collection with comparable items inside |
74 |
| - :return: the same collection ordered by ascending |
| 63 | + return arr |
75 | 64 |
|
76 |
| - Examples: |
77 |
| - >>> merge_insertion_sort([0, 5, 3, 2, 2]) |
78 |
| - [0, 2, 2, 3, 5] |
| 65 | +def iterative_merge_sort(arr: List[int]) -> List[int]: |
| 66 | + """ |
| 67 | + Perform iterative merge sort on a list of integers. |
79 | 68 |
|
80 |
| - >>> merge_insertion_sort([99]) |
81 |
| - [99] |
| 69 | + Args: |
| 70 | + arr: A list of integers. |
82 | 71 |
|
83 |
| - >>> merge_insertion_sort([-2, -5, -45]) |
84 |
| - [-45, -5, -2] |
| 72 | + Returns: |
| 73 | + A sorted list of integers. |
85 | 74 |
|
86 |
| - Testing with all permutations on range(0,5): |
87 |
| - >>> import itertools |
88 |
| - >>> permutations = list(itertools.permutations([0, 1, 2, 3, 4])) |
89 |
| - >>> all(merge_insertion_sort(p) == [0, 1, 2, 3, 4] for p in permutations) |
90 |
| - True |
| 75 | + Example: |
| 76 | + >>> iterative_merge_sort([8, 3, 5, 6]) |
| 77 | + [3, 5, 6, 8] |
91 | 78 | """
|
| 79 | + if len(arr) <= 1: |
| 80 | + return arr |
92 | 81 |
|
93 |
| - if len(collection) <= 1: |
94 |
| - return collection |
| 82 | + width = 1 |
| 83 | + n = len(arr) |
| 84 | + while width < n: |
| 85 | + for i in range(0, n, 2 * width): |
| 86 | + left = arr[i:i + width] |
| 87 | + right = arr[i + width:i + 2 * width] |
| 88 | + merged = merge(left, right) |
| 89 | + arr[i:i + len(merged)] = merged |
| 90 | + width *= 2 |
95 | 91 |
|
96 |
| - two_paired_list = [] |
97 |
| - has_last_odd_item = False |
98 |
| - for i in range(0, len(collection), 2): |
99 |
| - if i == len(collection) - 1: |
100 |
| - has_last_odd_item = True |
101 |
| - else: |
102 |
| - if collection[i] < collection[i + 1]: |
103 |
| - two_paired_list.append([collection[i], collection[i + 1]]) |
104 |
| - else: |
105 |
| - two_paired_list.append([collection[i + 1], collection[i]]) |
106 |
| - |
107 |
| - sorted_list_2d = sortlist_2d(two_paired_list) |
108 |
| - result = [i[0] for i in sorted_list_2d] |
109 |
| - result.append(sorted_list_2d[-1][1]) |
110 |
| - |
111 |
| - if has_last_odd_item: |
112 |
| - pivot = collection[-1] |
113 |
| - result = binary_search_insertion(result, pivot) |
114 |
| - |
115 |
| - is_last_odd_item_inserted_before_this_index = False |
116 |
| - for i in range(len(sorted_list_2d) - 1): |
117 |
| - if result[i] == collection[-1] and has_last_odd_item: |
118 |
| - is_last_odd_item_inserted_before_this_index = True |
119 |
| - pivot = sorted_list_2d[i][1] |
120 |
| - if is_last_odd_item_inserted_before_this_index: |
121 |
| - result = result[: i + 2] + binary_search_insertion(result[i + 2 :], pivot) |
122 |
| - else: |
123 |
| - result = result[: i + 1] + binary_search_insertion(result[i + 1 :], pivot) |
| 92 | + return arr |
124 | 93 |
|
125 |
| - return result |
| 94 | +def merge(left: List[int], right: List[int]) -> List[int]: |
| 95 | + """ |
| 96 | + Merge two sorted lists into a single sorted list. |
126 | 97 |
|
| 98 | + Args: |
| 99 | + left: A sorted list of integers. |
| 100 | + right: A sorted list of integers. |
127 | 101 |
|
128 |
| -def merge_iter(input_list: list[int], low: int, mid: int, high: int) -> list[int]: |
129 |
| - """ |
130 |
| - Sorts the left-half and right-half individually then merges them into a result. |
| 102 | + Returns: |
| 103 | + A merged and sorted list of integers. |
131 | 104 |
|
132 |
| - :param input_list: List to be sorted. |
133 |
| - :param low: Starting index of the segment. |
134 |
| - :param mid: Middle index to split the segment. |
135 |
| - :param high: Ending index of the segment. |
136 |
| - :return: Merged sorted list. |
| 105 | + Example: |
| 106 | + >>> merge([1, 3, 5], [2, 4, 6]) |
| 107 | + [1, 2, 3, 4, 5, 6] |
137 | 108 | """
|
138 |
| - left = input_list[low : mid + 1] |
139 |
| - right = input_list[mid + 1 : high + 1] |
140 |
| - result = [] |
141 |
| - |
| 109 | + merged = [] |
142 | 110 | i = j = 0
|
| 111 | + |
143 | 112 | while i < len(left) and j < len(right):
|
144 |
| - if left[i] <= right[j]: |
145 |
| - result.append(left[i]) |
| 113 | + if left[i] < right[j]: |
| 114 | + merged.append(left[i]) |
146 | 115 | i += 1
|
147 | 116 | else:
|
148 |
| - result.append(right[j]) |
| 117 | + merged.append(right[j]) |
149 | 118 | j += 1
|
150 | 119 |
|
151 |
| - result.extend(left[i:]) |
152 |
| - result.extend(right[j:]) |
153 |
| - |
154 |
| - input_list[low : high + 1] = result |
155 |
| - return input_list |
156 |
| - |
157 |
| - |
158 |
| -def iter_merge_sort(input_list: list[int]) -> list[int]: |
159 |
| - """ |
160 |
| - Return a sorted copy of the input list using iterative merge sort. |
161 |
| -
|
162 |
| - >>> iter_merge_sort([5, 9, 8, 7, 1, 2, 7]) |
163 |
| - [1, 2, 5, 7, 7, 8, 9] |
164 |
| - >>> iter_merge_sort([1]) |
165 |
| - [1] |
166 |
| - >>> iter_merge_sort([2, 1]) |
167 |
| - [1, 2] |
168 |
| - >>> iter_merge_sort([2, 1, 3]) |
169 |
| - [1, 2, 3] |
170 |
| - >>> iter_merge_sort([4, 3, 2, 1]) |
171 |
| - [1, 2, 3, 4] |
172 |
| - >>> iter_merge_sort([5, 4, 3, 2, 1]) |
173 |
| - [1, 2, 3, 4, 5] |
174 |
| - >>> iter_merge_sort(['c', 'b', 'a']) |
175 |
| - ['a', 'b', 'c'] |
176 |
| - >>> iter_merge_sort([0.3, 0.2, 0.1]) |
177 |
| - [0.1, 0.2, 0.3] |
178 |
| - >>> iter_merge_sort(['dep', 'dang', 'trai']) |
179 |
| - ['dang', 'dep', 'trai'] |
180 |
| - >>> iter_merge_sort([6]) |
181 |
| - [6] |
182 |
| - >>> iter_merge_sort([]) |
183 |
| - [] |
184 |
| - >>> iter_merge_sort([-2, -9, -1, -4]) |
185 |
| - [-9, -4, -2, -1] |
186 |
| - >>> iter_merge_sort([1.1, 1, 0.0, -1, -1.1]) |
187 |
| - [-1.1, -1, 0.0, 1, 1.1] |
188 |
| - """ |
189 |
| - if not isinstance(input_list, list): |
190 |
| - raise TypeError("Input must be a list") |
191 |
| - |
192 |
| - n = len(input_list) |
193 |
| - if n <= 1: |
194 |
| - return input_list |
| 120 | + merged.extend(left[i:]) |
| 121 | + merged.extend(right[j:]) |
195 | 122 |
|
196 |
| - width = 1 |
197 |
| - while width < n: |
198 |
| - for i in range(0, n, 2 * width): |
199 |
| - low = i |
200 |
| - mid = min(i + width - 1, n - 1) |
201 |
| - high = min(i + 2 * width - 1, n - 1) |
202 |
| - if mid < high: |
203 |
| - merge_iter(input_list, low, mid, high) |
204 |
| - width *= 2 |
205 |
| - |
206 |
| - return input_list |
| 123 | + return merged |
207 | 124 |
|
208 |
| - |
209 |
| -def merge_sort(collection: list[int]) -> list[int]: |
| 125 | +def merge_insertion_sort(arr: List[int]) -> List[int]: |
210 | 126 | """
|
211 |
| - Sorts a list using the merge sort algorithm. |
| 127 | + Perform merge insertion sort on a list of integers. |
212 | 128 |
|
213 |
| - :param collection: A mutable ordered collection with comparable items. |
214 |
| - :return: The same collection ordered in ascending order. |
| 129 | + Args: |
| 130 | + arr: A list of integers. |
215 | 131 |
|
216 |
| - Time Complexity: O(n log n) |
| 132 | + Returns: |
| 133 | + A sorted list of integers. |
217 | 134 |
|
218 |
| - >>> merge_sort([0, 5, 3, 2, 2]) |
219 |
| - [0, 2, 2, 3, 5] |
220 |
| - >>> merge_sort([]) |
221 |
| - [] |
222 |
| - >>> merge_sort([-2, -5, -45]) |
223 |
| - [-45, -5, -2] |
| 135 | + Example: |
| 136 | + >>> merge_insertion_sort([8, 3, 5, 6]) |
| 137 | + [3, 5, 6, 8] |
224 | 138 | """
|
| 139 | + if len(arr) <= 1: |
| 140 | + return arr |
225 | 141 |
|
226 |
| - def merge(left: list[int], right: list[int]) -> list[int]: |
227 |
| - """ |
228 |
| - Merge two sorted lists into a single sorted list. |
229 |
| -
|
230 |
| - :param left: Left collection |
231 |
| - :param right: Right collection |
232 |
| - :return: Merged result |
233 |
| - """ |
234 |
| - result = [] |
235 |
| - while left and right: |
236 |
| - result.append(left.pop(0) if left[0] <= right[0] else right.pop(0)) |
237 |
| - result.extend(left) |
238 |
| - result.extend(right) |
239 |
| - return result |
| 142 | + mid = len(arr) // 2 |
| 143 | + left = arr[:mid] |
| 144 | + right = arr[mid:] |
240 | 145 |
|
241 |
| - if len(collection) <= 1: |
242 |
| - return collection |
243 |
| - mid_index = len(collection) // 2 |
244 |
| - return merge(merge_sort(collection[:mid_index]), merge_sort(collection[mid_index:])) |
| 146 | + left = merge_insertion_sort(left) |
| 147 | + right = merge_insertion_sort(right) |
245 | 148 |
|
| 149 | + return merge(left, right) |
246 | 150 |
|
247 | 151 | if __name__ == "__main__":
|
248 |
| - if not hasattr(sys, "gettrace") or sys.gettrace() is None: |
249 |
| - doctest.testmod() # Only run tests if not debugging |
250 |
| - |
251 |
| - try: |
252 |
| - user_input = input("Enter numbers separated by a comma:\n").strip() |
253 |
| - unsorted = [int(item) for item in user_input.split(",")] |
254 |
| - |
255 |
| - sorted_list_merge_sort = merge_sort(unsorted) |
256 |
| - sorted_list_iter_merge_sort = iter_merge_sort( |
257 |
| - unsorted.copy() |
258 |
| - ) # Use copy to avoid sorting the same list twice |
259 |
| - sorted_list_merge_insertion_sort = merge_insertion_sort(unsorted.copy()) |
260 |
| - |
261 |
| - print(f"Unsorted list: {unsorted}") |
262 |
| - print(f"Sorted list using merge_sort: {sorted_list_merge_sort}") |
263 |
| - print(f"Sorted list using iter_merge_sort: {sorted_list_iter_merge_sort}") |
264 |
| - print( |
265 |
| - f"Sorted list using merge_insertion_sort: {sorted_list_merge_insertion_sort}" |
266 |
| - ) |
267 |
| - |
268 |
| - print("Merge Sort Result:", *sorted_list_merge_sort, sep=",") |
269 |
| - print("Iterative Merge Sort Result:", *sorted_list_iter_merge_sort, sep=",") |
270 |
| - print( |
271 |
| - "Merge Insertion Sort Result:", *sorted_list_merge_insertion_sort, sep="," |
272 |
| - ) |
273 |
| - except ValueError: |
274 |
| - print("Invalid input. Please enter valid integers separated by commas.") |
| 152 | + import doctest |
| 153 | + doctest.testmod() |
0 commit comments