-
Notifications
You must be signed in to change notification settings - Fork 747
/
Copy pathlist_rotation.py
144 lines (111 loc) · 3.52 KB
/
list_rotation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# This file lists some Python functions for rotating a list
# to the left. The emphasis is functions that don't use extra
# space.
# For an explanation, see
# https://eli.thegreenplace.net/2008/08/29/space-efficient-list-rotation/
#
# Based on chapter 2 of "Programming Pearls" by Jon Bentley
#
# Eli Bendersky (https://eli.thegreenplace.net)
#
def rotate_naive(lst, dist):
""" A 'naive' (space inefficient) rotation function.
The slice operations create new lists.
"""
lst[:] = lst[dist:len(lst)] + lst[0:dist]
def gcd(a, b):
""" Greatest common divisor of a and b
Using Euclid's algorithm
"""
while b:
a, b = b, a % b
return a
def rotate_juggle(lst, dist):
""" An iterative 'juggle' method
"""
n = len(lst)
for i in xrange(gcd(dist, n)):
t = lst[i]
j = i
while 1:
k = (j + dist) % n
if k == i: break
lst[j] = lst[k]
j = k
lst[j] = t
def sublist_swap(lst, a, b, m):
""" Swaps (in-place) the elements:
lst[a:a+m) with lst[b:b+m)
Without using extra space.
Assumes that all the indices point inside the list.
"""
for i in xrange(m):
lst[a + i], lst[b + i] = lst[b + i], lst[a + i]
def rotate_swap(lst, dist):
""" A 'recursive' sub-list swapping method.
"""
n = len(lst)
if dist == 0 or dist == n:
return
i = p = dist
j = n - p
while i != j:
if i > j:
sublist_swap(lst, p - i, p, j)
i -= j
else:
sublist_swap(lst, p - i, p + j - i, i)
j -= i
sublist_swap(lst, p - i, p, i)
def sublist_reverse(lst, a, b):
""" Reverses (in-place) the elements lst[a:b]
"""
while b > a:
lst[a], lst[b] = lst[b], lst[a]
b -= 1
a += 1
def rotate_reverse(lst, dist):
""" Uses reversing to rotate the list.
"""
n = len(lst)
sublist_reverse(lst, 0, dist - 1)
sublist_reverse(lst, dist, n - 1)
sublist_reverse(lst, 0, n - 1)
import timeit
def benchmark():
setup = """
from __main__ import rotate_naive, rotate_juggle, rotate_reverse, rotate_swap
lst = range(1000000)
dist = 100000
"""
N = 10
print "Naive:", timeit.Timer(stmt='rotate_naive(lst, dist)', setup=setup).timeit(N)
print "Juggle:", timeit.Timer(stmt='rotate_juggle(lst, dist)', setup=setup).timeit(N)
print "Swap:", timeit.Timer(stmt='rotate_swap(lst, dist)', setup=setup).timeit(N)
print "Reverse:", timeit.Timer(stmt='rotate_reverse(lst, dist)', setup=setup).timeit(N)
import unittest
class TestListRotation(unittest.TestCase):
def do_test_list(self, func, lst, dist, newlst):
mylst = lst[:]
func(mylst, dist)
self.assertEqual(mylst, newlst)
def run_tests(self, func):
self.do_test_list(func, [], 0, [])
self.do_test_list(func, [1], 0, [1])
self.do_test_list(func, [1, 2], 0, [1, 2])
self.do_test_list(func, [1, 2], 2, [1, 2])
self.do_test_list(func, [1, 2], 1, [2, 1])
self.do_test_list(func, [1, 2, 3], 1, [2, 3, 1])
self.do_test_list(func, [1, 2, 3], 2, [3, 1, 2])
N = 500
for i in range(N):
self.do_test_list(func, range(N), i, range(i, N) + range(i))
def test_rotates(self):
self.run_tests(rotate_naive)
self.run_tests(rotate_juggle)
self.run_tests(rotate_swap)
self.run_tests(rotate_reverse)
if __name__ == '__main__':
# Uncomment one of the following
#~ unittest.main()
benchmark()