Repository: sympy/sympy
Pull request: Function for rotation in iterables
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
Fixes #16127
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234". See
https://github.com/blog/1506-closing-issues-via-pull-requests . Please also
write a comment on that issue linking back to this pull request once it is
open. -->


#### Brief description of what is fixed or changed
Function yielding sequence rotations has been added to iterables.py of the utilities module.

#### Other comments


#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
*  utilities
   *  `rotations` function added to iterables.py
<!-- END RELEASE NOTES -->


Problem statement:
iterables could use rotations
Would a simple routine to return the rotations of a sequence be something useful in iterables?
```python
def rotations(s, dir=1):
    """Return a generator giving the items in s as list where
    each subsequent list has the items rotated to the left (default)
    or right (dir=-1) relative to the previous list.

    Examples
    ========

    >>> list(rotations([1,2,3]))
    [[1, 2, 3], [2, 3, 1], [3, 1, 2]]
    >>> list(rotations([1,2,3], -1))
    [[1, 2, 3], [3, 1, 2], [2, 3, 1]]
    """
    seq = list(s)
    for i in range(len(seq)):
        yield seq
        seq = rotate_left(seq, dir)
```

Hints from issue discussion:
I would like to take this.

Commit message: trailing-whitespaces

Patch to review:
diff --git a/sympy/utilities/__init__.py b/sympy/utilities/__init__.py
index 4a5f8d59608f..96bb668185b6 100644
--- a/sympy/utilities/__init__.py
+++ b/sympy/utilities/__init__.py
@@ -5,7 +5,8 @@
     variations, numbered_symbols, cartes, capture, dict_merge,
     postorder_traversal, interactive_traversal,
     prefixes, postfixes, sift, topological_sort, unflatten,
-    has_dups, has_variety, reshape, default_sort_key, ordered)
+    has_dups, has_variety, reshape, default_sort_key, ordered,
+    rotations)
 
 from .misc import filldedent
 
diff --git a/sympy/utilities/iterables.py b/sympy/utilities/iterables.py
index 935bef529bc3..a03d97fa6124 100644
--- a/sympy/utilities/iterables.py
+++ b/sympy/utilities/iterables.py
@@ -2340,3 +2340,23 @@ def signed_permutations(t):
     """
     return (type(t)(i) for j in permutations(t)
         for i in permute_signs(j))
+
+
+def rotations(s, dir=1):
+    """Return a generator giving the items in s as list where
+    each subsequent list has the items rotated to the left (default)
+    or right (dir=-1) relative to the previous list.
+
+    Examples
+    ========
+
+    >>> from sympy.utilities.iterables import rotations
+    >>> list(rotations([1,2,3]))
+    [[1, 2, 3], [2, 3, 1], [3, 1, 2]]
+    >>> list(rotations([1,2,3], -1))
+    [[1, 2, 3], [3, 1, 2], [2, 3, 1]]
+    """
+    seq = list(s)
+    for i in range(len(seq)):
+        yield seq
+        seq = rotate_left(seq, dir)
diff --git a/sympy/utilities/tests/test_iterables.py b/sympy/utilities/tests/test_iterables.py
index 9998af33e1b0..d50acef52e2f 100644
--- a/sympy/utilities/tests/test_iterables.py
+++ b/sympy/utilities/tests/test_iterables.py
@@ -15,7 +15,7 @@
     multiset_permutations, necklaces, numbered_symbols, ordered, partitions,
     permutations, postfixes, postorder_traversal, prefixes, reshape,
     rotate_left, rotate_right, runs, sift, subsets, take, topological_sort,
-    unflatten, uniq, variations, ordered_partitions)
+    unflatten, uniq, variations, ordered_partitions, rotations)
 from sympy.utilities.enumerative import (
     factoring_visitor, multiset_partitions_taocp )
 
@@ -732,3 +732,14 @@ def test_ordered_partitions():
                 sum(1 for p in f(i, j, 1)) ==
                 sum(1 for p in f(i, j, 0)) ==
                 nT(i, j))
+
+
+def test_rotations():
+    assert list(rotations([10,-20,20])) == [[10, -20, 20], [-20, 20, 10], [20, 10, -20]]
+    assert list(rotations([10,-20,20,10])) == [[10, -20, 20, 10], [-20, 20, 10, 10],                                                     [20, 10, 10, -20],[10, 10, -20, 20]]
+    assert list(rotations([-1,1,0.5], -1)) == [[-1, 1, 0.5], [0.5, -1, 1], [1, 0.5, -1]]
+    assert list(rotations([4,3,2,1], -1)) == [[4, 3, 2, 1], [1, 4, 3, 2], [2, 1, 4, 3], [3, 2, 1, 4]]
+
+    assert list(rotations(['a', 'b'], -1)) == [['a', 'b'], ['b', 'a']]
+    assert list(rotations(['s', 'y', 'm', 'p', 'y'])) == [['s', 'y', 'm', 'p', 'y'], ['y', 'm', 'p', 'y', 's'],
+                          ['m', 'p', 'y', 's', 'y'], ['p', 'y', 's', 'y', 'm'], ['y', 's', 'y', 'm', 'p']]


Code review comments:
diff_hunk: @@ -732,3 +732,14 @@ def test_ordered_partitions():
                 sum(1 for p in f(i, j, 1)) ==
                 sum(1 for p in f(i, j, 0)) ==
                 nT(i, j))
+
+
+def test_rotations():
+    assert list(rotations([10,-20,20])) == [[10, -20, 20], [-20, 20, 10], [20, 10, -20]]
path: sympy/utilities/tests/test_iterables.py
text: @user1:
You can thin this out a bit: perhaps test `'ab'`, `range(3)`, and one of those with `dir=-1`.