favoretti
favoretti

Reputation: 30197

Generate list of range tuples with given boundaries in python

[edit] I'm not sure if this better off to codereview, if so, please migrate :) thanks!

So we were sitting here, working on a semi-academic problem.

Given a start, stop and step, generate a list of range tuples, so that

gen_range(100, 140, 10)

would produce

[(100, 110), (110, 120), (120, 130), (130, 140)]

also, given then fact that it should work on, say, iterating 500M integers with a step of 100 and not take forever.

The implementation I've come up with is as follows:

def gen_range(start, stop, step):
    llist = range(start, stop+step, step)
    batch_list = []

    if llist[-1] > stop:
        llist[-1] = stop

    for a, b in enumerate(llist[:-1]):
        batch_list.append((llist[a], llist[a+1]))

    print batch_list

gen_range(100000000,600000000,100)

But I can't stop thinking that it can be done in a more efficient (also code-length wise) way. Any suggestions?

[edit]

One thing I forgot to point out. If the range boundary is not equal to the step, i.e. you have a case of:

gen_range(100, 143, 10)

the upper boundary should be 143, and not 150, as some answers here would produce, due to range() internals.

Upvotes: 12

Views: 40002

Answers (7)

Bin Chen
Bin Chen

Reputation: 81

I think generator is a good try:

def gen_range(start, stop, step):
    while start < stop:
        yield (start, min(start + step, stop))
        start = start + step

if __name__ == '__main__':
    print [x for x in gen_range(100, 143, 10)]

Upvotes: 1

tanantish
tanantish

Reputation: 284

I'd go with the generator approach, but if you're going to be unpacking the thing into an explicit list, another way to look at it is that you've got a really simple relationship between the elements of your tuple, and all you need to do is range the length properly.

def gen_range(start, stop, step):
   items = ((stop-start) // step)
   return [(start+(n*step), start+(n+1)*step) for n in range(items)]

The only reason I figured it's worth posting this is that my first thought (and hence the comment query) whether or not you could get away with just asking for the nth tuple in the list without having to generate the lot. If you can't, then I wouldn't go down this angle.

Upvotes: 1

martineau
martineau

Reputation: 123511

I'd make it a generator so it could handle huge ranges.

def gen_range(start, stop, step):
    a, b = start, start+step
    while b <= stop:
        yield a, b
        a, b = b, b+step

print list(gen_range(100, 140, 10))

Upvotes: 1

irrelephant
irrelephant

Reputation: 4111

Maybe code-length wise if (b - a) % c == 0:

def gen_range(a, b, c):
    return [(i, i + c) for i in range(a, b, c)]

or

def gen_range_2(a, b, c):
    s = range(a, b + c, c)
    return zip(s, s[1:])

See http://ideone.com/VhY215

If (b - a) % c != 0:

def gen_range(a, b, c):
    return [(i, i + c) for i in range(a, b - ((b - a) % c), c)]

or

def gen_range_2(a, b, c):
    s = range(a, b - ((b - a) % c) + c, c)
    return zip(s, s[1:])

See http://ideone.com/i3Pq69

Upvotes: 2

Amber
Amber

Reputation: 527213

A really short way:

ranges = [(n, min(n+step, stop)) for n in xrange(start, stop, step)]

More verbose ways:

Perhaps via a generator?

def gen_range(start, stop, step):
    current = start
    while current < stop:
        next_current = current + step
        if next_current < stop:
            yield (current, next_current)
        else:
            yield (current, stop)
        current = next_current

Calling this function gives you a generator object that will produce each of the tuples in order. You would use it like this:

for block in gen_range(100000000,600000000,100):
    print block

which would output...

(100000000,100000100)
(100000100,100000200)
(100000200,100000300)
...
(599999900,600000000)

You could do this even more simply with a generator expression if you're always certain that stop-start is an even multiple of the step:

ranges = ((n, n+step) for n in xrange(start, stop, step))

# Usage
for block in ranges:
    print block

Also note that if you want to turn a generator into a list, holding the entirety of the results in memory, you can simply pass it to list():

all_ranges = list(gen_range(100000000,600000000,100))

Upvotes: 11

Ashwini Chaudhary
Ashwini Chaudhary

Reputation: 251116

something like this:

In [48]: it=iter(xrange(100,200,10))

In [49]: it1=iter(xrange(110,210,10))

In [50]: [(next(it),next(it1)) for _ in range(10)]
Out[50]: 
[(100, 110),
 (110, 120),
 (120, 130),
 (130, 140),
 (140, 150),
 (150, 160),
 (160, 170),
 (170, 180),
 (180, 190),
 (190, 200)]

or using zip():

In [55]: it=iter(xrange(100,200,10))

In [56]: it1=iter(xrange(110,210,10))

In [57]: [(x,y) for x,y in zip(it,it1)]
Out[57]: 
[(100, 110),
 (110, 120),
 (120, 130),
 (130, 140),
 (140, 150),
 (150, 160),
 (160, 170),
 (170, 180),
 (180, 190),
 (190, 200)]

Upvotes: 1

EnricoGiampieri
EnricoGiampieri

Reputation: 6095

I'm not sure if I correctly understood the problem, but this can be simply done with a list comprehension:

start = 100
stop = 140
step = 10
[ range(s,s+step+1,step) for s in range(start,stop,step)]

if you need only to iterate over your list element one at the time, I strongly suggest you to use a generator comprehension instead:

res = ( range(s,s+step+1,step) for s in range(start,stop,step))

You can iterate over it in a cycle:

for range_piece in res:
    do_something with it

Upvotes: 1

Related Questions