tags:

views:

218

answers:

5

Hello,

I'm building a range between two numbers (floats) and I'd like this range to be of an exact fixed length (no more, no less). range and arange work with steps, instead. To put things into pseudo Python, this is what I'd like to achieve:

start_value = -7.5
end_value = 0.1
my_range = my_range_function(star_value, end_value, length=6)

print my_range
[-7.50,-5.98,-4.46,-2.94,-1.42,0.10]

This is essentially equivalent to the R function seq which can specify a sequence of a given length. Is this possible in Python?

Thanks.

+4  A: 

See Recipe 66472: frange(), a range function with float increments (Python) with various float implementations, their pros and cons.

Alternatively, if precision is important to you, work with decimal.Decimal instead of float (convert to and then back) as answered in Python decimal range() step value.

van
`numpy.arange` does the trick for that. The problem is that those functions use steps, while I am requiring an exact length beforehand.
Einar
does that really stop you: `inc = (end-start)/length` before using the function, or have another function that does that and calls real implementation? Still, if you want any EXACTness, you should try to to work with Decimal instead, then convert back to float.
van
+1  A: 
def my_function(start, end, length):
    len = length - 1
    incr = (end-start) / len
    r = [ start ]
    for i in range(len):
        r.append ( r[i] + incr )
    return r
Richard
+1  A: 

How about this:

def my_range_function(start, end, length):
    if length <= 1: return [ start ]
    step = (end - start) / (length - 1)
    return [(start + i * step) for i in xrange(length)]

For your sample range, it returns:

[-7.5, -5.9800000000000004, -4.46,
 -2.9399999999999995, -1.4199999999999999, 0.099999999999999645]

Of course it's full of round errors, but that's what you get when working with floats.

unwind
Thanks, this does the trick.
Einar
+4  A: 

Use linspace() from NumPy.

>>> from numpy import linspace
>>> linspace(-7.5, 0.1, 6)
array([-7.5 , -5.98, -4.46, -2.94, -1.42,  0.1])
>>> linspace(-7.5, 0.1, 6).tolist()
[-7.5, -5.9800000000000004, -4.46, -2.9399999999999995, -1.4199999999999999, 0.10000000000000001]

It should be the most efficient and accurate.

Leonid Shvechikov
This also works very nicely. Thanks!
Einar
Is there some advantage to calling the `tolist()` member function rather than using the `list` function that takes an iterator, `list(linspace(...))`?
Seth Johnson
i knew that somewhere there would be a func to do it!!
Richard
+1  A: 

In order to handle the rounding errors, the following code utilizes Python's decimal module. You can set the rounding; for this sample I've set it to two decimal points via round_setting = '.01'. In order to handle any rounding errors, the last step is adjusted to the remainder.

Code

#!/usr/bin/env python
# encoding: utf-8
from __future__ import print_function
import math
import decimal


start_value = -7.5
end_value = 0.1
num_of_steps = 6

def my_range(start_value, end_value, num_of_steps):
    round_setting = '.01'
    start_decimal = decimal.Decimal(str(start_value)).quantize(
        decimal.Decimal(round_setting))
    end_decimal = decimal.Decimal(str(end_value)).quantize(
        decimal.Decimal(round_setting))
    num_of_steps_decimal = decimal.Decimal(str(num_of_steps)).quantize(
        decimal.Decimal(round_setting))
    step_decimal = ((end_decimal - start_decimal) / 
        num_of_steps_decimal).quantize(decimal.Decimal(round_setting))
    # Change the last step in case there are rounding errors
    last_step_decimal = (end_decimal - ((num_of_steps - 1) * step_decimal) -
            start_decimal).quantize(decimal.Decimal(round_setting))
    print('Start value = ', start_decimal)
    print('End value = ', end_decimal)
    print('Number of steps = ', num_of_steps)
    print('Normal step for range = ', step_decimal)
    print('Last step used for range = ', last_step_decimal)

my_range(start_value, end_value, num_of_steps)

Output

$ ./fixed_range.py 
Start value =  -7.50
End value =  0.10
Number of steps =  6
Normal step for range =  1.27
Last step used for range =  1.25

From there you can use the normal step and the last step to create your list.

Matthew Rankin