Create evenly spaced multiples / samples within a range

A concrete example of a problem
I have an int range from 1 to 100. I want to generate n total numbers within this range that are equally spaced as possible and include the first and last values.

Example

start = 1, end = 100, n = 5   
Output: [1, 25, 50, 75, 100]

start = 1, end = 100, n = 4   
Output: [1, 33, 66, 100]

start = 1, end = 100, n = 2   
Output: [1, 100]

      

What I have now
I actually have a working approach, but all the time I feel like I've stopped thinking about it and skipped something simpler? Is this the most efficient approach or can it be improved?

def steps(start, end, n):
    n = min(end, max(n, 2) - 1)
    mult = end / float(n)
    yield start
    for scale in xrange(1, n+1):
        val = int(mult * scale)
        if val != start:
            yield val

      

Please note that I guarantee that this function will always return at least the lower and upper limits of the range. So, I forcen >= 2

Just for searching, I use this to sample frames of images from a rendered sequence where you usually want to get first, middle, last. But I wanted to be able to scale a little better to handle very long sequences of images and get better lighting.

Solved: from selected answer

I ended up using this slightly modified version of @ vartec's answer to be a generator and also closed the value n

for security:

def steps(start,end,n):
    n = min(end, max(n, 2))
    step = (end-start)/float(n-1)
    return (int(round(start+x*step)) for x in xrange(n))

      

+3


source to share


5 answers


You need correct rounding:



def steps(start,end,n):
    if n<2:
        raise Exception("behaviour not defined for n<2")
    step = (end-start)/float(n-1)
    return [int(round(start+x*step)) for x in range(n)]

      

+5


source


Additional dependency and possibly overkill , but short, proven and should give correct results: numpy.linspace



>>> numpy.linspace(1, 100, 4).astype(int).tolist()
[1, 34, 67, 100]

      

+4


source


The problem with usage range

is that the step has to be an integer and so you get rounding problems for example steps(1,100,4) == [1, 33, 66, 100]

. If you need whole outputs but want as many steps as possible, use a float as a step.

>>> def steps(start,end,n):
...    step = (end-start)/float(n-1)
...    return [int(round(start+i*step)) for i in range(n)]

>>> steps(1,100,5)
>>> [1, 26, 51, 75, 100]
>>> steps(1,100,4)
>>> [1, 34, 67, 100]
>>> steps(1,100,2)
>>> [1, 100]
>>>

      

+3


source


>>> from itertools import count
>>> def steps(start,end,n):
        yield start
        begin = start if start>1 else 0
        c = count(begin,(end-begin)/(n-1))
        next(c)
        for _ in range(n-2):
            yield next(c)
        yield end


>>> list(steps(1,100,2))
[1, 100]
>>> list(steps(1,100,5))
[1, 25, 50, 75, 100]
>>> list(steps(1,100,4))
[1, 33, 66, 100]
>>> list(steps(50,100,3))
[50, 75, 100]
>>> list(steps(10,100,10))
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

      

Can be reduced to

>>> from itertools import islice, count
>>> def steps(start,end,n):
        yield start
        begin = start if start>1 else 0
        c = islice(count(begin,(end-begin)/(n-1)),1,None)
        for _ in range(n-2):
            yield next(c)
        yield end

      

+1


source


What's wrong with using range ? This is how you can use it

>>> def steps(start,end,n):
    return [start]+range(start-1,end,end/(n-1))[1:]+[end]

>>> steps(1,100,5)
[1, 25, 50, 75, 100]
>>> steps(1,100,2)
[1, 100]
>>> 

      

0


source







All Articles