range () für float

140

Gibt es ein range()Äquivalent für Floats in 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
quelle
1
Das sind keine Brüche, sondern schwimmt. Und Schwimmer liefern wahrscheinlich andere Ergebnisse als erwartet.
6
Eine schnelle Problemumgehung wäre, Ganzzahlen als Dezimalstellen zu behandeln, z. B.: range(5, 50, 5)Und dann einfach jede Zahl durch 10 zu teilen.
NullUserException
@delnan - aktualisiert. Ich bin bereit, winzige Ungenauigkeiten zu akzeptieren, um eine Float-Reichweite zu haben
Jonathan
2
Mögliches Duplikat des Python-Dezimalbereichs () -Schrittswerts
Jonathan
@NullUserException - dies ist nur ein Beispiel - der echte Code ist natürlich parametrisch :)
Jonathan

Antworten:

97

Ich weiß nicht , eine eingebaute Funktion, aber Schreiben ist eine wie diese nicht zu kompliziert sein sollte.

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

Wie in den Kommentaren erwähnt, kann dies zu unvorhersehbaren Ergebnissen führen wie:

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

Um das erwartete Ergebnis zu erhalten, können Sie eine der anderen Antworten in dieser Frage verwenden oder, wie @Tadhg erwähnt, decimal.Decimalals jumpArgument verwenden. Stellen Sie sicher, dass Sie es mit einem String und nicht mit einem Float initialisieren.

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

Oder auch:

import decimal

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

Und dann:

>>> list(drange(0, 100, '0.1'))[-1]
99.9
kichik
quelle
34
Pythons Motto lautet eigentlich: Es sollte einen - und vorzugsweise nur einen - offensichtlichen Weg geben, dies zu tun . Aber Python ist trotzdem großartig :)
Jonathan
3
>>> print list(frange(0,100,0.1))[-1]==100.0wird seinFalse
Volodimir Kopey
frangekann unerwartet arbeiten. Aufgrund des Fluches der Gleitkomma-Arithmetik ergeben sich beispielsweise frange(0.0, 1.0, 0.1)11 Werte, wobei der letzte Wert ist 0.9999999999999999. Eine praktische Verbesserung wäre, while x + sys.float_info.epsilon < y:obwohl selbst dies bei großen Zahlen wahrscheinlich scheitern kann .
Akseli Palén
10
-1 Bitte verwenden Sie diesen Code nicht , zumindest nicht in Software, die jemals mein Leben beeinträchtigen könnte. Es gibt keine Möglichkeit, es zuverlässig zum Laufen zu bringen. Verwenden Sie auch nicht die Antwort von Akseli Palén. Verwenden Sie die Antwort von Xaerxess oder wim (außer den Teil über arange zu ignorieren).
Benrg
3
Dies funktioniert hervorragend, wenn Siedecimal.Decimal als Schritt anstelle von Floats verwenden.
Tadhg McDonald-Jensen
112

Sie können entweder verwenden:

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

oder benutze Lambda / Karte:

map(lambda x: x/10.0, range(5, 50, 15))
Xaerxess
quelle
1
Und Array (Bereich (5,50,15)) / 10,0 als Numpy-Arrays haben Operatoren für die Behandlung von Division, Multiplikation usw.
edvaldig
2
@edvaldig: du hast recht, ich wusste nichts davon ... Trotzdem denke ich, dass arange(0.5, 5, 1.5)IMO besser lesbar ist.
Xaerxess
2
Ich bevorzuge diese Antwort gegenüber der akzeptierten, da die ersten beiden vorgestellten Lösungen auf der Iteration über Ganzzahlen und der Ableitung der endgültigen Gleitkommazahlen aus den Ganzzahlen basieren. Das ist robuster. Wenn Sie dies direkt mit Floats tun, besteht die Gefahr, dass seltsame einmalige Fehler auftreten, da Floats intern dargestellt werden. Wenn Sie es beispielsweise versuchen list(frange(0, 1, 0.5)), funktioniert es einwandfrei und 1 wird ausgeschlossen. Wenn Sie es jedoch versuchen list(frange(0, 1, 0.1)), liegt der letzte Wert nahe bei 1,0, was wahrscheinlich nicht das ist, was Sie wollen. Die hier vorgestellten Lösungen haben dieses Problem nicht.
Blubberdiblub
3
Verwenden Sie niemals numpy.arange (die numpy-Dokumentation selbst empfiehlt dagegen). Verwenden Sie numpy.linspace, wie von wim empfohlen, oder einen der anderen Vorschläge in dieser Antwort.
Benrg
79

Früher habe ich verwendet, hatte numpy.arangeaber aufgrund von Gleitkommafehlern einige Komplikationen bei der Steuerung der Anzahl der zurückgegebenen Elemente. Also benutze ich jetzt linspacezB:

>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([  0.        ,   3.33333333,   6.66666667,  10.        ])
wim
quelle
Es gibt jedoch immer noch Gleitkommafehler, ohne die Verwendung von decimalzB:np.linspace(-.1,10,num=5050)[0]
TNT
2
@TNT Nein, das ist kein Fehler. Sie werden feststellen, np.linspace(-.1,10,num=5050)[0] == -.1ist wahr. Es ist nur so, dass das repr(np.float64('-0.1'))mehr Ziffern zeigt.
wim
1
Während dieses spezielle Beispiel keinen übermäßigen Rundungsfehler zeigt, gibt es Fehlerfälle. Zum Beispiel print(numpy.linspace(0, 3, 148)[49])druckt , 0.9999999999999999wenn das ideale Ergebnis wäre 1.0. linspacemacht einen viel besseren Job als arange, aber es ist nicht garantiert, dass der minimal mögliche Rundungsfehler erzeugt wird.
user2357112 unterstützt Monica
Es wird garantiert, dass die Endpunktbehandlung korrekt ausgeführt wird und immer genau die angeforderte Anzahl von Elementen erzeugt wird.
user2357112 unterstützt Monica
40

Pylab hat frange(eigentlich eine Hülle für 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. ])
Klopfen
quelle
4
Frange ist seit matplotlib Version 2.2 veraltet. numpy.arange sollte verwendet werden.
Kuzavas
13

Eifrig bewertet (2.x range):

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

Faul bewertet (2.x xrange, 3.x range):

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

Abwechselnd:

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
quelle
4
+1; aber warum nicht (x * .5 for x in range(10))als Generatorausdruck für faule Auswertung?
Tim Pietzcker
2
Weil das zu einfach wäre, denke ich? :)
Karl Knechtel
11

using itertools: träge ausgewerteter Gleitkommabereich:

>>> 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
quelle
3
+1 für die Verwendung itertools.takewhile. Leidet jedoch itertools.count(start, step)unter akkumulierten Gleitkommafehlern. ( takewhile(lambda x: x < 100, count(0, 0.1))Zum Beispiel auswerten .) Ich würde takewhile(lambda x: x < stop, (start + i * step for i in count()))stattdessen schreiben .
Musiphil
6

Ich habe geholfen, die Funktion numeric_range zum Paket more-itertools hinzuzufügen .

more_itertools.numeric_range(start, stop, step) verhält sich wie der integrierte Funktionsbereich, kann jedoch Gleitkomma-, Dezimal- und Bruchteile verarbeiten.

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

Es gibt keine solche integrierte Funktion, aber Sie können Folgendes verwenden (Python 3-Code), um die Arbeit so sicher auszuführen, wie es Python zulässt.

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

Sie können alles überprüfen, indem Sie einige Zusicherungen ausführen:

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 auf GitHub verfügbar

marcotama
quelle
4

Warum gibt es in der Standardbibliothek keine Implementierung eines Gleitkommabereichs?

Wie aus allen Beiträgen hier hervorgeht, gibt es keine Gleitkommaversion von range(). Das Auslassen ist jedoch sinnvoll, wenn man bedenkt, dass die range()Funktion häufig als Indexgenerator (und natürlich als Accessor- Generator) verwendet wird. Wenn wir also anrufen range(0,40), sagen wir tatsächlich, wir wollen 40 Werte, die bei 0 beginnen, bis zu 40, aber ohne 40 selbst.

Wenn wir bedenken, dass es bei der Indexgenerierung sowohl um die Anzahl der Indizes als auch um deren Werte geht, ist die Verwendung einer Float-Implementierung range()in der Standardbibliothek weniger sinnvoll. Wenn wir zum Beispiel die Funktion frange(0, 10, 0.25)aufrufen würden, würden wir erwarten, dass sowohl 0 als auch 10 enthalten sind, aber das würde einen Vektor mit 41 Werten ergeben.

Daher zeigt eine frange()Funktion in Abhängigkeit von ihrer Verwendung immer ein kontraintuitives Verhalten. Entweder hat es zu viele Werte, wie aus der Indexierungsperspektive wahrgenommen, oder es enthält keine Zahl, die aus mathematischer Sicht vernünftigerweise zurückgegeben werden sollte.

Der mathematische Anwendungsfall

Wie bereits erwähnt, numpy.linspace()führt die Generierung die Generierung mit der mathematischen Perspektive gut durch:

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

Der Anwendungsfall für die Indizierung

Und für die Indizierungsperspektive habe ich einen etwas anderen Ansatz mit etwas kniffliger String-Magie geschrieben, mit dem wir die Anzahl der Dezimalstellen angeben können.

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

In ähnlicher Weise können wir auch die integrierte roundFunktion verwenden und die Anzahl der Dezimalstellen angeben:

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

Ein schneller Vergleich und Leistung

Angesichts der obigen Diskussion haben diese Funktionen natürlich einen ziemlich begrenzten Anwendungsfall. Hier ein kurzer Vergleich:

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)

Die Ergebnisse sind jeweils identisch:

-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

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

Sieht aus wie die String-Formatierungsmethode durch ein Haar auf meinem System gewinnt.

Die Einschränkungen

Und schließlich eine Demonstration des Punktes aus der obigen Diskussion und eine letzte Einschränkung:

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

Wenn der skipParameter nicht durch den stopWert teilbar ist, kann es bei letzterem Problem zu einer gähnenden Lücke kommen:

# 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

Es gibt Möglichkeiten, dieses Problem zu beheben, aber am Ende des Tages wäre es wahrscheinlich der beste Ansatz, nur Numpy zu verwenden.

Greenstick
quelle
Dies ist ein ziemlich verdrehtes Argument. range () sollte einfach als Iterationsgenerator betrachtet werden und ob es in der for-Schleife oder zum Indizieren verwendet wird, sollte den Aufrufern überlassen bleiben. Die Leute benutzen seit Jahrtausenden Floats in for-Schleifen und darüber hinaus sind Rechtfertigungen unsinnig. Die Leute in Python-Komitees haben es hier sehr vermasselt und gute Argumente wurden wahrscheinlich durch einige verdrehte Rechtfertigungen wie oben übertönt. So einfach ist das. Es gibt jetzt zu viele Entscheidungen wie oben in der Python-Sprache verankert.
Shital Shah
3

Eine Lösung ohne numpy usw. Abhängigkeiten wurde von kichik bereitgestellt, aber aufgrund der Gleitkomma-Arithmetik verhält es sich oft unerwartet. Wie von mir und blubberdiblub festgestellt , schleichen sich zusätzliche Elemente leicht in das Ergebnis ein. Zum Beispiel naive_frange(0.0, 1.0, 0.1)würde 0.999...als letzter Wert ergeben und somit insgesamt 11 Werte ergeben.

Eine robuste Version finden Sie hier:

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

Aufgrund der Multiplikation häufen sich die Rundungsfehler nicht an. Die Verwendung von epsilonsorgt für einen möglichen Rundungsfehler der Multiplikation, obwohl natürlich Probleme in den sehr kleinen und sehr großen Bereichen auftreten können. Nun, wie erwartet:

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

Und mit etwas größeren Zahlen:

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

Der Code ist auch als GitHub Gist verfügbar .

Akseli Palén
quelle
Dies schlägt mit dem Bereich fehl (2.0, 17.0 / 6.0, 1.0 / 6.0). Es gibt keine Möglichkeit, es jemals robust zu machen.
Benrg
@benrg Danke für den Hinweis! Es führte mich zu der Erkenntnis, dass das Epsilon vom Sprung abhängen sollte, also überprüfte ich den Algorithmus und reparierte das Problem. Diese neue Version ist viel robuster, nicht wahr?
Akseli Palén
2

Eine einfachere Version ohne Bibliothek

Aw, zum Teufel - ich werde eine einfache Version ohne Bibliothek einwerfen. Fühlen Sie sich frei, es zu verbessern [*]:

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

Die Kernidee ist, dass nstepsdie Anzahl der Schritte von Anfang bis Ende reicht und range(nsteps)immer ganze Zahlen ausgibt, damit keine Genauigkeitsverluste auftreten. Der letzte Schritt besteht darin, [0..nsteps] linear auf [start..stop] abzubilden.

bearbeiten

Wenn Sie wie bei alancalvitti eine exakte rationale Darstellung der Serie wünschen , können Sie immer Brüche verwenden :

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

[*] Gibt insbesondere frange()eine Liste zurück, keinen Generator. Aber es genügte für meine Bedürfnisse.

furchtloser Dummkopf
quelle
Wenn Sie den Stoppwert in die Ausgabe aufnehmen möchten, indem Sie Stopp + Sprung hinzufügen, kehrt diese Methode zum naiven Ergebnis mit schlechten Gleitkommazahlen in der Mitte zurück. Versuchen Sie es frange(0,1.1,0.1)mit noch mehr von denen mit einer Auswahl wiefrange(0,1.05,0.1)
alancalvitti
@alancalvitti: Was ist Ihre Definition eines "schlechten" Gleitkommas? Ja, die Ergebnisse werden möglicherweise nicht gut gedruckt, aber frange () liefert den engsten Satz gleichmäßig verteilter Werte innerhalb der Grenzen der Gleitkommadarstellung. Wie würden Sie es verbessern?
Fearless_fool
Guter Punkt, ich bin so an eine Hochsprache gewöhnt, in der man für eine solche Aufgabe über rationale Zahlen streicht, dass Py sich wie eine Versammlung fühlt.
Alancalvitti
Versammlung? Hrrumph! ;) Natürlich exakte Darstellung mit Fraktionen liefern Python CAN: docs.python.org/3/library/fractions.html
fearless_fool
Richtig, danke, aber zum Beispiel konvertiert die Sprache, die ich mag, diese Typen automatisch. 1/2 ist also rational, während 1 / 2.0 float ist, müssen sie nicht als solche deklariert werden - überlassen Sie Deklarationen Java, was noch mehr ist niedriger / Montage als Py.
Alancalvitti
2

Dies kann mit numpy.arange erfolgen (Start, Stopp, Schrittweite)

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]

Hinweis 1: Aus der Diskussion im Kommentarbereich hier: "Niemals verwenden numpy.arange()(die Numpy-Dokumentation selbst empfiehlt dagegen). Verwenden Sie numpy.linspace, wie von wim empfohlen, oder einen der anderen Vorschläge in dieser Antwort."

Anmerkung 2: Ich habe die Diskussion hier in einigen Kommentaren gelesen, aber nachdem ich jetzt zum dritten Mal auf diese Frage zurückgekommen bin, sollte diese Information meiner Meinung nach besser lesbar sein.

mrk
quelle
2

Wie Kichik schrieb, sollte dies nicht zu kompliziert sein. Jedoch dieser Code:

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

Ist wegen der kumulativen Auswirkung von Fehlern bei der Arbeit mit Floats unangemessen . Deshalb erhalten Sie so etwas wie:

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

Während das erwartete Verhalten wäre:

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

Lösung 1

Der kumulative Fehler kann einfach mithilfe einer Indexvariablen reduziert werden. Hier ist das Beispiel:

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

Dieses Beispiel funktioniert wie erwartet.

Lösung 2

Keine verschachtelten Funktionen. Nur eine Weile und eine Zählervariable:

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

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

Diese Funktion funktioniert auch gut, außer in den Fällen, in denen Sie den umgekehrten Bereich wünschen. Z.B:

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

Lösung 1 funktioniert in diesem Fall wie erwartet. Damit diese Funktion in solchen Situationen funktioniert, müssen Sie einen Hack anwenden, der dem folgenden ähnelt:

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

Mit diesem Hack können Sie diese Funktionen mit negativen Schritten verwenden:

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

Lösung 3

Mit der einfachen Standardbibliothek können Sie noch weiter gehen und eine Bereichsfunktion für die meisten numerischen Typen erstellen:

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

Dieser Generator wurde aus dem Fluent Python-Buch (Kapitel 14. Iterables, Iteratoren und Generatoren) übernommen. Es funktioniert nicht mit abnehmenden Bereichen. Sie müssen einen Hack anwenden, wie in der vorherigen Lösung.

Sie können diesen Generator beispielsweise wie folgt verwenden:

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

Und natürlich können Sie es auch mit float und int verwenden .

Achtung

Wenn Sie diese Funktionen mit negativen Schritten verwenden möchten, sollten Sie das Schrittzeichen überprüfen, z. B.:

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

Die beste Option ist hier das Erhöhen StopIteration, wenn Sie die rangeFunktion selbst nachahmen möchten .

Mimic Reichweite

Wenn Sie die rangeFunktionsschnittstelle nachahmen möchten , können Sie einige Argumentprüfungen durchführen:

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

Ich denke, du hast den Punkt. Sie können mit jeder dieser Funktionen (außer der allerersten) arbeiten und alles, was Sie für sie benötigen, ist eine Python-Standardbibliothek.

stolpa4
quelle
1

Ich habe eine Funktion geschrieben, die ein Tupel aus einem Bereich von Gleitkommazahlen mit doppelter Genauigkeit ohne Dezimalstellen jenseits der Hundertstel zurückgibt. Es ging einfach darum, die Bereichswerte wie Zeichenfolgen zu analysieren und den Überschuss abzuspalten. Ich verwende es zum Anzeigen von Bereichen, um innerhalb einer Benutzeroberfläche auszuwählen. Ich hoffe, jemand anderes findet es nützlich.

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

Verwendung

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

Rundung jedes Schritts auf N Dezimalstellen

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

Warum diese Antwort wählen?

  • Viele andere Antworten hängen, wenn Sie zum Countdown aufgefordert werden.
  • Viele andere Antworten führen zu falsch gerundeten Ergebnissen.
  • Andere Antworten, die auf basieren, np.linspacesind Treffer und Fehler. Sie können aufgrund von Schwierigkeiten bei der Auswahl der richtigen Anzahl von Abteilungen funktionieren oder auch nicht. np.linspaceWirklich Probleme mit Dezimalinkrementen von 0,1, und die Reihenfolge der Unterteilungen in der Formel, um das Inkrement in eine Anzahl von Teilungen umzuwandeln, kann entweder zu korrektem oder fehlerhaftem Code führen.
  • Andere Antworten, die auf basieren, np.arangesind veraltet.

Versuchen Sie im Zweifelsfall die vier oben genannten Testfälle.

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

Bitte beachten Sie, dass der erste Buchstabe von Range Großbuchstaben ist. Diese Benennungsmethode wird für Funktionen in Python nicht empfohlen. Sie können Range in Drange oder Frange ändern, wenn Sie möchten. Die Funktion "Bereich" verhält sich so, wie Sie es möchten. Sie können das Handbuch hier überprüfen [ http://reference.wolfram.com/language/ref/Range.html ].

Hua Congyi
quelle
0

Ich denke, dass es eine sehr einfache Antwort gibt, die wirklich alle Funktionen des Bereichs emuliert, außer für float und integer. In dieser Lösung nehmen Sie einfach an, dass Ihre Annäherung standardmäßig 1e-7 ist (oder die von Ihnen gewählte) und Sie können sie ändern, wenn Sie die Funktion aufrufen.

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

Es wird natürlich einige Rundungsfehler geben, daher ist dies nicht perfekt, aber dies ist das, was ich allgemein für Anwendungen verwende, die keine hohe Präzision erfordern. Wenn Sie dies genauer machen möchten, können Sie ein zusätzliches Argument hinzufügen, um anzugeben, wie mit Rundungsfehlern umgegangen werden soll. Möglicherweise kann das Übergeben einer Rundungsfunktion diese erweiterbar machen und es dem Programmierer ermöglichen, anzugeben, wie mit Rundungsfehlern umgegangen werden soll.

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

Wenn ich schreibe:

arange(0, 1, 0.1)

Es wird ausgegeben:

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

Gibt es ein range () -Äquivalent für Floats in Python? NEIN Verwenden Sie diese:

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
quelle
3
Ziemlich schlechte Lösung, versuchen Sie f_range(0.01,0.02,0.001)... Für die meisten praktischen Zwecke ist arangeNumpy eine einfache, sichere und schnelle Lösung.
Bart
Du hast recht. Mit numpy ist 1.8 schneller als mein Code.
Grigor Kolev
Du hast recht. Mit numpy ist 1.8 schneller als mein Code. Aber das System, in dem ich arbeite, ist völlig geschlossen. Nur Python und Pyserial nicht mehr.
Grigor Kolev
-2

Hier gibt es mehrere Antworten, die keine einfachen Kantenfälle wie negative Schritte, falscher Start, Stopp usw. behandeln. Hier ist die Version, die viele dieser Fälle korrekt behandelt und dasselbe Verhalten wie native gibt 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  

Beachten Sie, dass dies genau wie native den Fehler step = 0 auslösen würde range . Ein Unterschied besteht darin, dass der native Bereich ein Objekt zurückgibt, das indizierbar und reversibel ist, während dies oben nicht der Fall ist.

Sie können hier mit diesem Code und Testfällen spielen.

Shital Shah
quelle