forked from colonelpanic/dotfiles
Rotated array problem.
This commit is contained in:
parent
9418c8afe8
commit
fba727783c
91
dotfiles/lib/python/rotated_array.py
Normal file
91
dotfiles/lib/python/rotated_array.py
Normal file
@ -0,0 +1,91 @@
|
||||
def rotate_array(incoming, rotation_index):
|
||||
new_back = incoming[:rotation_index]
|
||||
new_front = incoming[rotation_index:]
|
||||
new_front.extend(new_back)
|
||||
return new_front
|
||||
|
||||
|
||||
def binary_search(
|
||||
array, item, low=0, high=None,
|
||||
lower_predicate=lambda item, array, index, low, high: item <= array[index]
|
||||
):
|
||||
if low < 0:
|
||||
raise ValueError('lo must be non-negative')
|
||||
if high is None:
|
||||
high = len(array)
|
||||
while low < high:
|
||||
mid = (low + high)//2
|
||||
if lower_predicate(item, array, mid, low, high):
|
||||
high = mid
|
||||
else:
|
||||
low = mid + 1
|
||||
return low
|
||||
|
||||
|
||||
class RotatedArrayProxy(object):
|
||||
|
||||
def __init__(self, incoming):
|
||||
self.incoming = incoming
|
||||
self._rotation_index = None
|
||||
if incoming:
|
||||
# Duplicates can not span the rotation
|
||||
assert incoming[0] != incoming[-1]
|
||||
|
||||
def __getitem__(self, item):
|
||||
if not isinstance(item, slice):
|
||||
return self.incoming[self._actual_index(item)]
|
||||
else:
|
||||
self._handle_slice(item)
|
||||
|
||||
def _handle_slice(self, item):
|
||||
start = self._actual_index(item.start)
|
||||
stop = self._actual_index(item.stop)
|
||||
if start > stop:
|
||||
sliced = self.incoming[start::item.stride]
|
||||
# Ensure that the stride is preserved as it passes through
|
||||
# the rotation.
|
||||
start_index = (len(self.incoming) - start) % (item.stride or 1)
|
||||
if start_index <= stop:
|
||||
sliced.extend(
|
||||
self.incoming[start_index:stop:item.stride]
|
||||
)
|
||||
return sliced
|
||||
else:
|
||||
return self.incoming[start:stop:item.stride]
|
||||
|
||||
def _actual_index(self, index):
|
||||
if index is None:
|
||||
return index
|
||||
elif 0 <= index < len(self.incoming):
|
||||
return (index + self.rotation_index) % len(self.incoming)
|
||||
elif index == len(self.incoming):
|
||||
return self.rotation_index
|
||||
else:
|
||||
raise Exception()
|
||||
|
||||
@property
|
||||
def rotation_index(self):
|
||||
if self._rotation_index is None:
|
||||
self._rotation_index = self._find_rotation_index()
|
||||
return self._rotation_index
|
||||
|
||||
def _find_lower_predicate(self, item, array, index, low, high):
|
||||
return array[0] > array[index]
|
||||
|
||||
def _find_rotation_index(self):
|
||||
if len(self.incoming) < 1:
|
||||
return 0
|
||||
return binary_search(self.incoming, self.incoming[0],
|
||||
lower_predicate=self._find_lower_predicate)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.incoming)
|
||||
|
||||
def sorted_insertion_index(self, x):
|
||||
return binary_search(self, x)
|
||||
|
||||
def actual_insertion_index(self, x):
|
||||
return self._actual_index(self.sorted_insertion_index(x))
|
||||
|
||||
def unrotated(self):
|
||||
return rotate_array(self.incoming, self.rotation_index)
|
69
dotfiles/lib/python/rotated_array_proxy_test.py
Normal file
69
dotfiles/lib/python/rotated_array_proxy_test.py
Normal file
@ -0,0 +1,69 @@
|
||||
import rotated_array
|
||||
|
||||
# duplicates, slicing with stride, insertion index for item greater than everything for completely sorted array
|
||||
def test_empty_rotated_array_proxy():
|
||||
empty_rap = rotated_array.RotatedArrayProxy([])
|
||||
assert empty_rap.rotation_index == 0
|
||||
assert empty_rap.unrotated() == []
|
||||
assert empty_rap.sorted_insertion_index(100) == 0
|
||||
assert empty_rap.actual_insertion_index(100) == 0
|
||||
|
||||
|
||||
def test_inserting_at_end_of_insertion_range():
|
||||
rap = rotated_array.RotatedArrayProxy([3, 4, 5, 0, 2])
|
||||
assert rap.rotation_index == 3
|
||||
assert rap.unrotated() == [0, 2, 3, 4, 5]
|
||||
|
||||
assert rap.sorted_insertion_index(-1) == 0
|
||||
assert rap.actual_insertion_index(-1) == 3
|
||||
|
||||
assert rap.sorted_insertion_index(1) == 1
|
||||
assert rap.actual_insertion_index(1) == 4
|
||||
|
||||
assert rap.sorted_insertion_index(2) in [1, 2]
|
||||
assert rap.actual_insertion_index(2) in [4, 5]
|
||||
|
||||
assert rap.sorted_insertion_index(3) in [2, 3]
|
||||
assert rap.actual_insertion_index(3) in [0, 1]
|
||||
|
||||
|
||||
def test_inserting_for_sorted_array():
|
||||
rap = rotated_array.RotatedArrayProxy([0, 1])
|
||||
assert rap.unrotated() == [0, 1]
|
||||
assert rap.sorted_insertion_index(1000) == 2
|
||||
assert rap.actual_insertion_index(1000) == 2
|
||||
|
||||
|
||||
def test_inserting_largest_element():
|
||||
rap = rotated_array.RotatedArrayProxy([3, 0, 1])
|
||||
assert rap.rotation_index == 1
|
||||
assert rap.sorted_insertion_index(1000) == 3
|
||||
assert rap.actual_insertion_index(1000) == 1
|
||||
|
||||
|
||||
def test_inserting_largest_element():
|
||||
rap = rotated_array.RotatedArrayProxy([3, 0, 1])
|
||||
assert rap.unrotated() == [0, 1, 3]
|
||||
assert rap.actual_insertion_index(2) == 0
|
||||
assert rap.actual_insertion_index(1) in [2, 3]
|
||||
|
||||
|
||||
def test_rotation_index_and_unrotate():
|
||||
arr = [3]*117 + [1] + [2]*16
|
||||
rap = rotated_array.RotatedArrayProxy(arr)
|
||||
assert rap[0] == 1
|
||||
assert rap.rotation_index == 117
|
||||
assert rap.unrotated() == sorted(arr)
|
||||
|
||||
arr = [3, 3, 3, 3, 1, 1, 1, 2, 2]
|
||||
rap = rotated_array.RotatedArrayProxy(arr)
|
||||
assert rap.rotation_index == 4
|
||||
assert rap.unrotated() == sorted(arr)
|
||||
|
||||
rap = rotated_array.RotatedArrayProxy([3, 3, 3, 3, 1, 1, 1, 2])
|
||||
assert rap.rotation_index == 4
|
||||
assert rap.unrotated() == [1, 1, 1, 2, 3, 3, 3, 3]
|
||||
|
||||
|
||||
def test_insert():
|
||||
|
Loading…
Reference in New Issue
Block a user