range () dla pływaków


140

Czy istnieje range()odpowiednik dla pływaków w Pythonie?

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

1
To nie są ułamki, ale pływaki. A pływaki ... cóż, prawdopodobnie dadzą inne wyniki niż się spodziewasz.

6
Szybkim obejściem byłoby traktowanie liczb całkowitych jako dziesiętnych, np .: range(5, 50, 5), a następnie dzielenie każdej liczby przez 10.
NullUserException

@delnan - zaktualizowano. Jestem gotów zaakceptować drobne niedokładności dla wygody posiadania zakresu zmiennoprzecinkowego
Jonathan,


@NullUserException - to tylko przykład - prawdziwy kod jest oczywiście parametryczny :)
Jonathan

Odpowiedzi:


97

Nie znam wbudowanej funkcji, ale napisanie takiej nie powinno być zbyt skomplikowane.

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

Jak wspominają komentarze, może to spowodować nieprzewidywalne wyniki, takie jak:

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

Aby uzyskać oczekiwany wynik, możesz użyć jednej z pozostałych odpowiedzi w tym pytaniu lub, jak wspomniał @Tadhg, możesz użyć decimal.Decimaljako jumpargumentu. Pamiętaj, aby zainicjować go za pomocą łańcucha, a nie liczby zmiennoprzecinkowej.

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

Lub nawet:

import decimal

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

I wtedy:

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

34
Motto Pythona brzmi tak naprawdę Powinien być jeden - a najlepiej tylko jeden - oczywisty sposób na zrobienie tego . Ale Python i tak jest niesamowity :)
Jonathan

3
>>> print list(frange(0,100,0.1))[-1]==100.0będzieFalse
Volodimir Kopey

frangemoże zadziałać nieoczekiwanie. Ze względu na klątwę arytmetyki zmiennoprzecinkowej , na przykład frange(0.0, 1.0, 0.1)daje 11 wartości, gdzie ostatnia wartość jest 0.9999999999999999. Praktyczna poprawa byłaby możliwa, while x + sys.float_info.epsilon < y:chociaż nawet to może się nie udać w przypadku dużej liczby .
Akseli Palén

10
-1 Proszę nie używać tego kodu , przynajmniej nie w oprogramowaniu, które mogłoby kiedykolwiek wpłynąć na moje życie. Nie ma sposobu, aby działał niezawodnie. Nie używaj też odpowiedzi Akseli Palén. Użyj odpowiedzi Xaerxessa lub wima (z wyjątkiem zignorowania części dotyczącej zakresu).
benrg

3
działa to świetnie, jeśli używaszdecimal.Decimal jako kroku zamiast pływaków.
Tadhg McDonald-Jensen

112

Możesz użyć:

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

lub użyj lambda / map:

map(lambda x: x/10.0, range(5, 50, 15))

1
A array (range (5,50,15)) / 10.0 jako numpy tablice mają operatory do obsługi dzielenia, mnożenia i tak dalej
edvaldig

2
@edvaldig: masz rację, nie wiedziałem o tym ... Niemniej jednak myślę, że arange(0.5, 5, 1.5)IMO jest bardziej czytelny.
Xaerxess

2
Wolę tę odpowiedź od akceptowanej, ponieważ pierwsze dwa przedstawione rozwiązania opierają się na iteracji po liczbach całkowitych i wyprowadzaniu ostatecznych liczb zmiennoprzecinkowych z liczb całkowitych. To jest bardziej wytrzymałe. Jeśli robisz to bezpośrednio za pomocą pływaków, ryzykujesz dziwne jednorazowe błędy ze względu na wewnętrzną reprezentację pływaków. Na przykład, jeśli spróbujesz list(frange(0, 1, 0.5)), działa dobrze i 1 jest wykluczone, ale jeśli spróbujesz list(frange(0, 1, 0.1)), ostatnia otrzymana wartość jest bliska 1,0, co prawdopodobnie nie jest tym, czego chcesz. Przedstawione tutaj rozwiązania nie mają tego problemu.
blubberdiblub

3
Nigdy nie używaj numpy.arange (sama dokumentacja numpy odradza to). Użyj numpy.linspace zgodnie z zaleceniami wim lub jednej z innych sugestii w tej odpowiedzi.
benrg

79

Kiedyś używałem, numpy.arangeale miałem pewne komplikacje w kontrolowaniu liczby elementów, które zwraca, z powodu błędów zmiennoprzecinkowych. Więc teraz używam linspacenp:

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

Nadal jednak występują błędy zmiennoprzecinkowe, bez użycia decimalnp .:np.linspace(-.1,10,num=5050)[0]
TNT

2
@TNT Nie, to nie jest błąd. Przekonasz się, że np.linspace(-.1,10,num=5050)[0] == -.1jest prawdą. Po prostu repr(np.float64('-0.1'))pokazuje więcej cyfr.
wim

1
Chociaż ten konkretny przykład nie pokazuje nadmiernego błędu zaokrąglania, istnieją przypadki niepowodzeń. Na przykład print(numpy.linspace(0, 3, 148)[49])drukuje, 0.9999999999999999gdy byłby idealny wynik 1.0. linspacewykonuje znacznie lepszą pracę niż arange, ale nie gwarantuje wygenerowania minimalnego możliwego błędu zaokrągleń.
user2357112 obsługuje Monikę

To jest gwarantowane, aby wykonać prawidłową obsługę końcowego i zawsze produkować dokładnie żądaną liczbę elementów.
user2357112 obsługuje Monikę

40

Pylab ma frange(właściwie opakowanie 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. ])

4
Frange jest przestarzała od wersji Matplotlib 2.2. Należy użyć numpy.arange.
kuzavas

13

Chętnie oceniane (2.x range):

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

Oceniane leniwie (2.x xrange, 3.x range):

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

Na przemian:

itertools.islice(itertools.imap(lambda x: x * .5, itertools.count()), 10)
# without applying the `islice`, we get an infinite stream of half-integers.

4
+1; ale dlaczego nie (x * .5 for x in range(10))jako wyrażenie generujące leniwą ocenę?
Tim Pietzcker

2
Bo to byłoby zbyt łatwe, jak sądzę? :)
Karl Knechtel

11

używanie itertools: leniwie obliczany zakres zmiennoprzecinkowy:

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

3
+1 za używanie itertools.takewhile. Jednak itertools.count(start, step)cierpi na skumulowane błędy zmiennoprzecinkowe. (Oceń takewhile(lambda x: x < 100, count(0, 0.1))na przykład.) takewhile(lambda x: x < stop, (start + i * step for i in count()))Zamiast tego napisałbym .
musiphil

6

Pomogłem dodać funkcję numeric_range do pakietu more-itertools .

more_itertools.numeric_range(start, stop, step) działa jak wbudowany zakres funkcji, ale może obsługiwać typy zmiennoprzecinkowe, dziesiętne i ułamkowe.

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

4

Nie ma takiej wbudowanej funkcji, ale możesz użyć następującego (kod Python 3), aby wykonać zadanie tak bezpiecznie, jak pozwala na to 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))

Możesz to wszystko zweryfikować, uruchamiając kilka asercji:

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

Kod dostępny na GitHub


4

Dlaczego w bibliotece standardowej nie ma implementacji zakresu zmiennoprzecinkowego?

Jak wyjaśniono we wszystkich zamieszczonych tutaj postach, nie ma wersji zmiennoprzecinkowej range(). To powiedziawszy, pominięcie ma sens, jeśli weźmiemy pod uwagę, że range()funkcja jest często używana jako generator indeksu (i oczywiście oznacza to akcesor ). Tak więc, kiedy wywołujemy range(0,40), w efekcie mówimy, że chcemy 40 wartości zaczynających się od 0, do 40, ale bez wartości 40.

Kiedy weźmiemy pod uwagę, że generowanie indeksu dotyczy w takim samym stopniu liczby indeksów, jak ich wartości, użycie implementacji typu float range()w bibliotece standardowej ma mniejszy sens. Na przykład, jeśli nazwiemy funkcjęfrange(0, 10, 0.25) , spodziewalibyśmy się uwzględnienia zarówno 0, jak i 10, ale dałoby to wektor z 41 wartościami.

Zatem frange()funkcja zależna od jej użycia zawsze będzie wykazywać sprzeczne z intuicją zachowanie; albo ma zbyt wiele wartości postrzeganych z perspektywy indeksowania, albo nie obejmuje liczby, która powinna zostać zwrócona z matematycznego punktu widzenia.

Matematyczny przypadek użycia

Powiedziawszy to, jak omówiono, numpy.linspace()ładnie wypada generowanie z perspektywy matematycznej:

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

Przypadek użycia indeksowania

A jeśli chodzi o perspektywę indeksowania, napisałem nieco inne podejście z pewną sztuczną magią ciągów, która pozwala nam określić liczbę miejsc dziesiętnych.

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

Podobnie możemy również skorzystać z funkcji wbudowanej roundi określić liczbę miejsc po przecinku:

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

Szybkie porównanie i wydajność

Oczywiście, biorąc pod uwagę powyższe omówienie, funkcje te mają dość ograniczony przypadek użycia. Niemniej jednak, oto krótkie porównanie:

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)

Wyniki są identyczne dla każdego:

-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

I niektóre czasy:

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

Wygląda na to, że metoda formatowania ciągów wygrywa w moim systemie.

Ograniczenia

Na koniec zademonstrowanie punktu z powyższej dyskusji i ostatnie ograniczenie:

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

Ponadto, gdy skipparametr nie jest podzielny przez stopwartość, może wystąpić luka ziewania, biorąc pod uwagę ten drugi problem:

# 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

Istnieją sposoby rozwiązania tego problemu, ale ostatecznie najlepszym podejściem byłoby po prostu użycie Numpy.


To dość pokręcony argument. range () należy po prostu spojrzeć na generator iteracji i to, czy jest używany w pętli for, czy do indeksowania, należy pozostawić wywołującym. Ludzie używają pływaków w pętli for od tysiącleci, a powyższe uzasadnienia są bezsensowne. Ludzie w komitetach Pythona spieprzyli tutaj dużo czasu i dobry argument prawdopodobnie został zagłuszony przez jakieś pokręcone uzasadnienia, takie jak powyżej. To takie proste i proste. Jest teraz zbyt wiele decyzji, takich jak powyżej, zapisanych w języku Python.
Shital Shah

3

Rozwiązanie bez NumPy itp zależnościami były dostarczone przez Kichik ale ze względu na pływających arytmetyki punktowych , często zachowuje się niespodziewanie. Jak zauważyłem przeze mnie i blubberdiblub , dodatkowe elementy łatwo wkradają się do wyniku. Na przykład naive_frange(0.0, 1.0, 0.1)dałby 0.999...jako ostatnią wartość, a tym samym dałby łącznie 11 wartości.

Solidna wersja jest dostępna tutaj:

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

Ponieważ mnożenie, błędy zaokrąglania nie kumulują się. Użycie epsilonznaku eliminuje ewentualny błąd zaokrąglenia mnożenia, chociaż oczywiście mogą pojawić się problemy na bardzo małych i bardzo dużych końcach. Teraz, zgodnie z oczekiwaniami:

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

I z nieco większymi liczbami:

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

Kod jest również dostępny jako GitHub Gist .


Nie udaje się to w przypadku frange (2,0, 17,0 / 6,0, 1,0 / 6,0). W żaden sposób nie można go kiedykolwiek uczynić solidnym.
benrg

@benrg Dzięki za zwrócenie uwagi! Doprowadziło mnie to do wniosku, że epsilon powinien zależeć od skoku, więc przejrzałem algorytm i naprawiłem problem. Ta nowa wersja jest znacznie bardziej solidna, prawda?
Akseli Palén

2

Prostsza wersja bez biblioteki

Do diabła - dorzucę prostą wersję bez biblioteki. Zapraszam do ulepszania go [*]:

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

Podstawową ideą jest nstepsliczba kroków, które prowadzą od początku do końca i range(nsteps)zawsze emituje liczby całkowite, więc nie ma utraty dokładności. Ostatnim krokiem jest liniowe odwzorowanie [0..nsteps] na [start..stop].

edytować

Jeśli, tak jak alancalvitti, chcesz, aby szereg miał dokładną racjonalną reprezentację, zawsze możesz użyć ułamków :

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

[*] W szczególności frange()zwraca listę, a nie generator. Ale to wystarczyło na moje potrzeby.


Jeśli chcesz dołączyć wartość stop do wyniku, dodając stop + skok, ta metoda następnie powraca do naiwnego wyniku ze złymi punktami zmiennoprzecinkowymi w środku, spróbuj frange(0,1.1,0.1)i jeszcze więcej tych z wyborem, takim jakfrange(0,1.05,0.1)
alancalvitti

@alancalvitti: Jaka jest Twoja definicja „złego” zmiennoprzecinkowego? Tak, wyniki mogą nie wyglądać dobrze, ale funkcja frange () zapewnia najbliższy zestaw równomiernie rozmieszczonych wartości w granicach reprezentacji zmiennoprzecinkowej. Jak byś to poprawił?
Fearless_fool

słuszna uwaga, jestem tak przyzwyczajony do języka wysokiego poziomu, w którym do takiego zadania przekraczałbyś racjonalne liczby, że Py czuje się jak asemblacja.
alancalvitti

Montaż? Hrrumph! ;) Oczywiście Python może zapewnić dokładną reprezentację z frakcjami: docs.python.org/3/library/fractions.html
fearless_fool

Tak, dziękuję, ale na przykład język, który lubię, automatycznie konwertuje te typy, więc 1/2 jest wymierne, a 1 / 2,0 to zmiennoprzecinkowe, nie ma potrzeby deklarowania ich jako takich - deklaracje zostaw Javie, co jest jeszcze bardziej niższy / montaż niż Py.
alancalvitti

2

Można to zrobić za pomocą 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]

Uwaga 1: Z dyskusji w sekcji komentarzy tutaj: „nigdy nie używaj numpy.arange()(sama dokumentacja numpy odradza). Użyj numpy.linspace zgodnie z zaleceniami wim lub jednej z innych sugestii w tej odpowiedzi”

Uwaga 2: Przeczytałem dyskusję w kilku komentarzach tutaj, ale po powrocie do tego pytania po raz trzeci uważam, że ta informacja powinna zostać umieszczona w bardziej czytelnym miejscu.


2

Jak napisał Kichik , nie powinno to być zbyt skomplikowane. Jednak ten kod:

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

Jest nieodpowiedni ze względu na skumulowany efekt błędów podczas pracy z pływakami. Dlatego otrzymujesz coś takiego:

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

Chociaż oczekiwane zachowanie byłoby:

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

Rozwiązanie 1

Skumulowany błąd można po prostu zmniejszyć za pomocą zmiennej indeksującej. Oto przykład:

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

Ten przykład działa zgodnie z oczekiwaniami.

Rozwiązanie 2

Brak funkcji zagnieżdżonych. Tylko chwila i zmienna licznika:

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

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

Ta funkcja również będzie działać dobrze, z wyjątkiem przypadków, w których chcesz odwrócić zakres. Na przykład:

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

Rozwiązanie 1 w tym przypadku będzie działać zgodnie z oczekiwaniami. Aby ta funkcja działała w takich sytuacjach, musisz zastosować hack, podobny do następującego:

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

Dzięki temu hackowi możesz używać tych funkcji z negatywnymi krokami:

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

Rozwiązanie 3

Możesz pójść jeszcze dalej dzięki zwykłej bibliotece standardowej i skomponować funkcję zakresu dla większości typów liczbowych:

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

Ten generator jest zaadaptowany z książki Fluent Python (Rozdział 14. Iterables, Iterators and Generators). Nie będzie działać przy malejących zakresach. Musisz zastosować hack, podobnie jak w poprzednim rozwiązaniu.

Możesz użyć tego generatora w następujący sposób, na przykład:

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

I oczywiście możesz go również używać z float i int .

Bądź ostrożny

Jeśli chcesz używać tych funkcji z krokami ujemnymi, powinieneś dodać czek na znak kroku, np:

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

Najlepszą opcją jest podbicie StopIteration, jeśli chcesz naśladować rangesamą funkcję.

Zasięg naśladowania

Jeśli chcesz naśladować rangeinterfejs funkcji, możesz zapewnić sprawdzanie argumentów:

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

Myślę, że masz rację. Możesz korzystać z dowolnej z tych funkcji (z wyjątkiem pierwszej) i wszystko, czego potrzebujesz do nich, to standardowa biblioteka Pythona.


1

Napisałem funkcję, która zwraca krotkę z zakresu liczb zmiennoprzecinkowych o podwójnej precyzji bez żadnych miejsc dziesiętnych poza częściami setnymi. chodziło po prostu o przeanalizowanie wartości zakresu, takich jak łańcuchy, i oddzielenie nadmiaru. Używam go do wyświetlania zakresów do wyboru w interfejsie użytkownika. Mam nadzieję, że ktoś inny uzna to za przydatne.

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

1

Stosowanie

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

Zaokrąglić każdy krok do N miejsc dziesiętnych

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]

Kod

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

Dlaczego wybrać tę odpowiedź?

  • Wiele innych odpowiedzi zawiesza się po wyświetleniu prośby o odliczanie.
  • Wiele innych odpowiedzi da nieprawidłowo zaokrąglone wyniki.
  • Inne odpowiedzi oparte na np.linspacetrafieniach i chybieniach mogą, ale nie muszą, działać z powodu trudności w wyborze prawidłowej liczby dywizji. np.linspacenaprawdę zmaga się z przyrostami dziesiętnymi wynoszącymi 0,1, a kolejność podziałów w formule służąca do konwersji przyrostu na liczbę podziałów może skutkować poprawnym lub uszkodzonym kodem.
  • Inne odpowiedzi na podstawie np.arangesą przestarzałe.

W razie wątpliwości wypróbuj cztery powyższe przypadki testowe.


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)

Należy pamiętać, że pierwsza litera zakresu jest duża. Ta metoda nazewnictwa nie jest zalecana dla funkcji w Pythonie. Jeśli chcesz, możesz zmienić zakres na coś takiego jak drange lub frange. Funkcja „Zakres” zachowuje się tak, jak tego chcesz. Możesz sprawdzić jego instrukcję tutaj [ http://reference.wolfram.com/language/ref/Range.html ].


0

Myślę, że istnieje bardzo prosta odpowiedź, która naprawdę emuluje wszystkie cechy zakresu, ale zarówno dla liczby zmiennoprzecinkowej, jak i całkowitej. W tym rozwiązaniu po prostu załóżmy, że twoje przybliżenie domyślnie to 1e-7 (lub takie, które wybierzesz) i możesz je zmienić, wywołując funkcję.

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)

0

Będą oczywiście pewne błędy zaokrągleń, więc nie jest to idealne, ale to jest to, co zwykle używam w aplikacjach, które nie wymagają dużej precyzji. Jeśli chcesz, aby to było dokładniejsze, możesz dodać dodatkowy argument, aby określić, jak postępować z błędami zaokrąglania. Być może przekazanie funkcji zaokrąglającej może uczynić ją rozszerzalną i pozwolić programiście określić, jak postępować z błędami zaokrąglania.

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

Jeśli napiszę:

arange(0, 1, 0.1)

Wyświetli:

[0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9]

-1

Czy istnieje odpowiednik range () dla liczb zmiennoprzecinkowych w Pythonie? NIE Użyj tego:

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

3
Bardzo złe rozwiązanie, spróbuj f_range(0.01,0.02,0.001)... Dla większości celów praktycznych, arangez NumPy jest proste, bezpieczne i szybkie rozwiązanie.
Bart

Masz rację. Z numpy jest 1,8 szybszy niż mój kod.
Grigor Kolev

Masz rację. Z numpy jest 1,8 szybszy niż mój kod. Ale system, w którym pracuję, jest całkowicie zamknięty. Tylko Python i pyserial już nie.
Grigor Kolev

-2

Istnieje kilka odpowiedzi, które nie obsługują prostych przypadków skrajnych, takich jak krok ujemny, zły start, stop itp. Oto wersja, która obsługuje wiele z tych przypadków poprawnie, zachowując takie samo zachowanie jak natywny 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  

Zauważ, że spowoduje to błąd step = 0, tak jak native range. Jedna różnica polega na tym, że zakres natywny zwraca obiekt, który jest indeksowalny i odwracalny, podczas gdy powyższy nie.

Możesz bawić się tym kodem i testami tutaj.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.