The wrapping is free and the empty case is not. Read the last line before you write anything.
print(7 % 3) # -> 1 seven is one past two full turns print(3 % 3) # -> 0 a whole number of turns is no rotation print(-1 % 3) # -> 2 Python wraps to a positive remainder print(5 % 0) # ZeroDivisionError: integer modulo by zero
- Modulo turns any rotation into one within range
Rotating a three-item list by seven is the same as rotating it by one, and % says so in one character. Without it, slicing past the end simply returns the whole list plus nothing, and the rotation silently stops happening.
- Python’s % is positive for a positive divisor
-1 % 3 is 2, not -1. That is what makes negative rotation work with no special case at all: rotating back by one is the same as rotating forward by two. In languages that truncate towards zero this same code needs an extra branch.
- The fix for one trap creates the other
Using % means dividing by the length — and the length of an empty list is zero. So the guard is not defensive padding, it is required by the technique. Getting the empty case right is the whole reason this is not a one-line function.
A rotation is two slices swapped over: everything from a point onwards, followed by everything before it. The only question is what that point is.
places % len(items) gives a shift inside the list, handling both over-long and negative rotations. Then items[shift:] + items[:shift] is the answer.
Guard the empty list first and return [] — not because empty is special, but because % by zero raises. Then compute the shift and return the two slices joined. No loop, no branching for negatives.
def rotate(items, places): if not items: return ____ shift = places ____ len(items) return items[____:] + items[:____]
def rotate(items, places):
if not items:
return []
shift = places % len(items)
return items[shift:] + items[:shift]
Without % you would not divide, and the empty list would slice to empty quite happily — but rotations beyond the length would silently do nothing. Adding % fixes that and introduces the division by zero. It is a good example of a fix carrying its own new edge case, which is worth expecting rather than being surprised by.
Two cases here are negative and neither is mentioned in the solution. Python defines % so the result takes the sign of the divisor, and since the length is always positive the shift always lands inside the list. Knowing that saves a branch that would otherwise be wrong for -4.
Both halves are new lists and joining them makes a third, so the caller’s list is untouched without any copying being written down. The in-place alternative — popping from the front and appending — is O(n) per item as well as destructive.
collections.deque has a rotate method that works in place and in constant time per step, which matters if you are rotating a large structure repeatedly — a scheduler, say, rather than a one-off. If you only need to *read* items in a cycle rather than produce a rotated copy, itertools.cycle avoids building anything at all. And if the rotation is by one, popping and appending on a deque is clearer than slicing.
- Rotate in place instead, returning None, and decide when that would be the better API.
- Write it with collections.deque and compare the two on a list of a million items rotated a thousand times.
- Handle a rotation of a string, and notice how little changes — and what the guard has to return instead of [].