5

Consider a string '1234'. You need a function which generates all the rotations: '1234', '3412', '4123', '2341'. I created a simple test suite:

assert rotations('123') == set(['123', '231', '312'])
assert rotations('111') == set(['111'])
assert rotations('197') == set(['197', '971', '719'])

What is the pythonic way to do that? I finished with code below

def rotations(num):
    str_num = str(num)
    result = set()
    for mid in xrange(len(str_num)):
        result.add(
            str_num[mid:] + str_num[:mid]
        )
    return result
kharandziuk
  • 12,020
  • 17
  • 63
  • 121
  • 3
    If this is **working code** that you think could be improved, see [codereview.se]. – jonrsharpe Nov 04 '15 at 12:01
  • 4
    maybe a set comprehension? `{s[mid:] + s[:mid] for mid in range(len(s))}` – Niklas B. Nov 04 '15 at 12:05
  • 1
    What you have is Pythonic enough -- though @NiklasB.'s comment, which perhaps deserves to be an accepted answer, is probably more so (though replace `range` by `xrange` in Python 2) – John Coleman Nov 04 '15 at 12:10
  • I remember using the `itertools` package to form permutations, but I kinda like that slicing approach better. – OneCricketeer Nov 04 '15 at 12:22
  • if you want more "verbose" code: Use itertools's cycle combined with "chunking" See http://stackoverflow.com/questions/434287/what-is-the-most-pythonic-way-to-iterate-over-a-list-in-chunks?lq=1 . Btw. your function should return a set of strings, not a set with a single list of strings if you do not care for order of the elements. – Tommy Nov 04 '15 at 12:38

3 Answers3

4
s='1234'
z = {s[x:]+s[:x] for x in range(len(s))}
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
Saiful Azad
  • 1,823
  • 3
  • 17
  • 27
3

Your second example suggests that your approach isn't efficient if the number is periodic (e.g. 123123123123). As a solution -- check if a given rotation has already appeared. If so -- the sequence is periodic and you have already discovered all distinguishable rotations, so just return the result:

def rotations(num):
    str_num = str(num)
    result = set()
    for mid in range(len(str_num)):
        rot = str_num[mid:] + str_num[:mid]
        if rot in result:
            return result
        else:
            result.add(rot)
    return result

(I changed your xrange to range since I am using Python 3 -- you can of course change back).

John Coleman
  • 51,337
  • 7
  • 54
  • 119
2

Although definitely not the fastest or necessarily most Pythonic answer, you could use collections.deque for this.

from collections import deque

def rotations(s):
    s_dq = deque(s)
    result = set()
    for _ in xrange(len(s_dq)):
        s_dq.rotate(1)
        result.add(''.join(s_dq))
    return result

And it passes all of your example tests:

def main():
    tests = ['123',
             '111',
             '197']

    desired = [set(['123', '231', '312']),
               set(['111']),
               set(['197', '971', '719'])]

    for test, result in zip(tests, desired):
        print rotations(test) == result

if __name__ == '__main__':
    main()

True

True

True

I would not recommend that you use collections.deque for this particular problem, but it can be applied to more complicated versions of problems like this one. It is a very useful and, in my opinion, underused tool--that's why I included it as an answer to this question.

pzp
  • 6,249
  • 1
  • 26
  • 38