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