Python: shift elements in a list with constant length
Question:
I’m looking for an elegant way to write a simple function that would shift the elements of list by a given number of positions, while keeping the list of the same length and padding empty positions with a default value. This would be the docstring of the function:
def shift_list(l, shift, empty=0):
"""
Shifts the elements of a list **l** of **shift** positions,
padding new items with **empty**::
>>> l = [0, 1, 4, 5, 7, 0]
>>> shift_list(l, 3)
[0, 0, 0, 0, 1, 4]
>>> shift_list(l, -3)
[5, 7, 0, 0, 0, 0]
>>> shift_list(l, -8)
[0, 0, 0, 0, 0, 0]
"""
pass
How would you proceed ? Any help greatly appreciated !
Answers:
Would this do it?
def shift_list(l, shift, empty=0):
while shift > 0:
l.insert(0,empty)
l.pop()
shift -= 1
return l
I’d use slice assignment:
def shift_list(l, shift, empty=0):
src_index = max(-shift, 0)
dst_index = max(shift, 0)
length = max(len(l) - abs(shift), 0)
new_l = [empty] * len(l)
new_l[dst_index:dst_index + length] = l[src_index:src_index + length]
return new_l
I would do it like this:
def shift_list(l, shift, empty=0):
if shift > 0:
return [empty] * shift + l[:-shift]
return l[-shift:] + [empty] * abs(shift)
A bit naïve, but job’s done.
def shift(l, shift, empty=0):
reverse = False
if shift < 0:
reverse = True
shift = abs(shift)
while (shift > 0) :
if reverse:
l.pop(0)
l.append(empty)
else:
l.pop()
l.insert(0, empty)
shift-=1
l = [0, 1, 4, 5, 7, 0]
shift(l, 3)
print l
l = [0, 1, 4, 5, 7, 0]
shift(l, -3)
print l
l = [0, 1, 4, 5, 7, 0]
shift(l, -8)
print l
A non intuitive way of rotating a list
def shift_list(l, shift, empty=0):
l1=[empty]*len(l)+l+[empty]*len(l)
l1=l1[len(l)-shift:][0:len(l)]
return l1+[empty]*(len(l)-len(l1))
If you can use a deque instead of a list, it has a few nice features that make it a natural choice for this problem. Working from the left of a deque is much less expensive than doing so on a list. Also it has a maxlen argument that can be used to make the code prettier and faster. I’m rather sure using itertools.repeat
instead of [empty] * n
is more efficient, both memory-wize and with regards to speed.
from collections import deque
from itertools import repeat
def shift_list(l, n, empty=0):
d = deque(l, maxlen=len(l))
if n > 0:
d.extendleft(repeat(empty, min(n, len(l))))
elif n < 0:
d.extend(repeat(empty, min(-n, len(l))))
# return list(d) to pass your doctest, at the cost of performance
return d
A bit of a warning though — while the time complexity of iterating over the elements of a deque is comparable to doing the same on a list, item lookup may be rather expensive — it depends on the index: s[0]
and s[-1]
is very fast, s[len(s)/2]
is the most expensive. So if you do a lot of lookups, consider either using another solution, or converting the result back to a list. See this page for an overview.
The following method does not answer the question, but I still find it helpful for those who would like to shift the valuse like a circle.
# Shift to the left.
shift = 1
a = [1, 2, 3, 4]
a = a[shift:] + a[:shift]
# [2, 3, 4, 1]
or
# Shift to the right.
shift = -1
a = [1, 2, 3, 4]
a = a[shift:] + a[:shift]
# [4, 1, 2, 3]
I’m looking for an elegant way to write a simple function that would shift the elements of list by a given number of positions, while keeping the list of the same length and padding empty positions with a default value. This would be the docstring of the function:
def shift_list(l, shift, empty=0):
"""
Shifts the elements of a list **l** of **shift** positions,
padding new items with **empty**::
>>> l = [0, 1, 4, 5, 7, 0]
>>> shift_list(l, 3)
[0, 0, 0, 0, 1, 4]
>>> shift_list(l, -3)
[5, 7, 0, 0, 0, 0]
>>> shift_list(l, -8)
[0, 0, 0, 0, 0, 0]
"""
pass
How would you proceed ? Any help greatly appreciated !
Would this do it?
def shift_list(l, shift, empty=0):
while shift > 0:
l.insert(0,empty)
l.pop()
shift -= 1
return l
I’d use slice assignment:
def shift_list(l, shift, empty=0):
src_index = max(-shift, 0)
dst_index = max(shift, 0)
length = max(len(l) - abs(shift), 0)
new_l = [empty] * len(l)
new_l[dst_index:dst_index + length] = l[src_index:src_index + length]
return new_l
I would do it like this:
def shift_list(l, shift, empty=0):
if shift > 0:
return [empty] * shift + l[:-shift]
return l[-shift:] + [empty] * abs(shift)
A bit naïve, but job’s done.
def shift(l, shift, empty=0):
reverse = False
if shift < 0:
reverse = True
shift = abs(shift)
while (shift > 0) :
if reverse:
l.pop(0)
l.append(empty)
else:
l.pop()
l.insert(0, empty)
shift-=1
l = [0, 1, 4, 5, 7, 0]
shift(l, 3)
print l
l = [0, 1, 4, 5, 7, 0]
shift(l, -3)
print l
l = [0, 1, 4, 5, 7, 0]
shift(l, -8)
print l
A non intuitive way of rotating a list
def shift_list(l, shift, empty=0):
l1=[empty]*len(l)+l+[empty]*len(l)
l1=l1[len(l)-shift:][0:len(l)]
return l1+[empty]*(len(l)-len(l1))
If you can use a deque instead of a list, it has a few nice features that make it a natural choice for this problem. Working from the left of a deque is much less expensive than doing so on a list. Also it has a maxlen argument that can be used to make the code prettier and faster. I’m rather sure using itertools.repeat
instead of [empty] * n
is more efficient, both memory-wize and with regards to speed.
from collections import deque
from itertools import repeat
def shift_list(l, n, empty=0):
d = deque(l, maxlen=len(l))
if n > 0:
d.extendleft(repeat(empty, min(n, len(l))))
elif n < 0:
d.extend(repeat(empty, min(-n, len(l))))
# return list(d) to pass your doctest, at the cost of performance
return d
A bit of a warning though — while the time complexity of iterating over the elements of a deque is comparable to doing the same on a list, item lookup may be rather expensive — it depends on the index: s[0]
and s[-1]
is very fast, s[len(s)/2]
is the most expensive. So if you do a lot of lookups, consider either using another solution, or converting the result back to a list. See this page for an overview.
The following method does not answer the question, but I still find it helpful for those who would like to shift the valuse like a circle.
# Shift to the left.
shift = 1
a = [1, 2, 3, 4]
a = a[shift:] + a[:shift]
# [2, 3, 4, 1]
or
# Shift to the right.
shift = -1
a = [1, 2, 3, 4]
a = a[shift:] + a[:shift]
# [4, 1, 2, 3]