range () untuk mengapung

140

Apakah ada yang range()setara untuk mengapung di 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
sumber
1
Itu bukan pecahan tetapi mengapung. Dan pelampung adalah ... yah, kemungkinan memberikan hasil yang berbeda dari yang Anda harapkan.
6
Solusi cepat adalah memperlakukan bilangan bulat sebagai desimal, misalnya range(5, 50, 5)
:,
@delnan - diperbarui. Saya bersedia menerima ketidakakuratan kecil untuk kenyamanan memiliki jangkauan mengambang
Jonathan
2
kemungkinan duplikat rentang nilai desimal Python ()
Jonathan
@NullUserException - ini hanya sebuah contoh - kode sebenarnya tentu saja parametrik :)
Jonathan

Jawaban:

97

Saya tidak tahu fungsi bawaan, tetapi menulis yang seperti ini seharusnya tidak terlalu rumit.

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

Seperti yang disebutkan dalam komentar, ini dapat menghasilkan hasil yang tidak terduga seperti:

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

Untuk mendapatkan hasil yang diharapkan, Anda dapat menggunakan salah satu jawaban lain dalam pertanyaan ini, atau seperti yang disebutkan @Tadhg, Anda dapat menggunakan decimal.Decimalsebagai jumpargumen. Pastikan untuk menginisialisasi dengan string daripada float.

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

Atau bahkan:

import decimal

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

Lalu:

>>> list(drange(0, 100, '0.1'))[-1]
99.9
kichik
sumber
34
Moto Python sebenarnya Harus ada satu - dan lebih disukai hanya satu - cara yang jelas untuk melakukannya . Tapi Python yang luar biasa lagian :)
Jonathan
3
>>> print list(frange(0,100,0.1))[-1]==100.0akanFalse
Volodimir Kopey
frangedapat bekerja secara tidak terduga. Karena kutukan floating point arithmetics , misalnya frange(0.0, 1.0, 0.1)menghasilkan 11 nilai, di mana nilai terakhir adalah 0.9999999999999999. Peningkatan praktis akan terjadi while x + sys.float_info.epsilon < y:walaupun ini mungkin bisa gagal dengan jumlah besar .
Akseli Palén
10
-1 Harap jangan menggunakan kode ini , setidaknya tidak dalam perangkat lunak yang mungkin mempengaruhi hidup saya. Tidak ada cara untuk membuatnya bekerja dengan andal. Jangan gunakan jawaban Akseli Palén juga. Gunakan jawaban Xaerxess atau wim (kecuali abaikan bagian tentang arange).
benrg
3
ini berfungsi baik jika Anda menggunakandecimal.Decimal sebagai langkah alih-alih mengapung.
Tadhg McDonald-Jensen
112

Anda dapat menggunakan:

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

atau gunakan lambda / peta:

map(lambda x: x/10.0, range(5, 50, 15))
Xaerxess
sumber
1
Dan array (kisaran (5,50,15)) / 10,0 sebagai array numpy memiliki operator untuk menangani divisi, perkalian dan sebagainya
edvaldig
2
@edvaldig: Anda benar, saya tidak tahu tentang ini ... Meskipun demikian saya pikir arange(0.5, 5, 1.5)IMO lebih mudah dibaca.
Xaerxess
2
Saya lebih suka jawaban ini daripada yang diterima, karena dua solusi pertama yang disajikan didasarkan pada iterasi over integer dan menurunkan float akhir dari integer. Ini lebih kuat. Jika Anda melakukannya secara langsung dengan float, Anda berisiko memiliki kesalahan satu kali yang aneh karena bagaimana float diwakili secara internal. Misalnya, jika Anda mencoba list(frange(0, 1, 0.5)), itu berfungsi dengan baik dan 1 dikecualikan, tetapi jika Anda mencoba list(frange(0, 1, 0.1)), nilai terakhir yang Anda dapatkan dekat dengan 1,0, yang mungkin bukan yang Anda inginkan. Solusi yang disajikan di sini tidak memiliki masalah ini.
blubberdiblub
3
Jangan pernah gunakan numpy.arange (dokumentasi numpy sendiri merekomendasikannya). Gunakan numpy.linspace seperti yang direkomendasikan oleh wim, atau salah satu saran lain dalam jawaban ini.
benrg
79

Saya dulu menggunakan numpy.arangetetapi memiliki beberapa komplikasi mengendalikan jumlah elemen yang dikembalikan, karena kesalahan floating point. Jadi sekarang saya gunakan linspace, misalnya:

>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([  0.        ,   3.33333333,   6.66666667,  10.        ])
wim
sumber
Meskipun demikian, masih ada kesalahan floating point, tanpa menggunakan decimal, misalnya:np.linspace(-.1,10,num=5050)[0]
TNT
2
@TNT Tidak, itu bukan kesalahan. Anda akan menemukan np.linspace(-.1,10,num=5050)[0] == -.1itu Benar. Hanya saja repr(np.float64('-0.1'))menunjukkan angka lebih banyak.
wim
1
Sementara contoh khusus itu tidak menunjukkan kesalahan pembulatan berlebih, ada kasus kegagalan. Misalnya, print(numpy.linspace(0, 3, 148)[49])mencetak 0.9999999999999999ketika hasil yang ideal adalah 1.0. linspacemelakukan pekerjaan yang jauh lebih baik daripada arange, tetapi tidak dijamin untuk menghasilkan kesalahan pembulatan seminimal mungkin.
user2357112 mendukung Monica
Hal ini dijamin untuk melakukan penanganan endpoint yang benar, dan selalu menghasilkan persis jumlah yang diminta dari elemen.
user2357112 mendukung Monica
40

Pylab memiliki frange(pembungkus, sebenarnya, untuk 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. ])
Menepuk
sumber
4
Frange sudah tidak digunakan lagi sejak matplotlib versi 2.2. numpy.arange harus digunakan.
kuzavas
13

Dievaluasi dengan penuh semangat (2.x range):

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

Dievaluasi dengan sederhana (2.x xrange, 3.x range):

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

Bergantian:

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
sumber
4
+1; tetapi mengapa tidak (x * .5 for x in range(10))sebagai ekspresi generator untuk evaluasi malas?
Tim Pietzcker
2
Karena itu akan terlalu mudah, kurasa? :)
Karl Knechtel
11

menggunakan itertools: rentang floating point yang dievaluasi dengan malas:

>>> 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
sumber
3
+1 untuk digunakan itertools.takewhile. Namun, itertools.count(start, step)menderita akumulasi kesalahan floating-point. (Evaluate takewhile(lambda x: x < 100, count(0, 0.1))misalnya.) Saya akan menulis takewhile(lambda x: x < stop, (start + i * step for i in count()))sebagai gantinya.
musiphil
6

Saya membantu menambahkan fungsi numeric_range ke paket more-itertools .

more_itertools.numeric_range(start, stop, step) bertindak seperti rentang fungsi bawaan tetapi dapat menangani tipe mengapung, Desimal, dan Fraksi.

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

Tidak ada fungsi bawaan seperti itu, tetapi Anda dapat menggunakan yang berikut (kode Python 3) untuk melakukan pekerjaan seaman Python memungkinkan Anda untuk melakukannya.

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

Anda dapat memverifikasi semua itu dengan menjalankan beberapa pernyataan:

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

Kode tersedia di GitHub

marcotama
sumber
4

Mengapa Tidak Ada Implementasi Floating Point Range di Perpustakaan Standar?

Sebagaimana dijelaskan oleh semua posting di sini, tidak ada versi floating point dari range(). Yang mengatakan, kelalaian masuk akal jika kita menganggap bahwa range()fungsi tersebut sering digunakan sebagai indeks (dan tentu saja, itu berarti accessor ) generator. Jadi, ketika kita menelepon range(0,40), kita sebenarnya mengatakan kita ingin 40 nilai mulai dari 0, hingga 40, tetapi tidak termasuk 40 itu sendiri.

Ketika kami menganggap bahwa pembuatan indeks sama banyaknya dengan jumlah indeks sebagaimana nilainya, penggunaan implementasi float range()di perpustakaan standar kurang masuk akal. Misalnya, jika kita memanggil fungsifrange(0, 10, 0.25) , kita akan mengharapkan 0 dan 10 untuk dimasukkan, tetapi itu akan menghasilkan vektor dengan 41 nilai.

Dengan demikian, suatu frange()fungsi tergantung pada penggunaannya akan selalu menunjukkan perilaku kontra intuitif; itu baik memiliki nilai terlalu banyak yang dirasakan dari perspektif pengindeksan atau tidak termasuk nomor yang wajar harus dikembalikan dari perspektif matematika.

Kasus Penggunaan Matematika

Dengan itu, seperti yang dibahas, numpy.linspace()melakukan generasi dengan perspektif matematika dengan baik:

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

Kotak Penggunaan Pengindeksan

Dan untuk perspektif pengindeksan, saya telah menulis pendekatan yang sedikit berbeda dengan beberapa trik sulap yang memungkinkan kita menentukan jumlah tempat desimal.

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

Demikian pula, kita juga dapat menggunakan roundfungsi bawaan dan menentukan jumlah desimal:

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

Perbandingan & Kinerja Cepat

Tentu saja, mengingat pembahasan di atas, fungsi-fungsi ini memiliki kasus penggunaan yang cukup terbatas. Meskipun demikian, inilah perbandingan cepat:

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)

Hasilnya identik untuk masing-masing:

-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

Dan beberapa timing:

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

Sepertinya metode pemformatan string menang dengan rambut di sistem saya.

Keterbatasan

Dan akhirnya, demonstrasi poin dari diskusi di atas dan satu batasan terakhir:

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

Lebih lanjut, ketika skipparameter tidak dapat dibagi dengan stopnilai, mungkin ada kesenjangan menguap mengingat masalah yang terakhir:

# 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

Ada beberapa cara untuk mengatasi masalah ini, tetapi pada akhirnya, pendekatan terbaik mungkin hanya menggunakan Numpy.

Tongkat hijau
sumber
Ini adalah argumen yang agak bengkok. range () hanya harus dilihat pada generator iterasi dan apakah itu digunakan untuk loop atau untuk mengindeks sesuatu harus diserahkan kepada penelepon. Orang-orang telah menggunakan pelampung untuk loop selama ribuan tahun dan pembenaran di atas tidak masuk akal. Orang-orang di komite Python mengacau di sini waktu besar dan argumen yang baik mungkin tenggelam oleh beberapa pembenaran bengkok seperti di atas. Sesederhana dan sesederhana itu. Sekarang terlalu banyak keputusan seperti di atas yang diabadikan dalam bahasa Python.
Shital Shah
3

Sebuah solusi tanpa ketergantungan numpy dll disediakan oleh kichik tetapi karena aritmatika floating point , sering berperilaku tak terduga. Seperti dicatat oleh saya dan blubberdiblub , elemen tambahan dengan mudah menyelinap ke hasilnya. Misalnya naive_frange(0.0, 1.0, 0.1)akan menghasilkan 0.999...sebagai nilai terakhirnya dan dengan demikian menghasilkan 11 nilai secara total.

Versi yang kuat disediakan di sini:

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

Karena perkalian, kesalahan pembulatan tidak menumpuk. Penggunaan epsilonmengurus kemungkinan kesalahan pembulatan perkalian, meskipun masalah tentu saja bisa muncul di ujung yang sangat kecil dan sangat besar. Sekarang, seperti yang diharapkan:

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

Dan dengan angka yang agak besar:

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

Kode ini juga tersedia sebagai GitHub Gist .

Akseli Palén
sumber
Ini gagal dengan frange (2.0, 17.0 / 6.0, 1.0 / 6.0). Tidak mungkin itu bisa dibuat kuat.
benrg
@ Benrg Terima kasih telah menunjukkan ini! Itu membuat saya menyadari bahwa epsilon harus bergantung pada lompatan, jadi saya meninjau algoritme dan memperbaiki masalah. Versi baru ini jauh lebih kuat, bukan?
Akseli Palén
2

Versi perpustakaan-kurang sederhana

Ah, sial - Saya akan melemparkan dalam versi perpustakaan-kurang sederhana. Jangan ragu untuk memperbaikinya [*]:

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

Gagasan intinya adalah nstepsjumlah langkah untuk membuat Anda mulai dan berhentirange(nsteps) selalu mengeluarkan bilangan bulat sehingga tidak ada kehilangan keakuratan. Langkah terakhir adalah memetakan [0..nsteps] secara linear ke [start..stop].

sunting

Jika, seperti alancalvitti Anda ingin seri memiliki representasi rasional yang tepat, Anda selalu dapat menggunakan Fraksi :

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

[*] Secara khusus, frange()mengembalikan daftar, bukan generator. Tetapi itu sudah cukup untuk kebutuhan saya.

fearless_fool
sumber
Jika Anda ingin memasukkan nilai stop dalam output, dengan menambahkan stop + jump, metode ini kemudian kembali ke hasil naif dengan titik mengambang buruk di tengah, coba frange(0,1.1,0.1)dan bahkan lebih banyak lagi dengan pilihan sepertifrange(0,1.05,0.1)
alancalvitti
@alancalvitti: Apa definisi Anda tentang titik apung "buruk"? Ya, hasilnya mungkin tidak dapat dicetak dengan baik, tetapi frange () memberikan set nilai spasi terdekat terdekat dalam batas representasi floating point. Bagaimana Anda memperbaikinya?
fearless_fool
Poin baiknya, saya sudah terbiasa dengan bahasa tingkat tinggi di mana Anda akan berkisar pada bilangan rasional untuk tugas seperti itu, sehingga Py terasa seperti perakitan.
alancalvitti
Majelis? Huh! ;) Tentu saja Python DAPAT memberikan representasi yang tepat dengan Fraksi: docs.python.org/3/library/fractions.html
fearless_fool
Benar, terima kasih, tetapi sebagai contoh, bahasa yang saya suka secara otomatis mengkonversi tipe-tipe ini, jadi 1/2 adalah rasional, sementara 1 / 2.0 adalah float, tidak perlu mendeklarasikannya - tinggalkan deklarasi ke Jawa, yang bahkan lebih lebih rendah / perakitan dari Py.
alancalvitti
2

Ini bisa dilakukan dengan 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]

Catatan 1: Dari diskusi di bagian komentar di sini, "jangan pernah gunakan numpy.arange()(dokumentasi numpy sendiri merekomendasikannya). Gunakan numpy.linspace seperti yang direkomendasikan oleh wim, atau salah satu saran lain dalam jawaban ini"

Catatan 2: Saya telah membaca diskusi dalam beberapa komentar di sini, tetapi setelah kembali ke pertanyaan ini untuk ketiga kalinya sekarang, saya merasa informasi ini harus ditempatkan pada posisi yang lebih mudah dibaca.

mrk
sumber
2

Seperti yang ditulis kichik , ini seharusnya tidak terlalu rumit. Namun kode ini:

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

Tidak pantas karena efek kumulatif kesalahan saat bekerja dengan float. Itu sebabnya Anda menerima sesuatu seperti:

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

Sedangkan perilaku yang diharapkan adalah:

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

Solusi 1

Kesalahan kumulatif hanya dapat dikurangi dengan menggunakan variabel indeks. Inilah contohnya:

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

Contoh ini berfungsi seperti yang diharapkan.

Solusi 2

Tidak ada fungsi bersarang. Hanya sementara dan variabel penghitung:

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

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

Fungsi ini akan bekerja dengan baik juga, kecuali untuk kasus ketika Anda ingin rentang terbalik. Misalnya:

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

Solusi 1 dalam hal ini akan berfungsi seperti yang diharapkan. Agar fungsi ini berfungsi dalam situasi seperti itu, Anda harus menerapkan peretasan, mirip dengan yang berikut:

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

Dengan peretasan ini, Anda dapat menggunakan fungsi-fungsi ini dengan langkah-langkah negatif:

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

Solusi 3

Anda dapat melangkah lebih jauh dengan pustaka standar biasa dan menyusun fungsi rentang untuk sebagian besar tipe numerik:

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

Generator ini diadaptasi dari buku Fluent Python (Bab 14. Iterables, Iterators, dan generator). Ini tidak akan bekerja dengan rentang yang menurun. Anda harus menerapkan peretasan, seperti pada solusi sebelumnya.

Anda dapat menggunakan generator ini sebagai berikut, misalnya:

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

Dan tentu saja Anda bisa menggunakannya dengan float dan int juga.

Hati-hati

Jika Anda ingin menggunakan fungsi-fungsi ini dengan langkah-langkah negatif, Anda harus menambahkan tanda centang untuk tanda langkah, misalnya:

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

Opsi terbaik di sini adalah menaikkan StopIteration, jika Anda ingin meniru rangefungsi itu sendiri.

Rentang Mimic

Jika Anda ingin meniru rangeantarmuka fungsi, Anda dapat memberikan beberapa pemeriksaan argumen:

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

Saya pikir, Anda ada benarnya. Anda bisa menggunakan salah satu dari fungsi-fungsi ini (kecuali yang pertama) dan semua yang Anda butuhkan adalah pustaka standar python.

stolpa4
sumber
1

saya menulis sebuah fungsi yang mengembalikan tuple dari berbagai angka floating point presisi ganda tanpa tempat desimal di luar seratus. itu hanya masalah penguraian nilai rentang seperti string dan memisahkan kelebihannya. Saya menggunakannya untuk menampilkan rentang untuk dipilih dari dalam UI. Saya harap orang lain merasakan manfaatnya.

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

Pemakaian

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

Untuk membulatkan setiap langkah ke N tempat desimal

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]

Kode

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

Mengapa memilih jawaban ini?

  • Banyak jawaban lain akan menggantung ketika diminta menghitung mundur.
  • Banyak jawaban lain akan memberikan hasil yang salah.
  • Jawaban lain berdasarkan pada np.linspaceuntung-untungan, mereka mungkin atau mungkin tidak bekerja karena kesulitan dalam memilih jumlah divisi yang benar. np.linspacebenar-benar berjuang dengan peningkatan desimal 0,1, dan urutan pembagian dalam rumus untuk mengubah kenaikan menjadi beberapa pemisahan dapat menghasilkan kode yang benar atau rusak.
  • Jawaban lain berdasarkan np.arangesudah usang.

Jika ragu, coba empat test case di atas.

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

Harap perhatikan huruf Range pertama adalah modal. Metode penamaan ini tidak dianjurkan untuk fungsi dalam Python. Anda dapat mengubah Rentang ke sesuatu seperti drange atau frange jika Anda mau. Fungsi "Rentang" berperilaku seperti yang Anda inginkan. Anda dapat memeriksa manualnya di sini [ http://reference.wolfram.com/language/ref/Range.html ].

Hua Congyi
sumber
0

Saya pikir ada jawaban yang sangat sederhana yang benar-benar mengemulasi semua fitur range tetapi untuk float dan integer. Dalam solusi ini, Anda hanya mengira bahwa perkiraan Anda secara default adalah 1e-7 (atau yang Anda pilih) dan Anda dapat mengubahnya ketika Anda memanggil fungsi.

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

Tentu saja akan ada beberapa kesalahan pembulatan, jadi ini tidak sempurna, tetapi ini adalah apa yang saya gunakan secara umum untuk aplikasi, yang tidak memerlukan presisi tinggi. Jika Anda ingin menjadikan ini lebih akurat, Anda bisa menambahkan argumen tambahan untuk menentukan cara menangani kesalahan pembulatan. Mungkin lewat fungsi pembulatan mungkin membuat ini dapat diperluas dan memungkinkan programmer untuk menentukan cara menangani kesalahan pembulatan.

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

Jika saya menulis:

arange(0, 1, 0.1)

Ini akan menampilkan:

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

Apakah ada rentang () yang setara untuk mengapung di Python? TIDAK Gunakan ini:

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
sumber
3
Solusi yang sangat buruk, coba f_range(0.01,0.02,0.001)... Untuk sebagian besar tujuan praktis, arangedari Numpy adalah solusi yang sederhana, aman dan cepat.
Bart
Kamu benar. Dengan numpy 1,8 lebih cepat dari kode saya.
Grigor Kolev
Kamu benar. Dengan numpy 1,8 lebih cepat dari kode saya. Tetapi sistem tempat saya bekerja benar-benar tertutup. Hanya Python dan pyserial tidak lebih.
Grigor Kolev
-2

Ada beberapa jawaban di sini yang tidak menangani kasus tepi sederhana seperti langkah negatif, mulai salah, berhenti dll. Berikut adalah versi yang menangani banyak kasus ini dengan benar memberikan perilaku yang sama seperti asli 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  

Perhatikan bahwa ini akan membuat kesalahan langkah = 0 seperti yang asli range . Satu perbedaan adalah bahwa rentang asli mengembalikan objek yang dapat diindeks dan dapat dibalik sementara di atas tidak.

Anda dapat bermain dengan kode ini dan menguji kasus di sini.

Shital Shah
sumber