range () pour les flotteurs

140

Existe-t-il un range()équivalent pour les flottants 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
la source
1
Ce ne sont pas des fractions mais des flotteurs. Et les flotteurs sont ... eh bien, susceptibles de donner des résultats différents de ceux que vous attendez.
6
Une solution rapide serait de traiter les entiers comme des nombres décimaux, par exemple range(5, 50, 5)
:,
@delnan - mis à jour. Je suis prêt à accepter des inexactitudes minimes pour la commodité d'avoir une plage de flotteurs
Jonathan
2
duplication possible de la valeur d'étape de la plage décimale Python ()
Jonathan
@NullUserException - ce n'est qu'un exemple - le vrai code est bien sûr paramétrique :)
Jonathan

Réponses:

97

Je ne connais pas de fonction intégrée, mais en écrire une comme celle- ci ne devrait pas être trop compliqué.

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

Comme le mentionnent les commentaires, cela pourrait produire des résultats imprévisibles tels que:

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

Pour obtenir le résultat attendu, vous pouvez utiliser l'une des autres réponses de cette question, ou comme @Tadhg l'a mentionné, vous pouvez utiliser decimal.Decimalcomme jumpargument. Assurez-vous de l'initialiser avec une chaîne plutôt qu'un flottant.

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

Ou même:

import decimal

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

Puis:

>>> list(drange(0, 100, '0.1'))[-1]
99.9
kichik
la source
34
La devise de Python est en fait qu'il devrait y avoir une - et de préférence une seule - façon évidente de le faire . Mais Python est génial quand même :)
Jonathan
3
>>> print list(frange(0,100,0.1))[-1]==100.0seraFalse
Volodimir Kopey
frangepeut fonctionner de manière inattendue. En raison de la malédiction de l'arithmétique en virgule flottante , par exemple frange(0.0, 1.0, 0.1)donne 11 valeurs, où se trouve la dernière valeur 0.9999999999999999. Une amélioration pratique serait while x + sys.float_info.epsilon < y:même si cela peut probablement échouer avec de grands nombres .
Akseli Palén
10
-1 Veuillez ne pas utiliser ce code , du moins pas dans des logiciels qui pourraient affecter ma vie. Il n'y a aucun moyen de le faire fonctionner de manière fiable. N'utilisez pas non plus la réponse d'Akseli Palén. Utilisez la réponse de Xaerxess ou de wim (sauf ignorer la partie sur arange).
benrg
3
cela fonctionne très bien si vous utilisezdecimal.Decimal comme étape au lieu de flotteurs.
Tadhg McDonald-Jensen
112

Vous pouvez soit utiliser:

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

ou utilisez lambda / map:

map(lambda x: x/10.0, range(5, 50, 15))
Xaerxess
la source
1
Et array (range (5,50,15)) / 10.0 car les tableaux numpy ont des opérateurs pour gérer la division, la multiplication, etc.
edvaldig
2
@edvaldig: vous avez raison, je ne savais pas à ce sujet ... Néanmoins, je pense que l' arange(0.5, 5, 1.5)OMI est plus lisible.
Xaerxess le
2
Je préfère cette réponse à celle acceptée, car les deux premières solutions présentées sont basées sur une itération sur des entiers et sur la dérivation des flottants finaux à partir des entiers. C'est plus robuste. Si vous le faites directement avec des flottants, vous risquez d'avoir d'étranges erreurs ponctuelles en raison de la façon dont les flottants sont représentés en interne. Par exemple, si vous essayez list(frange(0, 1, 0.5)), cela fonctionne bien et 1 est exclu, mais si vous essayez list(frange(0, 1, 0.1)), la dernière valeur que vous obtenez est proche de 1.0, ce qui n'est probablement pas ce que vous voulez. Les solutions présentées ici n'ont pas ce problème.
blubberdiblub
3
N'utilisez jamais numpy.arange (la documentation numpy elle-même le recommande). Utilisez numpy.linspace comme recommandé par wim, ou l'une des autres suggestions de cette réponse.
benrg
79

J'avais l'habitude de l'utiliser numpy.arangemais j'ai eu quelques complications pour contrôler le nombre d'éléments qu'il renvoie, en raison d'erreurs en virgule flottante. Alors maintenant linspace, j'utilise , par exemple:

>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([  0.        ,   3.33333333,   6.66666667,  10.        ])
wim
la source
Il y a toujours des erreurs en virgule flottante, sans l'utilisation de decimal, par exemple:np.linspace(-.1,10,num=5050)[0]
TNT
2
@TNT Non, ce n'est pas une erreur. Vous trouverez que np.linspace(-.1,10,num=5050)[0] == -.1c'est vrai. C'est juste que le repr(np.float64('-0.1'))montre plus de chiffres.
wim
1
Bien que cet exemple particulier ne montre aucune erreur d'arrondi excessive, il existe des cas d'échec. Par exemple, print(numpy.linspace(0, 3, 148)[49])imprime 0.9999999999999999lorsque le résultat idéal serait 1.0. linspacefait un bien meilleur travail que arange, mais il n'est pas garanti de produire le minimum d'erreur d'arrondi possible.
user2357112 prend en charge Monica le
Il est garanti d'effectuer une gestion correcte des points de terminaison et de toujours produire exactement le nombre d'éléments requis.
user2357112 prend en charge Monica le
40

Pylab a frange(un wrapper, en fait, pour 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. ])
Tapoter
la source
4
Frange est obsolète depuis la version 2.2 de matplotlib. numpy.arange doit être utilisé.
kuzavas
13

Évalué avec impatience (2.x range):

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

Évalué paresseusement (2.x xrange, 3.x range):

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

Alternativement:

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
la source
4
+1; mais pourquoi pas en (x * .5 for x in range(10))tant qu'expression génératrice pour une évaluation paresseuse?
Tim Pietzcker
2
Parce que ce serait trop facile, je suppose? :)
Karl Knechtel
11

en utilisant itertools: plage de virgule flottante évaluée paresseusement:

>>> 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
la source
3
+1 pour l'utilisation itertools.takewhile. Cependant, itertools.count(start, step)souffre d'erreurs en virgule flottante accumulées. (Évaluer takewhile(lambda x: x < 100, count(0, 0.1))par exemple.) J'écrirais à la takewhile(lambda x: x < stop, (start + i * step for i in count()))place.
musiphil
6

J'ai aidé à ajouter la fonction numeric_range au package more-itertools .

more_itertools.numeric_range(start, stop, step) agit comme la plage de fonctions intégrée mais peut gérer les types floats, Decimal et Fraction.

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

Il n'existe pas de fonction intégrée de ce type, mais vous pouvez utiliser ce qui suit (code Python 3) pour effectuer le travail de la manière la plus sûre que Python vous permet.

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))

Vous pouvez tout vérifier en exécutant quelques assertions:

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

Code disponible sur GitHub

Marcotama
la source
4

Pourquoi n'y a-t-il pas d'implémentation de plage de virgule flottante dans la bibliothèque standard?

Comme le montrent tous les articles ici, il n'y a pas de version en virgule flottante de range(). Cela dit, l'omission a du sens si l'on considère que la range()fonction est souvent utilisée comme un générateur d' index (et bien sûr, cela signifie un accesseur ). Ainsi, lorsque nous appelons range(0,40), nous disons en fait que nous voulons 40 valeurs commençant à 0, jusqu'à 40, mais non compris 40 lui-même.

Lorsque nous considérons que la génération d'index concerne autant le nombre d'indices que leurs valeurs, l'utilisation d'une implémentation float de range()dans la bibliothèque standard a moins de sens. Par exemple, si nous appelions la fonction frange(0, 10, 0.25), nous nous attendrions à ce que 0 et 10 soient inclus, mais cela donnerait un vecteur avec 41 valeurs.

Ainsi, une frange()fonction dépendant de son utilisation présentera toujours un comportement contre-intuitif; il a trop de valeurs perçues du point de vue de l'indexation ou ne comprend pas un nombre qui devrait raisonnablement être renvoyé du point de vue mathématique.

Le cas d'utilisation mathématique

Cela dit, comme discuté, numpy.linspace()effectue bien la génération avec la perspective mathématique:

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.
])

Le cas d'utilisation de l'indexation

Et pour la perspective de l'indexation, j'ai écrit une approche légèrement différente avec une magie de chaîne astucieuse qui nous permet de spécifier le nombre de décimales.

# 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))

De même, nous pouvons également utiliser la roundfonction intégrée et spécifier le nombre de décimales:

# 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)

Une comparaison rapide et des performances

Bien entendu, compte tenu de la discussion ci-dessus, ces fonctions ont un cas d'utilisation assez limité. Néanmoins, voici une comparaison rapide:

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)

Les résultats sont identiques pour chacun:

-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

Et quelques horaires:

>>> 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

On dirait que la méthode de formatage de chaîne gagne par un cheveu sur mon système.

Les limites

Et enfin, une démonstration du point de la discussion ci-dessus et une dernière limitation:

# "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

De plus, lorsque le skipparamètre n'est pas divisible par la stopvaleur, il peut y avoir un écart béant compte tenu de ce dernier problème:

# 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

Il existe des moyens de résoudre ce problème, mais en fin de compte, la meilleure approche serait probablement d'utiliser simplement Numpy.

Greenstick
la source
C'est un argument assez tordu. range () devrait simplement être regardé dans le générateur d'itération et s'il est utilisé dans la boucle for ou pour indexer quelque chose devrait être laissé aux appelants. Les gens utilisent des flotteurs dans la boucle for depuis des millénaires et les justifications ci-dessus sont absurdes. Les membres des comités Python ont foiré ici et les bons arguments ont probablement été noyés par des justifications tordues comme ci-dessus. C'est aussi simple que cela. Il y a maintenant trop de décisions comme ci-dessus enchâssées dans le langage Python.
Shital Shah
3

Une solution sans dépendances numpy etc a été fournie par kichik mais en raison de l'arithmétique en virgule flottante , elle se comporte souvent de manière inattendue. Comme je l' ai noté et blubberdiblub , des éléments supplémentaires se faufilent facilement dans le résultat. Par exemple, naive_frange(0.0, 1.0, 0.1)il donnerait 0.999...comme dernière valeur et donnerait ainsi 11 valeurs au total.

Une version robuste est fournie ici:

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

Parce que la multiplication, les erreurs d'arrondi ne s'accumulent pas. L'utilisation de epsilonprend en compte les éventuelles erreurs d'arrondi de la multiplication, même si des problèmes peuvent bien sûr surgir dans les très petites et très grandes extrémités. Maintenant, comme prévu:

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

Et avec des nombres un peu plus grands:

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

Le code est également disponible sous forme de GitHub Gist .

Akseli Palén
la source
Cela échoue avec frange (2.0, 17.0 / 6.0, 1.0 / 6.0). Il n'y a aucun moyen de le rendre robuste.
benrg
@benrg Merci d'avoir signalé cela! Cela m'a conduit à réaliser que l'epsilon devait dépendre du saut, j'ai donc revu l'algorithme et réparé le problème. Cette nouvelle version est bien plus robuste, n'est-ce pas?
Akseli Palén
2

Une version plus simple sans bibliothèque

Oh, diable - je vais jeter dans une version simple sans bibliothèque. N'hésitez pas à l'améliorer [*]:

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)]

L'idée principale est que nstepsc'est le nombre d'étapes pour vous amener du début à la fin etrange(nsteps) émet toujours des entiers afin qu'il n'y ait pas de perte de précision. La dernière étape consiste à mapper [0..nsteps] linéairement sur [start..stop].

Éditer

Si, comme alancalvitti, vous souhaitez que la série ait une représentation rationnelle exacte, vous pouvez toujours utiliser des fractions :

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 particulier, frange()renvoie une liste, pas un générateur. Mais cela suffisait à mes besoins.

sans peur
la source
Si vous souhaitez inclure la valeur d'arrêt dans la sortie, en ajoutant stop + jump, cette méthode revient au résultat naïf avec de mauvais points flottants au milieu, essayez frange(0,1.1,0.1)et encore plus de ceux avec un choix commefrange(0,1.05,0.1)
alancalvitti
@alancalvitti: Quelle est votre définition d'une "mauvaise" virgule flottante? Oui, les résultats peuvent ne pas s'imprimer correctement, mais frange () fournit l'ensemble le plus proche de valeurs régulièrement espacées dans les limites de la représentation en virgule flottante. Comment l'amélioreriez-vous?
peurless_fool
bon point, je suis tellement habitué au langage de haut niveau où vous étiez sur des nombres rationnels pour une telle tâche, que Py se sent comme un assemblage.
alancalvitti
Assemblée? Hrrumph! ;) Bien sûr Python peut fournir une représentation exacte avec Fractions: docs.python.org/3/library/fractions.html
fearless_fool
Bien, merci, mais par exemple, le langage que j'aime convertit automatiquement ces types, donc 1/2 est un rationnel, tandis que 1 / 2.0 est flottant, il n'est pas nécessaire de les déclarer comme tels - laissez les déclarations à Java, ce qui est encore plus inférieur / assemblage à Py.
alancalvitti
2

Cela peut être fait avec numpy.arange (start, stop, stepsize)

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]

Remarque 1: D'après la discussion dans la section des commentaires ici, "ne jamais utiliser numpy.arange()(la documentation numpy elle-même le recommande contre). Utilisez numpy.linspace comme recommandé par wim, ou l'une des autres suggestions de cette réponse"

Note 2: J'ai lu la discussion dans quelques commentaires ici, mais après être revenu sur cette question pour la troisième fois maintenant, je pense que cette information devrait être placée dans une position plus lisible.

mrk
la source
2

Comme l'a écrit Kichik , cela ne devrait pas être trop compliqué. Cependant ce code:

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

Est inapproprié en raison de l' effet cumulatif des erreurs lors de l'utilisation de flotteurs. C'est pourquoi vous recevez quelque chose comme:

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

Alors que le comportement attendu serait:

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

Solution 1

L'erreur cumulative peut simplement être réduite en utilisant une variable d'index. Voici l'exemple:

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))

Cet exemple fonctionne comme prévu.

Solution 2

Aucune fonction imbriquée. Seulement un moment et une variable de compteur:

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

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

Cette fonction fonctionnera également bien, sauf dans les cas où vous souhaitez inverser la plage. Par exemple:

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

La solution 1 dans ce cas fonctionnera comme prévu. Pour que cette fonction fonctionne dans de telles situations, vous devez appliquer un hack, semblable à ce qui suit:

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

Avec ce hack, vous pouvez utiliser ces fonctions avec des étapes négatives:

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

Solution 3

Vous pouvez aller encore plus loin avec la bibliothèque standard simple et composer une fonction de plage pour la plupart des types numériques:

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))

Ce générateur est adapté du livre Fluent Python (Chapitre 14. Itérables, itérateurs et générateurs). Cela ne fonctionnera pas avec des plages décroissantes. Vous devez appliquer un hack, comme dans la solution précédente.

Vous pouvez utiliser ce générateur comme suit, par exemple:

>>>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')]

Et bien sûr, vous pouvez également l'utiliser avec float et int .

Faites attention

Si vous souhaitez utiliser ces fonctions avec des étapes négatives, vous devez ajouter une vérification pour le signe de l'étape, par exemple:

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

La meilleure option ici est d'augmenter StopIteration, si vous voulez imiter la rangefonction elle-même.

Gamme Mimic

Si vous souhaitez imiter l' rangeinterface de la fonction, vous pouvez fournir des vérifications d'argument:

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))

Je pense que vous avez compris. Vous pouvez utiliser l'une de ces fonctions (sauf la toute première) et tout ce dont vous avez besoin est une bibliothèque standard python.

stolpa4
la source
1

J'ai écrit une fonction qui renvoie un tuple d'une plage de nombres à virgule flottante double précision sans décimales au-delà des centièmes. il s'agissait simplement d'analyser les valeurs de plage telles que les chaînes et de séparer l'excédent. Je l'utilise pour afficher des plages à sélectionner dans une interface utilisateur. J'espère que quelqu'un d'autre le trouvera utile.

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
la source
1

Usage

# 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]

Pour arrondir chaque pas à N décimales

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]

Code

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

Pourquoi choisir cette réponse?

  • De nombreuses autres réponses se bloqueront lorsqu'on leur demandera de compter à rebours.
  • De nombreuses autres réponses donneront des résultats incorrectement arrondis.
  • D'autres réponses basées sur np.linspacesont aléatoires, elles peuvent ou non fonctionner en raison de la difficulté à choisir le nombre correct de divisions. np.linspacea vraiment du mal avec les incréments décimaux de 0,1, et l'ordre des divisions dans la formule pour convertir l'incrément en un certain nombre de fractionnements peut entraîner un code correct ou cassé.
  • Les autres réponses basées sur np.arangesont obsolètes.

En cas de doute, essayez les quatre cas de tests ci-dessus.

Contango
la source
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)

Veuillez noter que la première lettre de Range est en majuscule. Cette méthode de dénomination n'est pas recommandée pour les fonctions en Python. Vous pouvez changer Range en quelque chose comme drange ou frange si vous le souhaitez. La fonction "Range" se comporte exactement comme vous le souhaitez. Vous pouvez consulter son manuel ici [ http://reference.wolfram.com/language/ref/Range.html ].

Hua Congyi
la source
0

Je pense qu'il existe une réponse très simple qui émule vraiment toutes les fonctionnalités de la plage, mais pour les nombres flottants et entiers. Dans cette solution, vous supposez simplement que votre approximation par défaut est 1e-7 (ou celle que vous choisissez) et vous pouvez la modifier lorsque vous appelez la fonction.

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)
José Alavedra
la source
0

Il y aura bien sûr des erreurs d'arrondi, donc ce n'est pas parfait, mais c'est ce que j'utilise généralement pour les applications qui ne nécessitent pas de haute précision. Si vous souhaitez rendre cela plus précis, vous pouvez ajouter un argument supplémentaire pour spécifier comment gérer les erreurs d'arrondi. Peut-être que passer une fonction d'arrondi pourrait rendre cette fonction extensible et permettre au programmeur de spécifier comment gérer les erreurs d'arrondi.

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

Si j'écris:

arange(0, 1, 0.1)

Il produira:

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

Existe-t-il un équivalent range () pour les flottants en Python? NON Utilisez ceci:

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
la source
3
Assez mauvaise solution, essayez f_range(0.01,0.02,0.001)... Pour des raisons pratiques, arangede Numpy est une solution simple, sûre et rapide.
Bart
Vous avez raison. Avec numpy, c'est 1,8 plus rapide que mon code.
Grigor Kolev
Vous avez raison. Avec numpy, c'est 1,8 plus rapide que mon code. Mais le système dans lequel je travaille est complètement fermé. Plus que Python et pyserial.
Grigor Kolev
-2

Il a plusieurs réponses ici qui ne gère pas les cas de pointe simples comme pas négatif, mauvais départ, arrêt , etc. Voici la version qui gère de nombreux de ces cas donnent correctement même comportement que natif 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  

Notez que cela entraînerait une erreur de step = 0 tout comme native range. Une différence est que la plage native renvoie un objet indexable et réversible alors que ce n'est pas le cas ci-dessus.

Vous pouvez jouer avec ce code et des cas de test ici.

Shital Shah
la source