rango () para flotadores

140

¿Hay un range()equivalente para flotadores en Python?

>>> range(0.5,5,1.5)
[0, 1, 2, 3, 4]
>>> range(0.5,5,0.5)

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    range(0.5,5,0.5)
ValueError: range() step argument must not be zero
Jonathan
fuente
1
Esas no son fracciones sino flotadores. Y las carrozas son ... bueno, es probable que den resultados diferentes de lo esperado.
66
Una solución rápida sería tratar los enteros como decimales, por ejemplo: range(5, 50, 5)y luego dividir cada número entre 10.
NullUserException
@delnan - actualizado. Estoy dispuesto a aceptar inexactitudes mínimas por la conveniencia de tener un rango de flotación
Jonathan
2
posible duplicado del valor de paso de rango decimal ()
Jonathan
@NullUserException - esto es solo un ejemplo - el código real es por supuesto paramétrico :)
Jonathan

Respuestas:

97

No conozco una función incorporada, pero escribir una como esta no debería ser demasiado complicado.

def frange(x, y, jump):
  while x < y:
    yield x
    x += jump

Como mencionan los comentarios, esto podría producir resultados impredecibles como:

>>> list(frange(0, 100, 0.1))[-1]
99.9999999999986

Para obtener el resultado esperado, puede usar una de las otras respuestas en esta pregunta, o como mencionó @Tadhg, puede usarlo decimal.Decimalcomo jumpargumento. Asegúrese de inicializarlo con una cadena en lugar de un flotador.

>>> import decimal
>>> list(frange(0, 100, decimal.Decimal('0.1')))[-1]
Decimal('99.9')

O incluso:

import decimal

def drange(x, y, jump):
  while x < y:
    yield float(x)
    x += decimal.Decimal(jump)

Y entonces:

>>> list(drange(0, 100, '0.1'))[-1]
99.9
kichik
fuente
34
El lema de Python es en realidad Debe haber una, y preferiblemente solo una, forma obvia de hacerlo . Pero de todos modos, Python es increíble :)
Jonathan
3
>>> print list(frange(0,100,0.1))[-1]==100.0seráFalse
Volodimir Kopey
frangepuede funcionar inesperadamente Debido a la maldición de la aritmética de coma flotante , por ejemplo, frange(0.0, 1.0, 0.1)arroja 11 valores, donde está el último valor 0.9999999999999999. Una mejora práctica sería while x + sys.float_info.epsilon < y:aunque incluso esto probablemente pueda fallar con grandes números .
Akseli Palén
10
-1 Por favor, no use este código , al menos no en software que pueda afectar mi vida. No hay forma de hacerlo funcionar de manera confiable. Tampoco uses la respuesta de Akseli Palén. Use la respuesta de Xaerxess o wim (excepto ignore la parte sobre un rango).
benrg
3
esto funciona muy bien si lo usadecimal.Decimal como paso en lugar de flotadores.
Tadhg McDonald-Jensen el
112

Puedes usar:

[x / 10.0 for x in range(5, 50, 15)]

o use lambda / map:

map(lambda x: x/10.0, range(5, 50, 15))
Xaerxess
fuente
1
Y array (rango (5,50,15)) / 10.0 como arreglos numpy tienen operadores para manejar la división, multiplicación, etc.
edvaldig
2
@edvaldig: tienes razón, no sabía sobre esto ... Sin embargo, creo que arange(0.5, 5, 1.5)es IMO más legible.
Xaerxess
2
Prefiero esta respuesta sobre la aceptada, porque las dos primeras soluciones presentadas se basan en iterar sobre enteros y derivar los flotantes finales de los enteros. Esto es más robusto. Si lo hace directamente con flotadores, corre el riesgo de tener errores únicos extraños debido a cómo los flotadores se representan internamente. Por ejemplo, si lo intentas list(frange(0, 1, 0.5)), funciona bien y se excluye 1, pero si lo intentas list(frange(0, 1, 0.1)), el último valor que obtienes está cerca de 1.0, que probablemente no sea lo que deseas. Las soluciones presentadas aquí no tienen este problema.
blubberdiblub
3
Nunca use numpy.arange (la documentación de numpy en sí misma recomienda no hacerlo). Use numpy.linspace como lo recomienda wim, o una de las otras sugerencias en esta respuesta.
benrg
79

Solía ​​usar, numpy.arangepero tuve algunas complicaciones para controlar la cantidad de elementos que devuelve, debido a errores de coma flotante. Entonces ahora uso linspace, por ejemplo:

>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([  0.        ,   3.33333333,   6.66666667,  10.        ])
wim
fuente
Sin embargo, todavía hay errores de coma flotante, sin el uso de decimal, por ejemplo:np.linspace(-.1,10,num=5050)[0]
TNT
2
@TNT No, eso no es un error. Encontrará que np.linspace(-.1,10,num=5050)[0] == -.1es cierto. Es solo que repr(np.float64('-0.1'))muestra más dígitos.
wim
1
Si bien ese ejemplo en particular no muestra un error de redondeo excesivo, hay casos de falla. Por ejemplo, print(numpy.linspace(0, 3, 148)[49])imprime 0.9999999999999999cuándo sería el resultado ideal 1.0. linspacehace un trabajo mucho mejor que arange, pero no se garantiza que produzca el mínimo error de redondeo posible.
user2357112 es compatible con Monica el
Se está garantizado para llevar a cabo la manipulación punto final correcto, y siempre produce exactamente el número solicitado de elementos.
user2357112 es compatible con Monica el
40

Pylab tiene frange(un contenedor, en realidad, para matplotlib.mlab.frange):

>>> import pylab as pl
>>> pl.frange(0.5,5,0.5)
array([ 0.5,  1. ,  1.5,  2. ,  2.5,  3. ,  3.5,  4. ,  4.5,  5. ])
Palmadita
fuente
44
Frange está en desuso desde matplotlib versión 2.2. numpy.arange se debe utilizar.
Kuzavas
13

Evaluado ansiosamente (2.x range):

[x * .5 for x in range(10)]

Evaluado perezosamente (2.x xrange, 3.x range):

itertools.imap(lambda x: x * .5, xrange(10)) # or range(10) as appropriate

Alternativamente:

itertools.islice(itertools.imap(lambda x: x * .5, itertools.count()), 10)
# without applying the `islice`, we get an infinite stream of half-integers.
Karl Knechtel
fuente
44
+1; pero ¿por qué no (x * .5 for x in range(10))como una expresión generadora para una evaluación perezosa?
Tim Pietzcker
2
¿Porque eso sería demasiado fácil, supongo? :)
Karl Knechtel
11

utilizando itertools: rango de punto flotante evaluado perezosamente:

>>> from itertools import count, takewhile
>>> def frange(start, stop, step):
        return takewhile(lambda x: x< stop, count(start, step))

>>> list(frange(0.5, 5, 1.5))
# [0.5, 2.0, 3.5]
Ayush
fuente
3
+1 por usar itertools.takewhile. Sin embargo, itertools.count(start, step)sufre de errores de coma flotante acumulados. (Evalúe takewhile(lambda x: x < 100, count(0, 0.1))por ejemplo). Escribiría en su takewhile(lambda x: x < stop, (start + i * step for i in count()))lugar.
musiphil
6

Ayudé a agregar la función numeric_range al paquete more-itertools .

more_itertools.numeric_range(start, stop, step) actúa como el rango de funciones incorporado, pero puede manejar tipos flotantes, decimales y fracciones.

>>> from more_itertools import numeric_range
>>> tuple(numeric_range(.1, 5, 1))
(0.1, 1.1, 2.1, 3.1, 4.1)
William Rusnack
fuente
4

No existe tal función incorporada, pero puede usar lo siguiente (código Python 3) para hacer el trabajo tan seguro como Python le permite.

from fractions import Fraction

def frange(start, stop, jump, end=False, via_str=False):
    """
    Equivalent of Python 3 range for decimal numbers.

    Notice that, because of arithmetic errors, it is safest to
    pass the arguments as strings, so they can be interpreted to exact fractions.

    >>> assert Fraction('1.1') - Fraction(11, 10) == 0.0
    >>> assert Fraction( 0.1 ) - Fraction(1, 10) == Fraction(1, 180143985094819840)

    Parameter `via_str` can be set to True to transform inputs in strings and then to fractions.
    When inputs are all non-periodic (in base 10), even if decimal, this method is safe as long
    as approximation happens beyond the decimal digits that Python uses for printing.


    For example, in the case of 0.1, this is the case:

    >>> assert str(0.1) == '0.1'
    >>> assert '%.50f' % 0.1 == '0.10000000000000000555111512312578270211815834045410'


    If you are not sure whether your decimal inputs all have this property, you are better off
    passing them as strings. String representations can be in integer, decimal, exponential or
    even fraction notation.

    >>> assert list(frange(1, 100.0, '0.1', end=True))[-1] == 100.0
    >>> assert list(frange(1.0, '100', '1/10', end=True))[-1] == 100.0
    >>> assert list(frange('1', '100.0', '.1', end=True))[-1] == 100.0
    >>> assert list(frange('1.0', 100, '1e-1', end=True))[-1] == 100.0
    >>> assert list(frange(1, 100.0, 0.1, end=True))[-1] != 100.0
    >>> assert list(frange(1, 100.0, 0.1, end=True, via_str=True))[-1] == 100.0

    """
    if via_str:
        start = str(start)
        stop = str(stop)
        jump = str(jump)
    start = Fraction(start)
    stop = Fraction(stop)
    jump = Fraction(jump)
    while start < stop:
        yield float(start)
        start += jump
    if end and start == stop:
        yield(float(start))

Puede verificarlo todo ejecutando algunas afirmaciones:

assert Fraction('1.1') - Fraction(11, 10) == 0.0
assert Fraction( 0.1 ) - Fraction(1, 10) == Fraction(1, 180143985094819840)

assert str(0.1) == '0.1'
assert '%.50f' % 0.1 == '0.10000000000000000555111512312578270211815834045410'

assert list(frange(1, 100.0, '0.1', end=True))[-1] == 100.0
assert list(frange(1.0, '100', '1/10', end=True))[-1] == 100.0
assert list(frange('1', '100.0', '.1', end=True))[-1] == 100.0
assert list(frange('1.0', 100, '1e-1', end=True))[-1] == 100.0
assert list(frange(1, 100.0, 0.1, end=True))[-1] != 100.0
assert list(frange(1, 100.0, 0.1, end=True, via_str=True))[-1] == 100.0

assert list(frange(2, 3, '1/6', end=True))[-1] == 3.0
assert list(frange(0, 100, '1/3', end=True))[-1] == 100.0

Código disponible en GitHub

marcotama
fuente
4

¿Por qué no hay implementación de rango de punto flotante en la biblioteca estándar?

Como lo aclaran todas las publicaciones aquí, no hay una versión de coma flotante range(). Dicho esto, la omisión tiene sentido si consideramos que la range()función se usa a menudo como un generador de índice (y, por supuesto, eso significa un accesor ). Entonces, cuando llamamos range(0,40), en efecto estamos diciendo que queremos 40 valores que comiencen en 0, hasta 40, pero que no incluyan 40 en sí.

Cuando consideramos que la generación de índices tiene tanto que ver con el número de índices como con sus valores, el uso de una implementación flotante range()en la biblioteca estándar tiene menos sentido. Por ejemplo, si llamamos a la función frange(0, 10, 0.25), esperaríamos que se incluyan tanto 0 como 10, pero eso produciría un vector con 41 valores.

Por lo tanto, una frange()función que depende de su uso siempre exhibirá un comportamiento contrario a la intuición; o tiene demasiados valores como se perciben desde la perspectiva de indexación o no incluye un número que razonablemente debería ser devuelto desde la perspectiva matemática.

El caso de uso matemático

Dicho esto, como se discutió, numpy.linspace()realiza la generación con la perspectiva matemática muy bien:

numpy.linspace(0, 10, 41)
array([  0.  ,   0.25,   0.5 ,   0.75,   1.  ,   1.25,   1.5 ,   1.75,
         2.  ,   2.25,   2.5 ,   2.75,   3.  ,   3.25,   3.5 ,   3.75,
         4.  ,   4.25,   4.5 ,   4.75,   5.  ,   5.25,   5.5 ,   5.75,
         6.  ,   6.25,   6.5 ,   6.75,   7.  ,   7.25,   7.5 ,   7.75,
         8.  ,   8.25,   8.5 ,   8.75,   9.  ,   9.25,   9.5 ,   9.75,  10.
])

El caso de uso de indexación

Y para la perspectiva de indexación, he escrito un enfoque ligeramente diferente con un poco de magia con cuerdas engañosa que nos permite especificar el número de lugares decimales.

# Float range function - string formatting method
def frange_S (start, stop, skip = 1.0, decimals = 2):
    for i in range(int(start / skip), int(stop / skip)):
        yield float(("%0." + str(decimals) + "f") % (i * skip))

Del mismo modo, también podemos usar la roundfunción incorporada y especificar el número de decimales:

# Float range function - rounding method
def frange_R (start, stop, skip = 1.0, decimals = 2):
    for i in range(int(start / skip), int(stop / skip)):
        yield round(i * skip, ndigits = decimals)

Una comparación rápida y rendimiento

Por supuesto, dada la discusión anterior, estas funciones tienen un caso de uso bastante limitado. Sin embargo, aquí hay una comparación rápida:

def compare_methods (start, stop, skip):

    string_test  = frange_S(start, stop, skip)
    round_test   = frange_R(start, stop, skip)

    for s, r in zip(string_test, round_test):
        print(s, r)

compare_methods(-2, 10, 1/3)

Los resultados son idénticos para cada uno:

-2.0 -2.0
-1.67 -1.67
-1.33 -1.33
-1.0 -1.0
-0.67 -0.67
-0.33 -0.33
0.0 0.0
...
8.0 8.0
8.33 8.33
8.67 8.67
9.0 9.0
9.33 9.33
9.67 9.67

Y algunos horarios:

>>> import timeit

>>> setup = """
... def frange_s (start, stop, skip = 1.0, decimals = 2):
...     for i in range(int(start / skip), int(stop / skip)):
...         yield float(("%0." + str(decimals) + "f") % (i * skip))
... def frange_r (start, stop, skip = 1.0, decimals = 2):
...     for i in range(int(start / skip), int(stop / skip)):
...         yield round(i * skip, ndigits = decimals)
... start, stop, skip = -1, 8, 1/3
... """

>>> min(timeit.Timer('string_test = frange_s(start, stop, skip); [x for x in string_test]', setup=setup).repeat(30, 1000))
0.024284090992296115

>>> min(timeit.Timer('round_test = frange_r(start, stop, skip); [x for x in round_test]', setup=setup).repeat(30, 1000))
0.025324633985292166

Parece que el método de formato de cadena gana por un pelo en mi sistema.

Las limitaciones

Y finalmente, una demostración del punto de la discusión anterior y una última limitación:

# "Missing" the last value (10.0)
for x in frange_R(0, 10, 0.25):
    print(x)

0.25
0.5
0.75
1.0
...
9.0
9.25
9.5
9.75

Además, cuando el skipparámetro no es divisible por el stopvalor, puede haber una brecha enorme debido al último problema:

# Clearly we know that 10 - 9.43 is equal to 0.57
for x in frange_R(0, 10, 3/7):
    print(x)

0.0
0.43
0.86
1.29
...
8.14
8.57
9.0
9.43

Hay formas de abordar este problema, pero al final del día, el mejor enfoque probablemente sería simplemente usar Numpy.

Tallo verde
fuente
Este es un argumento bastante retorcido. range () simplemente debe observarse en el generador de iteraciones y si se usa en for loop o para indexar algo debe dejarse a las personas que llaman. Las personas han estado usando flotadores en bucle durante milenios y las justificaciones anteriores no tienen sentido. La gente en los comités de Python se equivocó aquí a lo grande y el argumento bueno probablemente se ahogó por algunas justificaciones retorcidas como las anteriores. Es así de simple y llanamente. Ahora hay demasiadas decisiones como las anteriores consagradas en lenguaje Python.
Shital Shah
3

Kichik proporcionó una solución sin dependencias numpy, etc., pero debido a la aritmética de coma flotante , a menudo se comporta inesperadamente. Como noté yo y blubberdiblub , elementos adicionales se infiltran fácilmente en el resultado. Por ejemplo, naive_frange(0.0, 1.0, 0.1)rendiría 0.999...como su último valor y, por lo tanto, arrojaría 11 valores en total.

Aquí se proporciona una versión robusta:

def frange(x, y, jump=1.0):
    '''Range for floats.'''
    i = 0.0
    x = float(x)  # Prevent yielding integers.
    x0 = x
    epsilon = jump / 2.0
    yield x  # yield always first value
    while x + epsilon < y:
        i += 1.0
        x = x0 + i * jump
        yield x

Debido a la multiplicación, los errores de redondeo no se acumulan. El uso de epsilonse encarga del posible error de redondeo de la multiplicación, aunque, por supuesto, los problemas pueden surgir en los extremos muy pequeños y muy grandes. Ahora, como se esperaba:

> a = list(frange(0.0, 1.0, 0.1))
> a[-1]
0.9
> len(a)
10

Y con números algo mayores:

> b = list(frange(0.0, 1000000.0, 0.1))
> b[-1]
999999.9
> len(b)
10000000

El código también está disponible como GitHub Gist .

Akseli Palén
fuente
Esto falla con frange (2.0, 17.0 / 6.0, 1.0 / 6.0). No hay forma de que pueda hacerse robusto.
benrg
@benrg ¡Gracias por señalar esto! Me llevó a darme cuenta de que el épsilon debería depender del salto, así que revisé el algoritmo y reparé el problema. Esta nueva versión es mucho más robusta, ¿no?
Akseli Palén
2

Una versión más simple sin biblioteca

Aw, diablos: agregaré una versión simple sin biblioteca. Siéntase libre de mejorarlo [*]:

def frange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    dy = stop-start
    # f(i) goes from start to stop as i goes from 0 to nsteps
    return [start + float(i)*dy/nsteps for i in range(nsteps)]

La idea central es que nstepses el número de pasos que lo llevan de principio a fin y range(nsteps)siempre emite enteros para que no haya pérdida de precisión. El paso final es mapear [0..nsteps] linealmente en [start..stop].

editar

Si, como alancalvitti, desea que la serie tenga una representación racional exacta, siempre puede usar Fracciones :

from fractions import Fraction

def rrange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    return [Fraction(i, nsteps) for i in range(nsteps)]

[*] En particular, frange()devuelve una lista, no un generador. Pero fue suficiente para mis necesidades.

temerario
fuente
Si desea incluir el valor de stop en la salida, al agregar stop + jump, este método vuelve al resultado ingenuo con puntos flotantes malos en el medio, intente frange(0,1.1,0.1)y aún más de aquellos con una opción comofrange(0,1.05,0.1)
alancalvitti
@alancalvitti: ¿Cuál es su definición de un punto flotante "malo"? Sí, los resultados pueden no imprimirse bien, pero frange () proporciona el conjunto más cercano de valores espaciados uniformemente dentro de los límites de la representación de coma flotante. ¿Cómo lo mejorarías?
fearless_fool
Buen punto, estoy tan acostumbrado a un lenguaje de alto nivel en el que se puede ir más allá de los números racionales para tal tarea, que Py se siente como un ensamblaje.
alancalvitti
¿Montaje? Hrrumph! ;) Por supuesto, Python PUEDE proporcionar una representación exacta con fracciones: docs.python.org/3/library/fractions.html
fearless_fool
Bien, gracias, pero por ejemplo, el lenguaje que me gusta convierte automáticamente estos tipos, por lo que 1/2 es racional, mientras que 1 / 2.0 es flotante, no hay necesidad de declararlos como tales: deje las declaraciones en Java, que es aún más menor / ensamblaje que Py.
alancalvitti
2

Esto se puede hacer con numpy.arange (iniciar, detener, tamaño de paso)

import numpy as np

np.arange(0.5,5,1.5)
>> [0.5, 2.0, 3.5, 5.0]

# OBS you will sometimes see stuff like this happening, 
# so you need to decide whether that's not an issue for you, or how you are going to catch it.
>> [0.50000001, 2.0, 3.5, 5.0]

Nota 1: De la discusión en la sección de comentarios aquí, "nunca use numpy.arange()(la documentación numpy en sí misma recomienda no hacerlo). Use numpy.linspace como lo recomienda wim, o una de las otras sugerencias en esta respuesta"

Nota 2: He leído la discusión en algunos comentarios aquí, pero después de volver a esta pregunta por tercera vez, siento que esta información debería colocarse en una posición más legible.

mrk
fuente
2

Como escribió Kichik , esto no debería ser demasiado complicado. Sin embargo este código:

def frange(x, y, jump):
  while x < y:
    yield x
    x += jump

Es inapropiado debido al efecto acumulativo de los errores al trabajar con flotadores. Por eso recibes algo como:

>>>list(frange(0, 100, 0.1))[-1]
99.9999999999986

Si bien el comportamiento esperado sería:

>>>list(frange(0, 100, 0.1))[-1]
99.9

Solución 1

El error acumulativo simplemente se puede reducir mediante el uso de una variable de índice. Aquí está el ejemplo:

from math import ceil

    def frange2(start, stop, step):
        n_items = int(ceil((stop - start) / step))
        return (start + i*step for i in range(n_items))

Este ejemplo funciona como se esperaba.

Solución 2

No hay funciones anidadas. Solo un tiempo y una variable de contador:

def frange3(start, stop, step):
    res, n = start, 1

    while res < stop:
        yield res
        res = start + n * step
        n += 1

Esta función también funcionará bien, excepto en los casos en que desee el rango invertido. P.ej:

>>>list(frange3(1, 0, -.1))
[]

La solución 1 en este caso funcionará como se esperaba. Para que esta función funcione en tales situaciones, debe aplicar un truco, similar al siguiente:

from operator import gt, lt

def frange3(start, stop, step):
    res, n = start, 0.
    predicate = lt if start < stop else gt
    while predicate(res, stop):
        yield res
        res = start + n * step
        n += 1

Con este truco puedes usar estas funciones con pasos negativos:

>>>list(frange3(1, 0, -.1))
[1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.3999999999999999, 0.29999999999999993, 0.19999999999999996, 0.09999999999999998]

Solución 3

Puede ir aún más lejos con la biblioteca estándar simple y componer una función de rango para la mayoría de los tipos numéricos:

from itertools import count
from itertools import takewhile

def any_range(start, stop, step):
    start = type(start + step)(start)
    return takewhile(lambda n: n < stop, count(start, step))

Este generador está adaptado del libro Fluent Python (Capítulo 14. Iterables, iteradores y generadores). No funcionará con rangos decrecientes. Debe aplicar un truco, como en la solución anterior.

Puede usar este generador de la siguiente manera, por ejemplo:

>>>list(any_range(Fraction(2, 1), Fraction(100, 1), Fraction(1, 3)))[-1]
299/3
>>>list(any_range(Decimal('2.'), Decimal('4.'), Decimal('.3')))
[Decimal('2'), Decimal('2.3'), Decimal('2.6'), Decimal('2.9'), Decimal('3.2'), Decimal('3.5'), Decimal('3.8')]

Y, por supuesto, también puede usarlo con float e int .

Ten cuidado

Si desea utilizar estas funciones con pasos negativos, debe agregar una marca para el signo de paso, por ejemplo:

no_proceed = (start < stop and step < 0) or (start > stop and step > 0)
if no_proceed: raise StopIteration

La mejor opción aquí es subir StopIteration, si quieres imitar la rangefunción en sí.

Rango mimético

Si desea imitar la rangeinterfaz de la función, puede proporcionar algunas comprobaciones de argumentos:

def any_range2(*args):
    if len(args) == 1:
        start, stop, step = 0, args[0], 1.
    elif len(args) == 2:
        start, stop, step = args[0], args[1], 1.
    elif len(args) == 3:
        start, stop, step = args
    else:
        raise TypeError('any_range2() requires 1-3 numeric arguments')

    # here you can check for isinstance numbers.Real or use more specific ABC or whatever ...

    start = type(start + step)(start)
    return takewhile(lambda n: n < stop, count(start, step))

Creo que tienes el punto. Puede utilizar cualquiera de estas funciones (excepto la primera) y todo lo que necesita para ellas es la biblioteca estándar de Python.

stolpa4
fuente
1

Escribí una función que devuelve una tupla de un rango de números de coma flotante de doble precisión sin decimales más allá de las centésimas. era simplemente una cuestión de analizar los valores de rango como cadenas y dividir el exceso. Lo uso para mostrar rangos para seleccionar desde una interfaz de usuario. Espero que alguien más lo encuentre útil.

def drange(start,stop,step):
    double_value_range = []
    while start<stop:
        a = str(start)
        a.split('.')[1].split('0')[0]
        start = float(str(a))
        double_value_range.append(start)
        start = start+step
    double_value_range_tuple = tuple(double_value_range)
   #print double_value_range_tuple
    return double_value_range_tuple
Chris mcinnis
fuente
1

Uso

# Counting up
drange(0, 0.4, 0.1)
[0, 0.1, 0.2, 0.30000000000000004, 0.4]

# Counting down
drange(0, -0.4, -0.1)
[0, -0.1, -0.2, -0.30000000000000004, -0.4]

Para redondear cada paso a N decimales

drange(0, 0.4, 0.1, round_decimal_places=4)
[0, 0.1, 0.2, 0.3, 0.4]

drange(0, -0.4, -0.1, round_decimal_places=4)
[0, -0.1, -0.2, -0.3, -0.4]

Código

def drange(start, end, increment, round_decimal_places=None):
    result = []
    if start < end:
        # Counting up, e.g. 0 to 0.4 in 0.1 increments.
        if increment < 0:
            raise Exception("Error: When counting up, increment must be positive.")
        while start <= end:
            result.append(start)
            start += increment
            if round_decimal_places is not None:
                start = round(start, round_decimal_places)
    else:
        # Counting down, e.g. 0 to -0.4 in -0.1 increments.
        if increment > 0:
            raise Exception("Error: When counting down, increment must be negative.")
        while start >= end:
            result.append(start)
            start += increment
            if round_decimal_places is not None:
                start = round(start, round_decimal_places)
    return result

¿Por qué elegir esta respuesta?

  • Muchas otras respuestas se colgarán cuando se les solicite una cuenta regresiva.
  • Muchas otras respuestas darán resultados incorrectamente redondeados.
  • Otras respuestas basadas en np.linspaceaciertos y errores pueden funcionar o no debido a la dificultad de elegir el número correcto de divisiones. np.linspacerealmente lucha con incrementos decimales de 0.1, y el orden de las divisiones en la fórmula para convertir el incremento en una serie de divisiones puede resultar en un código correcto o roto.
  • Otras respuestas basadas en np.arangeestán en desuso.

En caso de duda, pruebe los cuatro casos de prueba anteriores.

Aplazamiento de pago
fuente
0
def Range(*argSequence):
    if len(argSequence) == 3:
        imin = argSequence[0]; imax = argSequence[1]; di = argSequence[2]
        i = imin; iList = []
        while i <= imax:
            iList.append(i)
            i += di
        return iList
    if len(argSequence) == 2:
        return Range(argSequence[0], argSequence[1], 1)
    if len(argSequence) == 1:
        return Range(1, argSequence[0], 1)

Tenga en cuenta que la primera letra de Range es mayúscula. Este método de denominación no se recomienda para funciones en Python. Puede cambiar el rango a algo como drange o frange si lo desea. La función "Rango" se comporta tal como lo desea. Puede consultar su manual aquí [ http://reference.wolfram.com/language/ref/Range.html ].

Hua Congyi
fuente
0

Creo que hay una respuesta muy simple que realmente emula todas las características de rango, pero tanto para flotante como para entero. En esta solución, solo supone que su aproximación por defecto es 1e-7 (o la que elija) y puede cambiarla cuando llame a la función.

def drange(start,stop=None,jump=1,approx=7): # Approx to 1e-7 by default
  '''
  This function is equivalent to range but for both float and integer
  '''
  if not stop: # If there is no y value: range(x)
      stop= start
      start= 0
  valor= round(start,approx)
  while valor < stop:
      if valor==int(valor):
          yield int(round(valor,approx))
      else:
          yield float(round(valor,approx))
      valor += jump
  for i in drange(12):
      print(i)
Jose alavedra
fuente
0

Por supuesto, habrá algunos errores de redondeo, por lo que esto no es perfecto, pero esto es lo que uso generalmente para aplicaciones, que no requieren una alta precisión. Si desea que esto sea más preciso, puede agregar un argumento adicional para especificar cómo manejar los errores de redondeo. Quizás pasar una función de redondeo podría hacerlo extensible y permitirle al programador especificar cómo manejar los errores de redondeo.

arange = lambda start, stop, step: [i + step * i for i in range(int((stop - start) / step))]

Si escribo

arange(0, 1, 0.1)

Producirá:

[0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9]
MikeyE
fuente
-1

¿Hay un rango () equivalente para flotadores en Python? NO use esto:

def f_range(start, end, step):
    a = range(int(start/0.01), int(end/0.01), int(step/0.01))
    var = []
    for item in a:
        var.append(item*0.01)
    return var
Grigor Kolev
fuente
3
Solución bastante mala, intente f_range(0.01,0.02,0.001)... Para los propósitos más prácticos, arangede Numpy es una solución simple, segura y rápida.
Bart
Tienes razón. Con numpy es 1.8 más rápido que mi código.
Grigor Kolev
Tienes razón. Con numpy es 1.8 más rápido que mi código. Pero el sistema donde trabajo está completamente cerrado. Solo Python y pyserial no más.
Grigor Kolev
-2

Aquí hay varias respuestas que no manejan casos extremos simples como paso negativo, inicio incorrecto, detención, etc. Aquí está la versión que maneja muchos de estos casos correctamente dando el mismo comportamiento que el nativo range():

def frange(start, stop=None, step=1):
  if stop is None:
    start, stop = 0, start
  steps = int((stop-start)/step)
  for i in range(steps):
    yield start
    start += step  

Tenga en cuenta que esto daría un error en el paso = 0 al igual que nativo range. Una diferencia es que el rango nativo devuelve un objeto que es indexable y reversible, mientras que el anterior no lo hace.

Puedes jugar con este código y probar casos aquí.

Shital Shah
fuente