Skip to content

Commit e8af649

Browse files
Aayaan SinghAayaan Singh
authored andcommitted
Add library sort (gapped insertion sort)
1 parent f5eceed commit e8af649

1 file changed

Lines changed: 103 additions & 84 deletions

File tree

‎sorts/library_sort.py‎

Lines changed: 103 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,124 @@
1-
from typing import List
1+
"""
2+
A pure Python implementation of library sort (also called gapped insertion sort).
23
4+
It works like insertion sort, but leaves empty gaps in the array so a new item can
5+
be placed without shifting everything after it. When the gaps fill up, the array is
6+
rebalanced. Expected time is O(n log n).
37
4-
def library_sort(collection: List[int], reverse: bool = False) -> List[int]:
8+
Reference: https://en.wikipedia.org/wiki/Library_sort
9+
10+
For doctests run the following command:
11+
python3 -m doctest -v library_sort.py
12+
13+
For manual testing run:
14+
python3 library_sort.py
15+
"""
16+
17+
_EMPTY = object()
18+
19+
20+
def _spread(slots: list) -> None:
521
"""
6-
A pure Python implementation of library sort (also called gapped insertion sort).
22+
Redistribute the stored items evenly across all slots, keeping their order.
723
8-
It w orks like insertion sort, but leaves empty gaps in the array so a new item can
9-
be placed without shifting everything after it. When the gaps fill up, the array is
10-
rebalanced. Expected time is O(n log n).
24+
>>> slots = [3, 5, 7, _EMPTY, _EMPTY, _EMPTY]
25+
>>> _spread(slots)
26+
>>> [x if x is not _EMPTY else None for x in slots]
27+
[3, None, 5, None, 7, None]
28+
"""
29+
items = [x for x in slots if x is not _EMPTY]
30+
for i in range(len(slots)):
31+
slots[i] = _EMPTY
32+
for i, item in enumerate(items):
33+
slots[i * len(slots) // len(items)] = item
1134

12-
Reference: https://en.wikipedia.org/wiki/Library_sort
1335

14-
For doctests run the following command:
15-
python3 -m doctest -v library_sort.py
36+
def _insert(slots: list, item) -> None:
37+
"""
38+
Insert item into the gapped array, keeping the stored items in sorted order.
39+
40+
>>> slots = [1, _EMPTY, 5, _EMPTY]
41+
>>> _insert(slots, 3)
42+
>>> [x if x is not _EMPTY else None for x in slots]
43+
[1, 3, 5, None]
44+
"""
45+
size = len(slots)
46+
low, high = 0, size - 1
47+
# Binary search over the slots, skipping empty ones.
48+
while low <= high:
49+
mid = (low + high) // 2
50+
probe = mid
51+
while probe <= high and slots[probe] is _EMPTY:
52+
probe += 1
53+
if probe > high:
54+
high = mid - 1
55+
elif slots[probe] <= item:
56+
low = probe + 1
57+
else:
58+
high = mid - 1
59+
60+
# Every stored item before `low` is <= item, every one from `low` on is > item.
61+
if low < size and slots[low] is _EMPTY:
62+
slots[low] = item
63+
return
64+
65+
right = low
66+
while right < size and slots[right] is not _EMPTY:
67+
right += 1
68+
left = low - 1
69+
while left >= 0 and slots[left] is not _EMPTY:
70+
left -= 1
71+
72+
# Shift items towards the nearest gap to make room.
73+
if left < 0 or (right < size and right - low <= low - 1 - left):
74+
slots[low + 1 : right + 1] = slots[low:right]
75+
slots[low] = item
76+
else:
77+
slots[left : low - 1] = slots[left + 1 : low]
78+
slots[low - 1] = item
79+
80+
81+
def library_sort(collection: list) -> list:
82+
"""
83+
A pure Python implementation of library sort (also called gapped insertion sort).
84+
85+
:param collection: a collection of comparable items
86+
:return: a new list with the items in ascending order
1687
17-
For manual testing run:
18-
python3 library_sort.py
19-
Time Complexity: O(N log N) average, O(N^2) worst-case
20-
Space Complexity: O(N)
88+
Time complexity: O(n log n) on average, O(n^2) in the worst case.
89+
Space complexity: O(n)
2190
2291
Examples:
2392
>>> library_sort([5, 2, 9, 1, 5, 6])
2493
[1, 2, 5, 5, 6, 9]
25-
>>> library_sort([3, 1, 4, 1, 5, 9, 2])
26-
[1, 1, 2, 3, 4, 5, 9]
2794
>>> library_sort([])
2895
[]
2996
>>> library_sort([1])
3097
[1]
31-
>>> library_sort([5, 2, 9, 1], reverse=True)
32-
[9, 5, 2, 1]
98+
>>> library_sort([-2, 5, 0, -45])
99+
[-45, -2, 0, 5]
100+
>>> library_sort(["d", "a", "c", "b"])
101+
['a', 'b', 'c', 'd']
102+
>>> import random
103+
>>> data = [random.randint(-100, 100) for _ in range(200)]
104+
>>> library_sort(data) == sorted(data)
105+
True
33106
"""
34-
if not collection:
35-
return []
36-
37107
n = len(collection)
38-
gapped = [None] * (2 * n)
39-
gapped[0] = collection[0]
40-
41-
for i in range(1, n):
42-
val = collection[i]
43-
valid_indices = [idx for idx, x in enumerate(gapped) if x is not None]
44-
valid_vals = [gapped[idx] for idx in valid_indices]
45-
46-
low, high = 0, len(valid_vals) - 1
47-
pos = len(valid_vals)
48-
while low <= high:
49-
mid = (low + high) // 2
50-
if valid_vals[mid] >= val:
51-
pos = mid
52-
high = mid - 1
53-
else:
54-
low = mid + 1
55-
56-
if pos == len(valid_vals):
57-
target_idx = valid_indices[-1] + 1 if valid_indices else 0
58-
else:
59-
target_idx = valid_indices[pos]
108+
if n < 2:
109+
return list(collection)
60110

61-
if target_idx < len(gapped) and gapped[target_idx] is None:
62-
gapped[target_idx] = val
63-
else:
64-
current_elements = [x for x in gapped if x is not None]
65-
current_elements.append(val)
66-
current_elements.sort()
67-
68-
gapped = [None] * (2 * len(current_elements))
69-
step = len(gapped) // len(current_elements)
70-
for idx, elem in enumerate(current_elements):
71-
gapped[idx * step] = elem
72-
73-
sorted_arr = [x for x in gapped if x is not None]
74-
if reverse:
75-
sorted_arr.reverse()
76-
return sorted_arr
111+
slots: list = [_EMPTY] * (2 * n)
112+
next_spread = 1
113+
for count, item in enumerate(collection, start=1):
114+
_insert(slots, item)
115+
if count == next_spread:
116+
_spread(slots)
117+
next_spread *= 2
118+
return [x for x in slots if x is not _EMPTY]
77119

78120

79121
if __name__ == "__main__":
80-
import doctest
81-
82-
doctest.testmod()
83-
84-
print("\nLibrarySort Interactive Testing")
85-
print("=" * 40)
86-
87-
try:
88-
user_input = input("Enter numbers separated by a comma:\n").strip()
89-
if user_input == "":
90-
unsorted = []
91-
else:
92-
unsorted = [int(item.strip()) for item in user_input.split(",")]
93-
94-
print(f"\nOriginal: {unsorted}")
95-
sorted_list = library_sort(unsorted)
96-
print(f"Sorted: {sorted_list}")
97-
98-
# Test reverse
99-
sorted_reverse = library_sort(unsorted, reverse=True)
100-
print(f"Reverse: {sorted_reverse}")
101-
102-
except ValueError:
103-
print("Invalid input. Please enter valid integers separated by commas.")
104-
except KeyboardInterrupt:
105-
print("\n\nGoodbye!")
122+
user_input = input("Enter numbers separated by a comma:\n").strip()
123+
unsorted = [int(item) for item in user_input.split(",")]
124+
print(library_sort(unsorted))

0 commit comments

Comments
 (0)