347

What is the most efficient way to rotate a list in python? Right now I have something like this:

>>> def rotate(l, n):
...     return l[n:] + l[:n]
... 
>>> l = [1,2,3,4]
>>> rotate(l,1)
[2, 3, 4, 1]
>>> rotate(l,2)
[3, 4, 1, 2]
>>> rotate(l,0)
[1, 2, 3, 4]
>>> rotate(l,-1)
[4, 1, 2, 3]

Is there a better way?

jameshfisher
  • 34,029
  • 31
  • 121
  • 167
D R
  • 21,936
  • 38
  • 112
  • 149
  • @dzhelil I really like your original solution because it doesn't introduce mutations – juanchito May 16 '17 at 02:06
  • 4
    [numpy.roll](https://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html) – BoltzmannBrain Apr 12 '18 at 16:14
  • 2
    I think `rotate` is the right word, not `shift`. – codeforester Nov 08 '18 at 23:03
  • 5
    The *real* correct answer, is you should never be rotating the list in the first place. Create a "pointer" variable to the logical place in your list where you want the "head" or "tail" to be, and change that variable instead of moving any of the items in the list. Look up the "modulo" operator % for the efficient way to "wrap" your pointer around the start and end of the list. – cnd Aug 23 '19 at 22:36
  • 3
    @cnd Depending on how frequently rotation will be done and how often you need to iterate, a single "real" rotation can outperform having to repeatedly compute indices in that fashion. As asked, this is the *only* way to actually rotate the list. Whether the list *needs* to be rotated, or if a different data structure should be used, can't be answered without more context. – chepner Jan 03 '22 at 14:15
  • To also correctly treat cases where `n > len(l)` (i.e. rotate by more than 360°, so to speak), `rotate` should first do `n = n % len(l)` – root Feb 27 '22 at 22:47
  • 1
    @cnd Does your suggestion also work if the list has to be passed to python modules that we can't modify? – root Feb 27 '22 at 23:03

27 Answers27

375

A collections.deque is optimized for pulling and pushing on both ends. They even have a dedicated rotate() method.

from collections import deque
items = deque([1, 2])
items.append(3)        # deque == [1, 2, 3]
items.rotate(1)        # The deque is now: [3, 1, 2]
items.rotate(-1)       # Returns deque to original state: [1, 2, 3]
item = items.popleft() # deque == [2, 3]
codeforester
  • 39,467
  • 16
  • 112
  • 140
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 16
    For future readers: `collections.deque rotate()` is faster than slicing according to https://wiki.python.org/moin/TimeComplexity – Geoff Dec 16 '16 at 17:35
  • 4
    But be aware, using `deque.rotate` requires a type conversion to a `deque` object first, which is slower than `l.append(l.pop(0))`. So if you have a deque object to start with, sure it is fastest. Otherwise, use `l.append(l.pop(0))`. – Purrell Jul 04 '17 at 10:03
  • 13
    To elaborate, `deque.rotate` is O(k) but **type conversion from list to deque is O(n)**. So if you start with a list, using deque.rotate is O(n)+O(k)=O(n). `l.append(l.pop(0))` on the other hand is O(1). – Purrell Jul 04 '17 at 10:13
  • 8
    @Purrell, popping the front item is O(n). In wiki.python.org/moin/TimeComplexity it's listed as O(k), and k is the number of elements in the list following the popped item, because the data structure shifts all following elements toward the front of the list. Only the last element can be popped in O(1) time for this reason. – Kirk Boyer Jun 17 '18 at 21:26
98

What about just using pop(0)?

list.pop([i])

Remove the item at the given position in the list, and return it. If no index is specified, a.pop() removes and returns the last item in the list. (The square brackets around the i in the method signature denote that the parameter is optional, not that you should type square brackets at that position. You will see this notation frequently in the Python Library Reference.)

tobias_k
  • 81,265
  • 12
  • 120
  • 179
Jamgold
  • 1,716
  • 1
  • 14
  • 18
  • 19
    But wouldn't it cost O(k) for removing each element in the list where k is number of remaining elements. So the total time will be O(n^2) http://wiki.python.org/moin/TimeComplexity – Pramod Nov 09 '12 at 06:42
  • 5
    This doesn't really answer the question. The question isn't about returning items in order, but rather about creating a new list that is in a different order. – user650261 Jan 04 '16 at 21:18
  • 5
    no, the answer to the question using pop would be `l.append(l.pop(0)`. Which if I am not mistaken is O(1). – Purrell Jul 04 '17 at 10:12
  • This is very elegant and pythonic IMHO – ultrasounder Jun 27 '18 at 13:20
  • 6
    list.pop internally calls list_ass_slice which uses memmove to very quickly move all the items, but it's still O(n). See https://github.com/python/cpython/blob/master/Objects/listobject.c and https://wiki.python.org/moin/TimeComplexity. The only item that can be removed from a python list in constant time is the last. – DRayX Jul 28 '18 at 04:53
  • 3
    Downvoted. From https://docs.python.org/3/tutorial/datastructures.html#using-lists-as-queues It is also possible to use a list as a queue, where the first element added is the first element retrieved (“first-in, first-out”); however, lists are not efficient for this purpose. While appends and pops from the end of list are fast, doing inserts or pops from the beginning of a list is slow (because all of the other elements have to be shifted by one). – SantaXL May 05 '19 at 19:27
  • Upvoted. If only because of the insight it gave me that we can use list.pop(0) in Pyhton where in Javascript we would use array.shift(). (And, equivalently in PHP) – Ideogram Sep 08 '20 at 05:51
83

Numpy can do this using the roll command:

>>> import numpy
>>> a=numpy.arange(1,10) #Generate some data
>>> numpy.roll(a,1)
array([9, 1, 2, 3, 4, 5, 6, 7, 8])
>>> numpy.roll(a,-1)
array([2, 3, 4, 5, 6, 7, 8, 9, 1])
>>> numpy.roll(a,5)
array([5, 6, 7, 8, 9, 1, 2, 3, 4])
>>> numpy.roll(a,9)
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
Richard
  • 56,349
  • 34
  • 180
  • 251
  • 3
    What I love about SO is that sometimes down the answers feed you can find some great new treasures like this one :) – noamgot Dec 16 '19 at 08:38
  • 2
    This, when I tested it, is very, very slow – Peter Harrison Apr 10 '20 at 10:06
  • 3
    @PeterHarrison: Since you don't provide testing details it's hard to know what you even mean. [This answer](https://stackoverflow.com/a/44901770/752843) provides full testing details and a timing comparison. – Richard Apr 10 '20 at 16:17
  • it is slow because after each operation you return the changed list. The fastest solution to this problem is that you create a new class that includes the original list and the "rotator" position. You can rotate but you do not return the list. Then you need to make a specific "return list" procedure. So it will look like: x = rotator(list). x.rotate(5) x.rotate(6) x.rotate(-10). x.getlist() – user2473664 Jan 25 '23 at 08:52
  • @user2473664: Why not write this as an answer and benchmark it? – Richard Jan 25 '23 at 13:45
39

It depends on what you want to have happen when you do this:

>>> shift([1,2,3], 14)

You might want to change your:

def shift(seq, n):
    return seq[n:]+seq[:n]

to:

def shift(seq, n):
    n = n % len(seq)
    return seq[n:] + seq[:n]
jcdyer
  • 18,616
  • 5
  • 42
  • 49
33

Simplest way I can think of:

a.append(a.pop(0))
runDOSrun
  • 10,359
  • 7
  • 47
  • 57
Thijs
  • 355
  • 3
  • 2
  • 4
    This is the fastest way for lists. `collections.deque` is faster but for most common cases of list length on a single iteration, or any case of multiple iterations, `a.append(a.pop(0))` is going to be faster than the type conversion to deque – Purrell Jul 04 '17 at 10:27
  • @runDOSrun the perfect answer to [this question](https://stackoverflow.com/q/29840387/2932052) which is sadly closed as a duplicate. Maybe you'll vote for reopen it? – Wolf Oct 17 '19 at 10:24
24

Just some notes on timing:

If you're starting with a list, l.append(l.pop(0)) is the fastest method you can use. This can be shown with time complexity alone:

  • deque.rotate is O(k) (k=number of elements)
  • list to deque conversion is O(n)
  • list.append and list.pop are both O(1)

So if you are starting with deque objects, you can deque.rotate() at the cost of O(k). But, if the starting point is a list, the time complexity of using deque.rotate() is O(n). l.append(l.pop(0) is faster at O(1).

Just for the sake of illustration, here are some sample timings on 1M iterations:

Methods which require type conversion:

  • deque.rotate with deque object: 0.12380790710449219 seconds (fastest)
  • deque.rotate with type conversion: 6.853878974914551 seconds
  • np.roll with nparray: 6.0491721630096436 seconds
  • np.roll with type conversion: 27.558452129364014 seconds

List methods mentioned here:

  • l.append(l.pop(0)): 0.32483696937561035 seconds (fastest)
  • "shiftInPlace": 4.819645881652832 seconds
  • ...

Timing code used is below.


collections.deque

Showing that creating deques from lists is O(n):

from collections import deque
import big_o

def create_deque_from_list(l):
     return deque(l)

best, others = big_o.big_o(create_deque_from_list, lambda n: big_o.datagen.integers(n, -100, 100))
print best

# --> Linear: time = -2.6E-05 + 1.8E-08*n

If you need to create deque objects:

1M iterations @ 6.853878974914551 seconds

setup_deque_rotate_with_create_deque = """
from collections import deque
import random
l = [random.random() for i in range(1000)]
"""

test_deque_rotate_with_create_deque = """
dl = deque(l)
dl.rotate(-1)
"""
timeit.timeit(test_deque_rotate_with_create_deque, setup_deque_rotate_with_create_deque)

If you already have deque objects:

1M iterations @ 0.12380790710449219 seconds

setup_deque_rotate_alone = """
from collections import deque
import random
l = [random.random() for i in range(1000)]
dl = deque(l)
"""

test_deque_rotate_alone= """
dl.rotate(-1)
"""
timeit.timeit(test_deque_rotate_alone, setup_deque_rotate_alone)

np.roll

If you need to create nparrays

1M iterations @ 27.558452129364014 seconds

setup_np_roll_with_create_npa = """
import numpy as np
import random
l = [random.random() for i in range(1000)]
"""

test_np_roll_with_create_npa = """
np.roll(l,-1) # implicit conversion of l to np.nparray
"""

If you already have nparrays:

1M iterations @ 6.0491721630096436 seconds

setup_np_roll_alone = """
import numpy as np
import random
l = [random.random() for i in range(1000)]
npa = np.array(l)
"""

test_roll_alone = """
np.roll(npa,-1)
"""
timeit.timeit(test_roll_alone, setup_np_roll_alone)

"Shift in place"

Requires no type conversion

1M iterations @ 4.819645881652832 seconds

setup_shift_in_place="""
import random
l = [random.random() for i in range(1000)]
def shiftInPlace(l, n):
    n = n % len(l)
    head = l[:n]
    l[:n] = []
    l.extend(head)
    return l
"""

test_shift_in_place="""
shiftInPlace(l,-1)
"""

timeit.timeit(test_shift_in_place, setup_shift_in_place)

l.append(l.pop(0))

Requires no type conversion

1M iterations @ 0.32483696937561035

setup_append_pop="""
import random
l = [random.random() for i in range(1000)]
"""

test_append_pop="""
l.append(l.pop(0))
"""
timeit.timeit(test_append_pop, setup_append_pop)
Purrell
  • 12,461
  • 16
  • 58
  • 70
  • 4
    while list.pop() is a constant-time operation, list.pop(0) is _not_. It runs in linear time with respect to list length. You can test that by modifying your timeit setup: `l = [random.random() for i in range(100000)]` – emu Dec 31 '17 at 13:07
  • 1
    list.pop is not a constant time operation. list.pop runs in O(k) time where k is the number of elements past the removed element, so list.pop(0) is O(n). Internally, list.pop uses list_ass_slice which uses memmove to move items way faster than you ever could with python, but for long lists it is still very time consuming. See https://github.com/python/cpython/blob/master/Objects/listobject.c and https://wiki.python.org/moin/TimeComplexity – DRayX Jul 28 '18 at 04:58
  • Thanks for the timing (and comments @emu). So can we say that `l.append(l.pop(0))` is the performing best for shifting short lists (around 7 elements) by one? – Wolf Oct 17 '19 at 08:43
  • Again, concerning `l.append(l.pop(0))` as an answer: [This question](https://stackoverflow.com/q/29840387/2932052) is closed as a duplicate. Maybe you'll vote for reopen it? – Wolf Oct 17 '19 at 10:27
19

I also got interested in this and compared some of the suggested solutions with perfplot (a small project of mine).

It turns out that Kelly Bundy's suggestion

tmp = data[shift:]
tmp += data[:shift]

performs very well for all shifts.

Essentially, perfplot performs the shift for increasing large arrays and measures the time. Here are the results:

shift = 1:

enter image description here

shift = 100:

enter image description here


Code to reproduce the plot:

import numpy
import perfplot
import collections


shift = 100


def list_append(data):
    return data[shift:] + data[:shift]


def list_append2(data):
    tmp = data[shift:]
    tmp += data[:shift]
    return tmp


def shift_concatenate(data):
    return numpy.concatenate([data[shift:], data[:shift]])


def roll(data):
    return numpy.roll(data, -shift)


def collections_deque(data):
    items = collections.deque(data)
    items.rotate(-shift)
    return items


def pop_append(data):
    data = data.copy()
    for _ in range(shift):
        data.append(data.pop(0))
    return data


b = perfplot.bench(
    setup=lambda n: numpy.random.rand(n).tolist(),
    kernels=[
        list_append,
        list_append2,
        roll,
        shift_concatenate,
        collections_deque,
        pop_append,
    ],
    n_range=[2 ** k for k in range(7, 20)],
    xlabel="len(data)",
)
b.show()
b.save("shift100.png")
Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • Nice tool you built. Concerning `l.append(l.pop(0))` as an answer: [This question](https://stackoverflow.com/q/29840387/2932052) is closed as a duplicate. Maybe you'll vote for reopen it? – Wolf Oct 17 '19 at 10:28
  • 1
    This is even faster: `def tmp_del(data): tmp = data[:shift]; del data[:shift]; data += tmp; return data` (matches `pop_append` at n=1, beats it at n=10, and beats `collections_deque` at n=100). – Kelly Bundy Nov 30 '21 at 10:55
  • I see you changed "small" to "all". For "large" shifts, it's probably much faster to instead copy out and delete the short *suffix* and slice it into the front. So ideally one would first determine which of the two parts is shorter, and move that out and back in. – Kelly Bundy Nov 30 '21 at 11:27
  • Oh, just noticed you added `data.copy()` to it and to `pop_append`. Certainly fairer to the other solutions, although now it doesn't really make sense. For creating a new list it would be `tmp = data[shift:]` `tmp += data[:shift]` `return tmp`. – Kelly Bundy Nov 30 '21 at 11:45
  • That's just the `list_append` solution. – Nico Schlömer Nov 30 '21 at 11:55
  • No it's not, it's better. `list_append` copies `2n` elements and discards `n`. Mine (from my last comment) copies only `n+shft` and discards `shift`. – Kelly Bundy Nov 30 '21 at 12:07
16

If you just want to iterate over these sets of elements rather than construct a separate data structure, consider using iterators to construct a generator expression:

def shift(l,n):
    return itertools.islice(itertools.cycle(l),n,n+len(l))

>>> list(shift([1,2,3],1))
[2, 3, 1]
Phil H
  • 19,928
  • 7
  • 68
  • 105
11

This also depends on if you want to shift the list in place (mutating it), or if you want the function to return a new list. Because, according to my tests, something like this is at least twenty times faster than your implementation that adds two lists:

def shiftInPlace(l, n):
    n = n % len(l)
    head = l[:n]
    l[:n] = []
    l.extend(head)
    return l

In fact, even adding a l = l[:] to the top of that to operate on a copy of the list passed in is still twice as fast.

Various implementations with some timing at http://gist.github.com/288272

keturn
  • 4,780
  • 3
  • 29
  • 40
  • 3
    Instead of `l[:n] = []` I would go for `del l[:n]`. Just an alternative. – tzot Jan 28 '10 at 00:18
  • 1
    Oh, yeah, good old del. I often forget about del; the list operation that's a statement, not a method. Did py3k change that quirk, or have we still got it? – keturn Jan 28 '10 at 00:33
  • 3
    @keturn: `del` is still a statement in Py3. However `x.__delitem__(y) <==> del x[y]`, so if you prefer using methods, `l.__delitem__(slice(n))` is also equivalent and works in both 2 & 3. – martineau Nov 09 '13 at 18:11
5

For an immutable implementation, you could use something like this:

def shift(seq, n):
    shifted_seq = []
    for i in range(len(seq)):
        shifted_seq.append(seq[(i-n) % len(seq)])
    return shifted_seq

print shift([1, 2, 3, 4], 1)
dabuno
  • 353
  • 3
  • 5
Bittercoder
  • 11,753
  • 10
  • 58
  • 76
4

Possibly a ringbuffer is more suitable. It is not a list, although it is likely that it can behave enough like a list for your purposes.

The problem is that the efficiency of a shift on a list is O(n), which becomes significant for large enough lists.

Shifting in a ringbuffer is simply updating the head location which is O(1)

John La Rooy
  • 295,403
  • 53
  • 369
  • 502
3

If efficiency is your goal, (cycles? memory?) you may be better off looking at the array module: http://docs.python.org/library/array.html

Arrays do not have the overhead of lists.

As far as pure lists go though, what you have is about as good as you can hope to do.

recursive
  • 83,943
  • 34
  • 151
  • 241
3

I think you are looking for this:

a.insert(0, x)
redreamality
  • 1,034
  • 10
  • 11
  • 1
    I do not see the relation between the question and your answer. Can you please explain it? – Wolf Oct 17 '19 at 09:59
2

Another alternative:

def move(arr, n):
    return [arr[(idx-n) % len(arr)] for idx,_ in enumerate(arr)]
damio
  • 6,041
  • 3
  • 39
  • 58
2
def solution(A, K):
    if len(A) == 0:
        return A

    K = K % len(A)

    return A[-K:] + A[:-K]

# use case
A = [1, 2, 3, 4, 5, 6]
K = 3
print(solution(A, K))

For example, given

A = [3, 8, 9, 7, 6]
K = 3

the function should return [9, 7, 6, 3, 8]. Three rotations were made:

[3, 8, 9, 7, 6] -> [6, 3, 8, 9, 7]
[6, 3, 8, 9, 7] -> [7, 6, 3, 8, 9]
[7, 6, 3, 8, 9] -> [9, 7, 6, 3, 8]

For another example, given

A = [0, 0, 0]
K = 1

the function should return [0, 0, 0]

Given

A = [1, 2, 3, 4]
K = 4

the function should return [1, 2, 3, 4]

RobC
  • 22,977
  • 20
  • 73
  • 80
Rakesh Kumar
  • 117
  • 6
1

I don't know if this is 'efficient', but it also works:

x = [1,2,3,4]
x.insert(0,x.pop())

EDIT: Hello again, I just found a big problem with this solution! Consider the following code:

class MyClass():
    def __init__(self):
        self.classlist = []

    def shift_classlist(self): # right-shift-operation
        self.classlist.insert(0, self.classlist.pop())

if __name__ == '__main__':
    otherlist = [1,2,3]
    x = MyClass()

    # this is where kind of a magic link is created...
    x.classlist = otherlist

    for ii in xrange(2): # just to do it 2 times
        print '\n\n\nbefore shift:'
        print '     x.classlist =', x.classlist
        print '     otherlist =', otherlist
        x.shift_classlist() 
        print 'after shift:'
        print '     x.classlist =', x.classlist
        print '     otherlist =', otherlist, '<-- SHOULD NOT HAVE BIN CHANGED!'

The shift_classlist() method executes the same code as my x.insert(0,x.pop())-solution, otherlist is a list indipendent from the class. After passing the content of otherlist to the MyClass.classlist list, calling the shift_classlist() also changes the otherlist list:

CONSOLE OUTPUT:

before shift:
     x.classlist = [1, 2, 3]
     otherlist = [1, 2, 3]
after shift:
     x.classlist = [3, 1, 2]
     otherlist = [3, 1, 2] <-- SHOULD NOT HAVE BIN CHANGED!



before shift:
     x.classlist = [3, 1, 2]
     otherlist = [3, 1, 2]
after shift:
     x.classlist = [2, 3, 1]
     otherlist = [2, 3, 1] <-- SHOULD NOT HAVE BIN CHANGED!

I use Python 2.7. I don't know if thats a bug, but I think it's more likely that I missunderstood something here.

Does anyone of you know why this happens?

wese3112
  • 93
  • 1
  • 1
  • 6
  • 2
    That happens because `x.classlist = otherlist` makes `x.classlist` refer to the same list as `otherlist` and then when you call `x.shift_classlist()` it mutates the list and because both names refer to the same list object. Both names appear to change because they are just aliases for the same object. Use `x.classlist = otherlist[:]` instead to assign a copy of the list. – Dan D. Oct 16 '13 at 06:42
  • Hey wow! Thank you very much! I really didn't know that and It's really good to know! :) – wese3112 Oct 17 '13 at 18:49
1

The following method is O(n) in place with constant auxiliary memory:

def rotate(arr, shift):
  pivot = shift % len(arr)
  dst = 0
  src = pivot
  while (dst != src):
    arr[dst], arr[src] = arr[src], arr[dst]
    dst += 1
    src += 1
    if src == len(arr):
      src = pivot
    elif dst == pivot:
      pivot = src

Note that in python, this approach is horribly inefficient compared to others as it can't take advantage of native implementations of any of the pieces.

DRayX
  • 1,043
  • 2
  • 12
  • 19
  • well, actually you could use list.pop and list.append. It isn't the language's fault you wrote a 12 line function that is O(n), when you could have just wrote "l.append(l.pop(0))" which is constant time. – Purrell Jul 04 '17 at 11:21
  • l.append(l.pop(0)) is O(n) (l.pop(0) has to shift every element), thus if you wanted to shift m values the complexity is actually O(n*m). The complexity of the algorithm I provided is O(n) regardless of the number of shifts. In practice, this is slow because so much logic is done in python ops instead of C (list.pop is implemented in c, see https://github.com/python/cpython/blob/master/Objects/listobject.c). – DRayX Jul 28 '18 at 04:48
1

I have similar thing. For example, to shift by two...

def Shift(*args):
    return args[len(args)-2:]+args[:len(args)-2]
eyoeldefare
  • 2,136
  • 1
  • 15
  • 25
1

I think you've got the most efficient way

def shift(l,n):
    n = n % len(l)  
    return l[-U:] + l[:-U]
john k
  • 6,268
  • 4
  • 55
  • 59
1

Jon Bentley in Programming Pearls (Column 2) describes an elegant and efficient algorithm for rotating an n-element vector x left by i positions:

Let's view the problem as transforming the array ab into the array ba, but let's also assume that we have a function that reverses the elements in a specified portion of the array. Starting with ab, we reverse a to get arb, reverse b to get arbr, and then reverse the whole thing to get (arbr)r, which is exactly ba. This results in the following code for rotation:

reverse(0, i-1)
reverse(i, n-1)
reverse(0, n-1)

This can be translated to Python as follows:

def rotate(x, i):
    i %= len(x)
    x[:i] = reversed(x[:i])
    x[i:] = reversed(x[i:])
    x[:] = reversed(x)
    return x

Demo:

>>> def rotate(x, i):
...     i %= len(x)
...     x[:i] = reversed(x[:i])
...     x[i:] = reversed(x[i:])
...     x[:] = reversed(x)
...     return x
... 
>>> rotate(list('abcdefgh'), 1)
['b', 'c', 'd', 'e', 'f', 'g', 'h', 'a']
>>> rotate(list('abcdefgh'), 3)
['d', 'e', 'f', 'g', 'h', 'a', 'b', 'c']
>>> rotate(list('abcdefgh'), 8)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> rotate(list('abcdefgh'), 9)
['b', 'c', 'd', 'e', 'f', 'g', 'h', 'a']
Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
1

I was looking for in place solution to this problem. This solves the purpose in O(k).

def solution(self, list, k):
    r=len(list)-1
    i = 0
    while i<k:
        temp = list[0]
        list[0:r] = list[1:r+1]
        list[r] = temp
        i+=1
    return list
Ankit
  • 45
  • 4
1

I'm "old school" I define efficiency in lowest latency, processor time and memory usage, our nemesis are the bloated libraries. So there is exactly one right way:

    def rotatel(nums):
        back = nums.pop(0)
        nums.append(back)
        return nums
Light Bringer
  • 741
  • 7
  • 12
1

I take this cost model as a reference:

http://scripts.mit.edu/~6.006/fall07/wiki/index.php?title=Python_Cost_Model

Your method of slicing the list and concatenating two sub-lists are linear-time operations. I would suggest using pop, which is a constant-time operation, e.g.:

def shift(list, n):
    for i in range(n)
        temp = list.pop()
        list.insert(0, temp)
shanethehat
  • 15,460
  • 11
  • 57
  • 87
herrfz
  • 4,814
  • 4
  • 26
  • 37
  • 2
    update: take this as a better reference: http://wiki.python.org/moin/TimeComplexity, use `collections.dequeue` pop and appendleft, which both are O(1) ops. In my first answer above, insert is O(n). – herrfz Feb 21 '12 at 22:40
  • 1
    should be `collections.deque` – herrfz Feb 21 '12 at 23:18
0

What is the use case? Often, we don't actually need a fully shifted array --we just need to access a handful of elements in the shifted array.

Getting Python slices is runtime O(k) where k is the slice, so a sliced rotation is runtime N. The deque rotation command is also O(k). Can we do better?

Consider an array that is extremely large (let's say, so large it would be computationally slow to slice it). An alternative solution would be to leave the original array alone and simply calculate the index of the item that would have existed in our desired index after a shift of some kind.

Accessing a shifted element thus becomes O(1).

def get_shifted_element(original_list, shift_to_left, index_in_shifted):
    # back calculate the original index by reversing the left shift
    idx_original = (index_in_shifted + shift_to_left) % len(original_list)
    return original_list[idx_original]

my_list = [1, 2, 3, 4, 5]

print get_shifted_element(my_list, 1, 2) ----> outputs 4

print get_shifted_element(my_list, -2, 3) -----> outputs 2 
Nick L
  • 171
  • 1
  • 8
0

Following function copies sent list to a templist, so that pop function does not affect the original list:

def shift(lst, n, toreverse=False):
    templist = []
    for i in lst: templist.append(i)
    if toreverse:
        for i in range(n):  templist = [templist.pop()]+templist
    else:
        for i in range(n):  templist = templist+[templist.pop(0)]
    return templist

Testing:

lst = [1,2,3,4,5]
print("lst=", lst)
print("shift by 1:", shift(lst,1))
print("lst=", lst)
print("shift by 7:", shift(lst,7))
print("lst=", lst)
print("shift by 1 reverse:", shift(lst,1, True))
print("lst=", lst)
print("shift by 7 reverse:", shift(lst,7, True))
print("lst=", lst)

Output:

lst= [1, 2, 3, 4, 5]
shift by 1: [2, 3, 4, 5, 1]
lst= [1, 2, 3, 4, 5]
shift by 7: [3, 4, 5, 1, 2]
lst= [1, 2, 3, 4, 5]
shift by 1 reverse: [5, 1, 2, 3, 4]
lst= [1, 2, 3, 4, 5]
shift by 7 reverse: [4, 5, 1, 2, 3]
lst= [1, 2, 3, 4, 5]
rnso
  • 23,686
  • 25
  • 112
  • 234
0

For a list X = ['a', 'b', 'c', 'd', 'e', 'f'] and a desired shift value of shift less than list length, we can define the function list_shift() as below

def list_shift(my_list, shift):
    assert shift < len(my_list)
    return my_list[shift:] + my_list[:shift]

Examples,

list_shift(X,1) returns ['b', 'c', 'd', 'e', 'f', 'a'] list_shift(X,3) returns ['d', 'e', 'f', 'a', 'b', 'c']

helcode
  • 1,859
  • 1
  • 13
  • 32
  • 1
    That is exactly what the OP has. You just changed the names and added an assert. – RufusVS Oct 22 '18 at 17:41
  • The function `list_shift` in your answer is identical to the function `shift` in the original question, so this is not an answer to the actual question: "Is there a better way?" – RufusVS Oct 23 '18 at 15:19
0

Below is an efficient algorithm that doesn't require the use of any additional data structure:

def rotate(nums: List[int], k: int):

    k = k%len(nums)
    l, r = 0, len(nums)-1
    while (l<r):
        nums[l], nums[r]= nums[r], nums[l]
        l,r=l+1,r-1
    
    l,r = 0, k-1
    while (l<r):
        nums[l], nums[r]=nums[r], nums[l]
        l,r=l+1,r-1
        
    l,r=k,len(nums)-1
    while (l<r):
        nums[l], nums[r]=nums[r], nums[l]
        l,r=l+1,r-1