Cycle around a sequence

M

Mark Lawrence

I'm looking at a way of cycling around a sequence i.e. starting at some
given location in the middle of a sequence and running to the end before
coming back to the beginning and running to the start place. About the
best I could come up with is the following, any better ideas for some
definition of better?

PythonWin 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit
(Intel)] on win32.
Portions Copyright 1994-2008 Mark Hammond - see 'Help/About PythonWin'
for further copyright information.
from itertools import chain
a=range(10)
g = chain((a for i in xrange(4, 10, 1)), (a for i in xrange(4)))
for x in g: print x,

....
4 5 6 7 8 9 0 1 2 3
 
C

Christoph Hansen

Mark said:
I'm looking at a way of cycling around a sequence i.e. starting at some
given location in the middle of a sequence and running to the end before
coming back to the beginning and running to the start place. About the
best I could come up with is the following, any better ideas for some
definition of better?

# quick&dirty

seq=range(10)
for x in seq[4:]+seq[:4]:
print x

# or

def oneround(seq, start=0):
i=start
l=len(seq)
while True:
yield seq
i = (i+1) % l
if i==start: break

for x in oneround(range(50), 4):
print x
 
N

Neil Cerutti

I'm looking at a way of cycling around a sequence i.e. starting
at some given location in the middle of a sequence and running
to the end before coming back to the beginning and running to
the start place. About the best I could come up with is the
following, any better ideas for some definition of better?

Python's indices were designed for these kinds of shenanigans.

def rotated(seq, n):
"""Iterate through all of seq, but starting from index n.
'3, 4, 0, 1, 2'
"""

i = n - len(seq)
while i < n:
yield seq
i += 1

if __name__ == "__main__":
import doctest
doctest.testmod()

If you have merely an iterable instead of a sequence, then look
to some of the other clever stuff already posted.
 
T

Terry Reedy

I'm looking at a way of cycling around a sequence i.e. starting
at some given location in the middle of a sequence and running
to the end before coming back to the beginning and running to
the start place. About the best I could come up with is the
following, any better ideas for some definition of better?

Python's indices were designed for these kinds of shenanigans.

def rotated(seq, n):
"""Iterate through all of seq, but starting from index n.
'3, 4, 0, 1, 2'
"""

i = n - len(seq)
while i< n:
yield seq
i += 1


This is really nice, in the category of "Why didn't I think of that?"
(Probably because I knew the % mod solution from C and never 'updated'!)
if __name__ == "__main__":
import doctest
doctest.testmod()

If you have merely an iterable instead of a sequence, then look
to some of the other clever stuff already posted.

To make a repeating rotator is only a slight adjustment:

k = n - len(seq)
while True:
i = k
while i < n:
yield seq
i += 1
 
M

Mark Lawrence

I'm looking at a way of cycling around a sequence i.e. starting
at some given location in the middle of a sequence and running
to the end before coming back to the beginning and running to
the start place. About the best I could come up with is the
following, any better ideas for some definition of better?

Python's indices were designed for these kinds of shenanigans.

def rotated(seq, n):
"""Iterate through all of seq, but starting from index n.
'3, 4, 0, 1, 2'
"""

i = n - len(seq)
while i< n:
yield seq
i += 1

if __name__ == "__main__":
import doctest
doctest.testmod()

If you have merely an iterable instead of a sequence, then look
to some of the other clever stuff already posted.


The winner :)
 
S

Steven D'Aprano

I'm looking at a way of cycling around a sequence i.e. starting at some
given location in the middle of a sequence and running to the end before
coming back to the beginning and running to the start place.

If you have a sequence, and don't mind copying it, the easiest way is
just to slice and join:

a = range(20)
b = a[5:] + a[:5]
print b
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1, 2, 3, 4]

Short, sweet, easy and simple. What's not to like about it?

For small (say, less than a few thousand of items) sequences, this
probably is the fastest way to do it.

Handling this lazily is trickier than it seems, because you have to store
the first N items somewhere until you get to the rest of the iterable.
There is no Right Way to do it, since the best solution will depend on
how many items you have and how large N is.

Here's one way with itertools:

[5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1, 2, 3, 4]

But read the docs for tee first: it may be that converting to a list is
faster and more memory efficient.

http://docs.python.org/library/itertools.html#itertools.tee


Using tee may be overkill. Here's a simpler way:
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1, 2, 3, 4]

If your data is truly humongous, already stored in a list, and you don't
want to make a copy, then I recommend your trick of generating the
indexes:

def cycle(seq, n):
for indexes in (xrange(n, len(seq)), xrange(n)):
for i in indexes:
yield seq

If your data is humongous but only available lazily, buy more memory :)
 
C

Chris Angelico

If your data is humongous but only available lazily, buy more memory :)

Or if you have a huge iterable and only need a small index into it,
snag those first few entries into a list, then yield everything else,
then yield the saved ones:

def cycle(seq,n):
seq=iter(seq)
lst=[next(seq) for i in range(n)]
try:
while True: yield next(seq)
except StopIteration:
for i in lst: yield i
[12, 13, 14, 15, 16, 17, 18, 19, 10, 11]

Requires storage space relative to n, regardless of the length of the iterator.

ChrisA
 
P

Peter Otten

Chris said:
Or if you have a huge iterable and only need a small index into it,
snag those first few entries into a list, then yield everything else,
then yield the saved ones:
def cycle(seq,n):
seq=iter(seq)
lst=[next(seq) for i in range(n)]
try:
while True: yield next(seq)
except StopIteration:
for i in lst: yield i

I think that should be spelt

def cycle2(seq, n):
seq = iter(seq)
head = [next(seq) for i in range(n)]
for item in seq:
yield item
for item in head:
yield item

or, if you are into itertools,

def cycle3(seq, n):
seq = iter(seq)
return chain(seq, list(islice(seq, n)))

$ python -m timeit -s'from tmp import cycle; data = range(1000); start=10'
'for item in cycle(data, 10): pass'
1000 loops, best of 3: 358 usec per loop
$ python -m timeit -s'from tmp import cycle2; data = range(1000); start=10'
'for item in cycle2(data, 10): pass'
1000 loops, best of 3: 172 usec per loop
$ python -m timeit -s'from tmp import cycle3; data = range(1000); start=10'
'for item in cycle3(data, 10): pass'
10000 loops, best of 3: 56.5 usec per loop

For reference:

$ python -m timeit -s'data = range(1000); start=10' 'for item in
data[start:] + data[:start]: pass'
10000 loops, best of 3: 56.4 usec per loop
 
S

Serhiy Storchaka

08.02.12 22:15, Terry Reedy напиÑав(ла):
To make a repeating rotator is only a slight adjustment:

k = n - len(seq)
while True:
i = k
while i < n:
yield seq
i += 1


for i in range(n, len(seq)):
yield seq
while True:
for x in seq:
yield x
 
C

Chris Angelico

Chris said:
def cycle(seq,n):
        seq=iter(seq)
        lst=[next(seq) for i in range(n)]
        try:
                while True: yield next(seq)
        except StopIteration:
                for i in lst: yield i

I think that should be spelt

def cycle2(seq, n):
   seq = iter(seq)
   head = [next(seq) for i in range(n)]
   for item in seq:
       yield item
   for item in head:
       yield item

Thanks, yeah, don't know what I was thinking :) Too much C work lately!

ChrisA
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,058
Latest member
QQXCharlot

Latest Threads

Top