Björn Pollex
Björn Pollex

Reputation: 76788

How to generate an alternating range?

I need to search for an item in a list around a given index with in a given radius. Currently I use this function to generate alternating offsets for the search:

def generateSearchIndizes(radius):
    for i in range(1, radius + 1):
        yield i
        yield -i

The code that does the search looks something like this:

for i in generateSearchIndizes():
    if pred(myList[baseIndex + i]):
        result = myList[baseIndex + i]
        break # terminate search when first item is found

My question is, is there a more elegant way to generate the search indizes, maybe without defining a special function?

Upvotes: 6

Views: 1424

Answers (7)

tzot
tzot

Reputation: 95921

Your own solution, adding a yield 0 at the start of the generator, is the most straightforward (aka pythonic).

Here's an infinite offset generator with a different algorithm:

def gen_offsets():
    offset= 0
    yield offset
    step= 1; sign= 1
    while 1:
        offset+= sign*step
        yield offset
        step+= 1; sign= -sign

A more fanciful (aka not-so-pythonic :) way to write the algorithm above is:

import itertools as it, operator as op

def gen_offsets():
    steps= it.imap(op.mul, it.count(1), it.cycle( (1, -1) ))
    offset= 0
    yield offset
    for step in steps:
        offset+= step
        yield offset

Upvotes: 0

martineau
martineau

Reputation: 123453

This appears to me at least as readable as a separate function -- and arguably more understandable:

radius = 3
for outerbounds in ((-r,r) for r in range(1,radius+1)):
    for i in outerbounds :
        print i
# -1
# 1
# -2
# 2
# -3
# 3

Upvotes: 0

PaulMcG
PaulMcG

Reputation: 63709

Why alternate -i, i? Just do:

for i in range(-radius, radius+1):
    listitem = myList[baseIndex + i]
        if pred(listitem):
            return listitem  

Or if you absolutely must approach from front and back to get the outermost pred-matcher, how about:

for i in sorted(range(-radius, radius+1), key=abs):
    listitem = myList[baseIndex + i]
        if pred(listitem):
            return listitem  

If you have to do this often, just build sorted(range(-radius,radius+1),key=abs) once and keep it around for future iterating.

If you absolutely must not use the 0'th element, just insert a if not i: continue at the start of your loop.

Upvotes: 0

PaulMcG
PaulMcG

Reputation: 63709

Why not just do this with an inner loop, instead of creating a funky generator:

found = False
for i_abs in range(1, radius+1):
    for i in (i_abs, -i_abs):
        listitem = myList[baseIndex + i]
        if pred(listitem):
             result = listitem
             found = True
             break # terminate search when first item is found 
    if found:
        break
else:
    # handling in case no match found for pred

Some other comments:

  • you never test the 0'th element

  • since you are testing from both left and right, you should stop after i_abs reaches the halfway mark

  • I don't like repeating list indexing operations, would rather repeat a variable reference; so I lifted out the repeated myList[baseIndex+i] and assigned it to listitem

  • you should add some handling in case there is no matching element found

  • instead of breaking from the inner loop (which here requires an extra found variable to break out of the outer for loop), you might be better just returning result right from the inner loop,

as in:

for i_abs in range(1, radius+1):
    for i in (i_abs, -i_abs):
        listitem = myList[baseIndex + i]
        if pred(listitem):
             return listitem

Then there is no break management or found variable required.

Upvotes: 0

Here's my go at it:

from itertools import chain

>>> list(chain(*zip(range(1, 7), range(-7, 0)[::-1])))
[1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6]

Adjust as needed. :)

Upvotes: 2

Kos
Kos

Reputation: 72241

Perhaps a generator expression?

for i in (x/2 * (x%2 * 2 - 1) for x in xrange(2, radius*2)):
    print i

It is short and fits to your "without defining a special function" requirement...

But, franky, I'd still prefer to use that special function instead - just for the sake of having more clear code. :)

Upvotes: 0

Mark Byers
Mark Byers

Reputation: 838156

is there a more elegant way to generate the search indices

I don't think there's a more elegant way. Your code is very simple and clear.

maybe without defining a special function?

Yes, that's definitely possible.

>>> [b for a in ((x,-x) for x in range(1, 10 + 1)) for b in a]
[1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, 7, -7, 8, -8, 9, -9, 10, -10]

Upvotes: 4

Related Questions