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]

Edited 2 Years Ago by chophouse: add result

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 2 Years Ago by Gribouillis

Thanks pyTony,

I made two changes:
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 2 Years Ago 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 2 Years Ago 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 2 Years Ago by Gribouillis

Grub,

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

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.

This question has already been answered. Start a new discussion instead.