Skip to content

Fix RedisDeque.rotate() when abs(n) > len(d) - #857

Merged
brainix merged 1 commit into
brainix:masterfrom
Master-Norna:fix/issue-849-rotate-overflow
Oct 10, 2026
Merged

brainix merged 1 commit into
brainix:masterfrom
Master-Norna:fix/issue-849-rotate-overflow

Conversation

@Master-Norna

Copy link
Copy Markdown
Contributor

Fixes #849

Problem

RedisDeque.rotate(n) produced a wrong ordering (and, for negative n, even dropped elements) whenever abs(n) exceeded the deque length. collections.deque - which the class documents itself as compatible with - reduces n modulo the length, so rotation wraps; RedisDeque instead sliced and trimmed with the un-reduced n.

Reproduced locally with fakeredis before the fix:

[1, 2, 3].rotate(4)  -> [1, 2, 3]   (expected [3, 1, 2])
[1, 2, 3].rotate(-4) -> [2, 3]      (expected [2, 3, 1])

Fix

Reduce n to an equivalent rotation before the existing slice/trim logic, rotating the shorter way to minimize the number of elements pushed (the original code already pushed min(n, len) elements in the base case, so this keeps that):

length = len(self)
n %= length
if n > length // 2:
    n -= length
if n == 0:
    return

I did not use the bare n %= len(self) suggested in the issue, because it would turn a small negative rotation into a large positive one - e.g. rotate(-2) on a 10-element deque becomes -2 % 10 == 8, pushing 8 elements instead of 2. The shorter-way normalization keeps the push count at min(n, length), matching the original behavior and avoiding a performance regression. A whole number of turns (n == 0 after reduction) is now an explicit no-op instead of a push+trim round trip.

Tests

Added test_rotate_more_than_length covering abs(n) > len(d) in both directions plus the exact-wrap cases, which the issue notes were not covered. Re-ran the existing rotate tests (invalid steps, zero, empty, right, left) against fakeredis - all pass.

Verified locally: flake8 / isort clean on both touched files; mypy shows no new errors versus master; behavior checked against collections.deque.rotate for n in {1, -1, 3, -3, 4, -4, 6, -6, 7, -7, 10, -10} plus the empty-deque no-op.

Reduce n to the shorter equivalent rotation so rotating past the length wraps like collections.deque.rotate(), instead of slicing with the un-reduced n.
@Master-Norna

Copy link
Copy Markdown
Contributor Author

Hi @brainix - the CI run is sitting at "Action required" (fork PR approval). Could you approve the workflow run so the checks can complete? The change is small: pottery/deque.py reduces n to the shorter equivalent rotation before the existing slice/trim logic, plus a parametrized regression test in tests/test_deque.py. I verified it locally against collections.deque.rotate for a range of n (including the overflow cases from #849) using fakeredis, and ran flake8/isort/mypy with no new findings versus master.

@brainix
brainix merged commit 4e98b44 into brainix:master Oct 10, 2026
5 checks passed
@brainix

brainix commented Oct 10, 2026

Copy link
Copy Markdown
Owner

Thank you for the contribution!

@brainix

brainix commented Oct 10, 2026

Copy link
Copy Markdown
Owner

By the way, this is the most brilliant pull request I've seen. The only thing that rivals your ability to write code is your ability to explain your reasoning.

@brainix brainix self-assigned this Oct 17, 2026
@brainix brainix added the bug label Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RedisDeque.rotate() gives wrong results when abs(n) is greater than the deque's length

2 participants