web-dev-qa-db-fra.com

range () pour les flotteurs

Existe-t-il un équivalent de range() 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
109
Jonathan

Je ne connais pas de fonction intégrée, mais en écrire une comme ceci 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 à cette question ou, comme l'a mentionné @Tadhg, vous pouvez utiliser decimal.Decimal comme argument jump. Assurez-vous de l'initialiser avec une chaîne plutôt qu'un float.

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

Et alors:

>>> list(drange(0, 100, '0.1'))[-1]
99.9
86
kichik

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

J'avais l'habitude d'utiliser numpy.arange, mais des complications ont été rencontrées pour contrôler le nombre d'éléments renvoyés, en raison d'erreurs en virgule flottante. Alors maintenant, j'utilise linspace, par exemple:

>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([  0.        ,   3.33333333,   6.66666667,  10.        ])
67
wim

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

Eagerly évalué (2.x range):

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

Lazly évalué (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.
10
Karl Knechtel

avec 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]
8
Ayush

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

more_itertools.numeric_range(start, stop, step) agit comme la plage de fonctions intégrée, mais peut gérer les types float, 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)
4
William Rusnack

Il n’existe pas de fonction intégrée de ce type, mais vous pouvez utiliser les éléments suivants (code Python 3) pour effectuer le travail en toute sécurité, comme le permet Python.

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

3
marcotama

Une solution sans dépendances numériques, etc. a été fournie par Kichik, mais en raison de l'arithmétique à virgule flottante , elle se comporte souvent de manière inattendue. Comme indiqué par moi et blubberdiblub , d’autres éléments se glissent facilement dans le résultat. Par exemple, naive_frange(0.0, 1.0, 0.1) donnerait 0.999... comme dernière valeur et donnerait donc 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

En raison de la multiplication, les erreurs d'arrondi ne s'accumulent pas. L’utilisation de epsilon permet d’éviter les erreurs d’arrondi de la multiplication, même si des problèmes peuvent bien sûr se poser à propos des très petites et des 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 la forme un GitHub Gist .

3
Akseli Palén

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

Comme le montrent clairement tous les articles, il n’existe pas de version à virgule flottante de range(). Cela dit, l’omission a du sens si l’on considère que la fonction range() est souvent utilisée en tant qu’index (et bien sûr, cela signifie un générateur accessor). Ainsi, lorsque nous appelons range(0,40), nous disons en réalité que nous voulons 40 valeurs commençant de 0 à 40, mais non compris 40. 

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

Ainsi, une fonction frange() dépendant de son utilisation présentera toujours un comportement intuitif contre-intuitif; il contient soit trop de valeurs que celles perçues du point de vue de l'indexation, soit 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 indiqué, numpy.linspace() exécute 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 d’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 fonction round 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 & Performance

Bien entendu, compte tenu de ce qui précède, ces fonctions ont un cas d'utilisation relativement 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 timings:

>>> 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 paramètre skip n'est pas divisible par la valeur stop, 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 au bout du compte, la meilleure approche serait probablement d'utiliser simplement Numpy.

2
Greenstick

Une version simplifiée sans bibliothèque

Aw, heck - 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 de base est que nsteps est le nombre d'étapes pour vous rendre du début à la fin et que range(nsteps) émet toujours des entiers afin d'éviter toute perte de précision. La dernière étape consiste à mapper [0..nsteps] linéairement sur [start..stop].

modifier

Si, comme alancalvitti , vous souhaitez que la série ait une représentation rationnelle exacte, vous pouvez toujours utiliser 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 et non un générateur. Mais cela suffisait à mes besoins.

1
fearless_fool

j'ai écrit une fonction qui retourne un tuple d'une plage de nombres en virgule flottante en double précision, sans décimales, au-delà des centièmes. il s'agissait simplement d'analyser les valeurs d'intervalle telles que les chaînes et de séparer les valeurs en excès. 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
0
chris mcinnis

Il y a ici plusieurs réponses qui ne gèrent pas les cas simples, comme les étapes négatives, le démarrage incorrect, l'arrêt, etc. Voici la version qui gère bon nombre de ces cas, donnant le même comportement que 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 ceci provoquerait une erreur step = 0, exactement comme range en natif. Une différence est que la plage native retourne un objet indexable et réversible, alors que la précédente ne le fait pas.

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

0
Shital Shah