|
| 1 | +""" |
| 2 | +Reversort is a sorting algorithm described in Google Code Jam 2021 Qualification Round. |
| 3 | +
|
| 4 | +Algorithm: |
| 5 | +1. For i from 1 to N-1: |
| 6 | + a. Find the position j of the minimum element in the subarray from position i to N |
| 7 | + b. Reverse the subarray from position i to j |
| 8 | +
|
| 9 | +Time Complexity: O(n²) - For each position, we find the minimum and reverse |
| 10 | +Space Complexity: O(n) - Due to list slicing in Python |
| 11 | + (can be O(1) with in-place reversal) |
| 12 | +
|
| 13 | +For doctests run following command: |
| 14 | +python3 -m doctest -v reversort.py |
| 15 | +
|
| 16 | +For manual testing run: |
| 17 | +python reversort.py |
| 18 | +""" |
| 19 | + |
| 20 | +from typing import Any |
| 21 | + |
| 22 | + |
| 23 | +def reversort(collection: list[Any]) -> list[Any]: |
| 24 | + """ |
| 25 | + Sort a list using the Reversort algorithm. |
| 26 | +
|
| 27 | + Reversort works by repeatedly finding the minimum element in the unsorted |
| 28 | + portion and reversing the subarray from the current position to where the |
| 29 | + minimum element is located. |
| 30 | +
|
| 31 | + :param collection: A mutable ordered collection with comparable items |
| 32 | + :return: The sorted collection in ascending order |
| 33 | +
|
| 34 | + Examples: |
| 35 | + >>> reversort([4, 2, 1, 3]) |
| 36 | + [1, 2, 3, 4] |
| 37 | + >>> reversort([0, 5, 3, 2, 2]) |
| 38 | + [0, 2, 2, 3, 5] |
| 39 | + >>> reversort([]) |
| 40 | + [] |
| 41 | + >>> reversort([-2, -5, -45]) |
| 42 | + [-45, -5, -2] |
| 43 | + >>> reversort([1]) |
| 44 | + [1] |
| 45 | + >>> reversort([5, 4, 3, 2, 1]) |
| 46 | + [1, 2, 3, 4, 5] |
| 47 | + >>> reversort([2, 1, 4, 3]) |
| 48 | + [1, 2, 3, 4] |
| 49 | + >>> reversort([-23, 0, 6, -4, 34]) |
| 50 | + [-23, -4, 0, 6, 34] |
| 51 | + >>> reversort([1, 2, 3, 4]) |
| 52 | + [1, 2, 3, 4] |
| 53 | + >>> reversort([3, 3, 3, 3]) |
| 54 | + [3, 3, 3, 3] |
| 55 | + >>> reversort([56]) |
| 56 | + [56] |
| 57 | + >>> reversort([0, 5, 2, 3, 2]) == sorted([0, 5, 2, 3, 2]) |
| 58 | + True |
| 59 | + >>> reversort([]) == sorted([]) |
| 60 | + True |
| 61 | + >>> reversort([-2, -45, -5]) == sorted([-2, -45, -5]) |
| 62 | + True |
| 63 | + >>> reversort([-23, 0, 6, -4, 34]) == sorted([-23, 0, 6, -4, 34]) |
| 64 | + True |
| 65 | + >>> reversort(['d', 'a', 'b', 'e']) == sorted(['d', 'a', 'b', 'e']) |
| 66 | + True |
| 67 | + >>> reversort(['z', 'a', 'y', 'b', 'x', 'c']) |
| 68 | + ['a', 'b', 'c', 'x', 'y', 'z'] |
| 69 | + >>> reversort([1.1, 3.3, 5.5, 7.7, 2.2, 4.4, 6.6]) |
| 70 | + [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7] |
| 71 | + >>> reversort([1, 3.3, 5, 7.7, 2, 4.4, 6]) |
| 72 | + [1, 2, 3.3, 4.4, 5, 6, 7.7] |
| 73 | + >>> import random |
| 74 | + >>> collection_arg = random.sample(range(-50, 50), 100) |
| 75 | + >>> reversort(collection_arg) == sorted(collection_arg) |
| 76 | + True |
| 77 | + >>> import string |
| 78 | + >>> collection_arg = random.choices(string.ascii_letters + string.digits, k=100) |
| 79 | + >>> reversort(collection_arg) == sorted(collection_arg) |
| 80 | + True |
| 81 | + """ |
| 82 | + arr = collection[:] # Create a copy to avoid modifying the original |
| 83 | + n = len(arr) |
| 84 | + |
| 85 | + for i in range(n - 1): |
| 86 | + # Find the position of the minimum element in arr[i:] |
| 87 | + min_index = i |
| 88 | + for j in range(i + 1, n): |
| 89 | + if arr[j] < arr[min_index]: |
| 90 | + min_index = j |
| 91 | + |
| 92 | + # Reverse the subarray from position i to min_index |
| 93 | + if min_index != i: |
| 94 | + arr[i:min_index + 1] = arr[i:min_index + 1][::-1] |
| 95 | + |
| 96 | + return arr |
| 97 | + |
| 98 | + |
| 99 | +def reversort_cost(collection: list[Any]) -> int: |
| 100 | + """ |
| 101 | + Calculate the cost of sorting using Reversort. |
| 102 | +
|
| 103 | + The cost is defined as the sum of the lengths of all reversed segments. |
| 104 | + This is based on the Google Code Jam 2021 problem. |
| 105 | +
|
| 106 | + :param collection: A mutable ordered collection with comparable items |
| 107 | + :return: The total cost of sorting |
| 108 | +
|
| 109 | + Examples: |
| 110 | + >>> reversort_cost([4, 2, 1, 3]) |
| 111 | + 6 |
| 112 | + >>> reversort_cost([1, 2]) |
| 113 | + 1 |
| 114 | + >>> reversort_cost([7, 6, 5, 4, 3, 2, 1]) |
| 115 | + 12 |
| 116 | + >>> reversort_cost([1, 2, 3, 4]) |
| 117 | + 3 |
| 118 | + >>> reversort_cost([1]) |
| 119 | + 0 |
| 120 | + >>> reversort_cost([]) |
| 121 | + 0 |
| 122 | + """ |
| 123 | + arr = collection[:] # Create a copy to avoid modifying the original |
| 124 | + n = len(arr) |
| 125 | + total_cost = 0 |
| 126 | + |
| 127 | + for i in range(n - 1): |
| 128 | + # Find the position of the minimum element in arr[i:] |
| 129 | + min_index = i |
| 130 | + for j in range(i + 1, n): |
| 131 | + if arr[j] < arr[min_index]: |
| 132 | + min_index = j |
| 133 | + |
| 134 | + # Reverse the subarray from position i to min_index |
| 135 | + arr[i:min_index + 1] = arr[i:min_index + 1][::-1] |
| 136 | + |
| 137 | + # Cost is the length of the reversed segment |
| 138 | + total_cost += min_index - i + 1 |
| 139 | + |
| 140 | + return total_cost |
| 141 | + |
| 142 | + |
| 143 | +if __name__ == "__main__": |
| 144 | + import doctest |
| 145 | + from random import sample |
| 146 | + from timeit import timeit |
| 147 | + |
| 148 | + doctest.testmod() |
| 149 | + |
| 150 | + user_input = input("Enter numbers separated by a comma:\n").strip() |
| 151 | + unsorted = [int(item) for item in user_input.split(",")] |
| 152 | + print(f"Sorted list: {reversort(unsorted)}") |
| 153 | + print(f"Sort cost: {reversort_cost(unsorted)}") |
| 154 | + |
| 155 | + # Benchmark |
| 156 | + num_runs = 1000 |
| 157 | + test_arr = sample(range(-50, 50), 100) |
| 158 | + timer = timeit("reversort(test_arr[:])", globals=globals(), number=num_runs) |
| 159 | + print(f"\nProcessing time: {timer:.5f}s for {num_runs:,} runs on 100 elements") |
0 commit comments