I've written this function to take a list and shift each member right by the number supplied as the offset.

I want to preserve the supplied list and return the shifted list.

My problem is that the function modifies the supplied input list itself as well as returning a new list with the elements shifted, but I can't figure out why. I need the supplied list to be unchanged.

My debugging shows me that the line:

`work[i + offset] = list_in[i]` is the culprit because each time it executes, the supplied input list changes as well.

I'm stumped as to why. Any ideas?

Here's the function:

``````def shift_right(list_in, offset):
"""
Shifts list to the right by number supplied as offset
"""
# We want to preserve the supplied list, so make a copy
work = list_in

# Check that offset doesn't exceed length of list
#  if it does return 1 as error code
if offset >= len(list_in):
return "1"

else:
if len(list_in) > 0:
# capture fall off to the right to
# be re-inserted at beginning of list
hold = list_in[offset + 1:]

# now begin the shifting
for i in range(len(list_in)-offset):
work[i + offset] = list_in[i]

# Now take the values that fell off and put them
# back at the beginning
else:
for x in range(len(hold)):
work[x] = hold[x]
return work

# Test the function
one = [1,2,3,4,5,6,7]
print(shift_right(one, 3))
``````

The result I get is [5, 6, 7, 1, 2, 3, 1]

4
Contributors
9
Replies
66
Views
4 Years
Discussion Span
Last Post by chophouse

Do shallow copy of input list as list is mutable by slice [:] or .copy() method call.

I would do it this way

``````>>> def shift_right(list_in, offset):
...     offset = offset % len(list_in) if list_in else 0
...     return list_in[-offset:] + list_in[:-offset]
...
>>> one = [1,2,3,4,5,6,7]
>>> for i in range(-3, 10):
...     print(shift_right(one, i))
...
[4, 5, 6, 7, 1, 2, 3]
[3, 4, 5, 6, 7, 1, 2]
[2, 3, 4, 5, 6, 7, 1]
[1, 2, 3, 4, 5, 6, 7]
[7, 1, 2, 3, 4, 5, 6]
[6, 7, 1, 2, 3, 4, 5]
[5, 6, 7, 1, 2, 3, 4]
[4, 5, 6, 7, 1, 2, 3]
[3, 4, 5, 6, 7, 1, 2]
[2, 3, 4, 5, 6, 7, 1]
[1, 2, 3, 4, 5, 6, 7]
[7, 1, 2, 3, 4, 5, 6]
[6, 7, 1, 2, 3, 4, 5]
``````

Notice that in python, it is much better to raise an exception than to handle error via the return value. Instead of an error code, use

``````if offset >= len(list_in):
raise ValueError(("shift value too large", offset))
``````

Edited by Gribouillis

Thanks pyTony,

line 6 changed to `work = list_in[:]`

lin 17 changed to `hold = list_in[-offset:]`

And now all is well.

Valuable lesson there. Thanks

Grib,

Thanks for your approach which I'm still studying. I do have a question or two about it:

at line 2 `offset = offset % len(list_in)`
Wouldn't this always be the offset (as long as offset is < len(list_in) )? If so, then I can see that the original list_in would be returned if the offset were exactly equal to the len(list_in).

However if the supplied offset is > len(list_in), the else statement should raise an error, but as written it will apply an incorrect offset to the list_in, won't it?

Edited by chophouse: corrected reference

You might also consider working with deque (double ended que, pronounced 'deck') from the Python module collections ...

``````from collections import deque

mylist = [1,2,3,4,5,6,7]
mydeque = deque(mylist)

mydeque.rotate(3)

print(list(mydeque))  # [5, 6, 7, 1, 2, 3, 4]
``````

Edited by vegaseat

offset = offset % len(list_in)

This applies an offset modulo the length of `list_in`. For example if the list has length 6, it will apply an offset in
the infinite sequence

``````0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 ...
``````

So for example an offset of 15 results in an offset of 3. This is probably better because the list is not only shifted to the right: the end of the list is moved at the beginning. It is actualy a rotation of the list. Using offsets modulo n has an other advantage: a shift of 23 followed by a shift of 11 results in a shift of 23 + 11 = 34. A mathematician would say that the shift is a group action of the group of signed integers on the set of lists :)

Edited by Gribouillis

Grub,

thanks, but it only needs to shift the supplied list_in, no need for an infinite sequence

Vegaseat,

I'd never heard of deque before. Must study. Thanks

thanks, but it only needs to shift the supplied list_in, no need for an infinite sequence

I don't completely agree with you. See the following example which shows a list shifted by 3

``````>>> L = list("abcefg")
>>> L
['a', 'b', 'c', 'e', 'f', 'g']
>>> list(enumerate(L))
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'e'), (4, 'f'), (5, 'g')]
>>> S = L[-3:] + L[:-3]
>>> S
['e', 'f', 'g', 'a', 'b', 'c']
>>> list(enumerate(S))
[(0, 'e'), (1, 'f'), (2, 'g'), (3, 'a'), (4, 'b'), (5, 'c')]
``````

In the shifted list, the new index of each item is not increased by 3 as would be the case for a true shift. The new index of an item is

``````new_index = (old_index + 3) % 6
``````

So it is actually a shift modulo n (the length of the list). The real question is: what do you want your program to do if the offset is negative or larger than the length of the list.

It shouold throw an error in that instance