phạm vi () cho phao


140

range()tương đương với số float trong Python không?

>>> 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
Đó không phải là phân số mà là nổi. Và phao là ... tốt, có khả năng cho kết quả khác nhau hơn bạn mong đợi.

6
Một cách giải quyết nhanh sẽ là coi số nguyên là số thập phân, ví dụ : range(5, 50, 5), và sau đó chỉ cần chia mỗi số cho 10.
NullUserException

@del Nam - cập nhật. Tôi sẵn sàng chấp nhận sự thiếu chính xác của phút để thuận tiện cho việc có phạm vi nổi
Jonathan


@NullUserException - đây chỉ là một ví dụ - mã thực sự dĩ nhiên là tham số :)
Jonathan

Câu trả lời:


97

Tôi không biết một hàm dựng sẵn, nhưng viết một hàm như thế này không quá phức tạp.

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

Như các ý kiến ​​đề cập, điều này có thể tạo ra kết quả không thể đoán trước như:

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

Để có kết quả như mong đợi, bạn có thể sử dụng một trong những câu trả lời khác trong câu hỏi này hoặc như @Tadhg đã đề cập, bạn có thể sử dụng decimal.Decimallàm jumpđối số. Hãy chắc chắn để khởi tạo nó bằng một chuỗi chứ không phải là float.

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

Hoặc thậm chí:

import decimal

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

Và sau đó:

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

34
Phương châm của Python thực sự là nên có một - và tốt nhất là chỉ có một - cách rõ ràng để làm điều đó . Nhưng dù sao thì Python vẫn tuyệt vời :)
Jonathan

3
>>> print list(frange(0,100,0.1))[-1]==100.0sẽ làFalse
Volodimir Kopey

frangeCó thể làm việc bất ngờ. Do lời nguyền của mỹ phẩm điểm nổi , ví dụ frange(0.0, 1.0, 0.1)mang lại 11 giá trị, trong đó giá trị cuối cùng là 0.9999999999999999. Một cải tiến thực tế sẽ là while x + sys.float_info.epsilon < y:mặc dù điều này có thể thất bại với số lượng lớn .
Akseli Palén 18/03/2016

10
-1 Xin vui lòng không sử dụng mã này , ít nhất là không phải trong phần mềm có thể ảnh hưởng đến cuộc sống của tôi. Không có cách nào để làm cho nó hoạt động đáng tin cậy. Đừng sử dụng câu trả lời của Akseli Palén. Sử dụng câu trả lời của Xaerxess hoặc wim (ngoại trừ bỏ qua phần về arange).
benrg

3
Điều này hoạt động rất tốt nếu bạn sử dụngdecimal.Decimal như bước thay vì phao.
Tadhg McDonald-Jensen

112

Bạn có thể sử dụng:

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

hoặc sử dụng lambda / bản đồ:

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

1
Và mảng (phạm vi (5,50,15)) / 10,0 vì mảng numpy có toán tử để xử lý phân chia, nhân và vv
edvaldig

2
@edvaldig: bạn nói đúng, tôi không biết về điều này ... Tuy nhiên tôi nghĩ arange(0.5, 5, 1.5)IMO dễ đọc hơn.
Xaerxess

2
Tôi thích câu trả lời này hơn câu trả lời được chấp nhận, bởi vì hai giải pháp đầu tiên được trình bày dựa trên việc lặp lại các số nguyên và lấy ra các số float cuối cùng từ các số nguyên. Điều này là mạnh mẽ hơn. Nếu bạn làm điều đó trực tiếp với phao, bạn có nguy cơ gặp phải các lỗi một lần lạ do cách thức nổi được thể hiện trong nội bộ. Chẳng hạn, nếu bạn thử list(frange(0, 1, 0.5)), nó hoạt động tốt và 1 bị loại trừ, nhưng nếu bạn thử list(frange(0, 1, 0.1)), giá trị cuối cùng bạn nhận được gần bằng 1.0, có lẽ không phải là điều bạn muốn. Các giải pháp được trình bày ở đây không có vấn đề này.
blubberdiblub

3
Không bao giờ sử dụng numpy.arange (tài liệu numpy tự đề nghị chống lại nó). Sử dụng numpy.linspace theo khuyến nghị của wim hoặc một trong những gợi ý khác trong câu trả lời này.
benrg

79

Tôi đã từng sử dụng numpy.arangenhưng có một số biến chứng kiểm soát số lượng phần tử mà nó trả về, do lỗi dấu phẩy động. Vì vậy, bây giờ tôi sử dụng linspace, ví dụ:

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

Tuy nhiên, vẫn còn lỗi dấu phẩy động, không sử dụng decimal, ví dụ:np.linspace(-.1,10,num=5050)[0]
TNT

2
@TNT Không, đó không phải là một lỗi. Bạn sẽ tìm thấy np.linspace(-.1,10,num=5050)[0] == -.1là đúng. Chỉ là nó repr(np.float64('-0.1'))hiển thị nhiều chữ số hơn.
Wim

1
Trong khi ví dụ cụ thể cho thấy không có lỗi làm tròn quá mức, có những trường hợp thất bại. Ví dụ, print(numpy.linspace(0, 3, 148)[49])in 0.9999999999999999khi kết quả lý tưởng sẽ được 1.0. linspacethực hiện công việc tốt hơn nhiều arange, nhưng nó không được đảm bảo để tạo ra lỗi làm tròn tối thiểu có thể.
user2357112 hỗ trợ Monica

được đảm bảo để thực hiện xử lý điểm cuối chính xác và luôn tạo ra chính xác số lượng phần tử được yêu cầu.
user2357112 hỗ trợ Monica

40

Pylab có frange(một trình bao bọc, thực sự, cho 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 không được dùng nữa kể từ phiên bản matplotlib 2.2. numpy.arange nên được sử dụng.
kuzavas

13

Đánh giá háo hức (2.x range):

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

Đánh giá một cách nhanh chóng (2.x xrange, 3.x range):

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

Luân phiên:

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; nhưng tại sao không phải (x * .5 for x in range(10))là một biểu thức tạo cho đánh giá lười biếng?
Tim Pietzcker

2
Bởi vì điều đó sẽ quá dễ dàng, tôi đoán vậy? :)
Karl Knechtel

11

bằng cách sử dụng itertools: đánh giá lười biếng phạm vi điểm nổi:

>>> 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 để sử dụng itertools.takewhile. Tuy nhiên, itertools.count(start, step)bị lỗi tích lũy dấu phẩy động. (Đánh giá takewhile(lambda x: x < 100, count(0, 0.1))chẳng hạn.) Tôi sẽ viết takewhile(lambda x: x < stop, (start + i * step for i in count()))thay thế.
musiphil

6

Tôi đã giúp thêm hàm num_range vào gói thêm itertools .

more_itertools.numeric_range(start, stop, step) hoạt động như phạm vi chức năng được xây dựng nhưng có thể xử lý các kiểu float, Decimal và Fraction.

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

4

Không có chức năng tích hợp như vậy, nhưng bạn có thể sử dụng đoạn mã sau (mã Python 3) để thực hiện công việc an toàn như Python cho phép bạn làm.

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

Bạn có thể xác minh tất cả bằng cách chạy một vài xác nhận:

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

Mã có sẵn trên GitHub


4

Tại sao không có triển khai phạm vi dấu phẩy động trong Thư viện chuẩn?

Như được làm rõ bởi tất cả các bài viết ở đây, không có phiên bản dấu phẩy động của range(). Điều đó nói rằng, các thiếu sót có ý nghĩa nếu chúng ta xem xét rằng range()chức năng thường được sử dụng như một chỉ số (và dĩ nhiên, điều đó có nghĩa một accessor ) máy phát điện. Vì vậy, khi chúng tôi gọi range(0,40), chúng tôi có hiệu lực nói rằng chúng tôi muốn 40 giá trị bắt đầu từ 0, tối đa 40, nhưng không bao gồm 40 chính nó.

Khi chúng tôi xem việc tạo chỉ mục cũng nhiều về số lượng các chỉ số vì đó là giá trị của chúng, việc sử dụng triển khai float range()trong thư viện chuẩn sẽ ít có ý nghĩa hơn. Ví dụ, nếu chúng ta gọi hàm frange(0, 10, 0.25), chúng ta sẽ mong đợi cả 0 và 10 được bao gồm, nhưng điều đó sẽ mang lại một vectơ với 41 giá trị.

Do đó, một frange()chức năng tùy thuộc vào việc sử dụng nó sẽ luôn thể hiện hành vi trực quan chống lại; nó có quá nhiều giá trị theo cảm nhận từ góc độ lập chỉ mục hoặc không bao gồm một số có thể được trả về một cách hợp lý từ góc độ toán học.

Trường hợp sử dụng toán học

Như đã nói, như đã thảo luận, numpy.linspace()thực hiện thế hệ với quan điểm toán học độc đáo:

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

Ca sử dụng lập chỉ mục

Và đối với phối cảnh lập chỉ mục, tôi đã viết một cách tiếp cận hơi khác với một số phép thuật chuỗi phức tạp cho phép chúng tôi chỉ định số lượng vị trí thập phân.

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

Tương tự, chúng ta cũng có thể sử dụng hàm dựng sẵn roundvà chỉ định số thập phân:

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

So sánh nhanh & hiệu suất

Tất nhiên, với các thảo luận ở trên, các chức năng này có trường hợp sử dụng khá hạn chế. Tuy nhiên, đây là một so sánh nhanh:

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)

Các kết quả giống hệt nhau cho mỗi:

-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

Và một số thời gian:

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

Có vẻ như phương pháp định dạng chuỗi thắng bằng một sợi tóc trên hệ thống của tôi.

Những hạn chế

Và cuối cùng, một minh chứng về quan điểm từ cuộc thảo luận ở trên và một hạn chế cuối cùng:

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

Hơn nữa, khi skiptham số không chia hết cho stopgiá trị, có thể có một khoảng cách ngáp cho vấn đề sau:

# 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

Có nhiều cách để giải quyết vấn đề này, nhưng vào cuối ngày, cách tiếp cận tốt nhất có lẽ là chỉ sử dụng Numpy.


Đây là một đối số khá xoắn. phạm vi () đơn giản nên được xem xét trình tạo lặp và liệu nó được sử dụng trong vòng lặp hay để lập chỉ mục một cái gì đó nên để lại cho người gọi. Mọi người đã sử dụng phao trong vòng lặp cho milimet và các biện minh trên là vô nghĩa. Mọi người trong ủy ban Python đã gặp rắc rối ở đây và cuộc tranh luận tốt có lẽ đã bị nhấn chìm bởi một số biện minh xoắn như trên. Đó là đơn giản và đơn giản. Hiện tại có quá nhiều quyết định như trên được ghi nhận bằng ngôn ngữ Python.
Shital Shah

3

Một giải pháp không có phụ thuộc numpy vv được cung cấp bởi kichik nhưng do tính chất nổi của điểm nổi , nó thường hoạt động bất ngờ. Theo ghi nhận của tôiblubberdiblub , các yếu tố bổ sung dễ dàng lẻn vào kết quả. Ví dụ: naive_frange(0.0, 1.0, 0.1)sẽ mang lại 0.999...giá trị cuối cùng và do đó mang lại tổng cộng 11 giá trị.

Một phiên bản mạnh mẽ được cung cấp ở đây:

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

Bởi vì nhân, các lỗi làm tròn không tích lũy. Việc sử dụng epsilonchăm sóc các lỗi làm tròn có thể có của phép nhân, mặc dù các vấn đề tất nhiên có thể tăng lên ở các đầu rất nhỏ và rất lớn. Bây giờ, như mong đợi:

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

Và với số lượng lớn hơn một chút:

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

Mã này cũng có sẵn dưới dạng GitHub Gist .


Điều này không thành công với frange (2.0, 17.0 / 6.0, 1.0 / 6.0). Không có cách nào nó có thể được thực hiện mạnh mẽ.
benrg

@benrg Cảm ơn bạn đã chỉ ra điều này! Nó khiến tôi nhận ra rằng epsilon nên phụ thuộc vào bước nhảy, vì vậy tôi đã xem lại thuật toán và sửa chữa vấn đề. Phiên bản mới này mạnh mẽ hơn nhiều phải không?
Akseli Palén

2

Một phiên bản không có thư viện đơn giản hơn

Aw, heck - Tôi sẽ đưa vào một phiên bản không có thư viện đơn giản. Hãy cải thiện nó [*]:

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

Ý tưởng cốt lõi là nstepssố bước giúp bạn bắt đầu dừng lại và range(nsteps)luôn phát ra các số nguyên để không mất độ chính xác. Bước cuối cùng là ánh xạ [0..nsteps] tuyến tính vào [start..stop].

biên tập

Nếu, như alancalvitti, bạn muốn bộ truyện có biểu diễn hợp lý chính xác, bạn luôn có thể sử dụng Phân số :

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

[*] Đặc biệt, frange()trả về một danh sách, không phải là trình tạo. Nhưng nó đáp ứng nhu cầu của tôi.


Nếu bạn muốn bao gồm giá trị dừng trong đầu ra, bằng cách thêm stop + jump, phương thức này sẽ trở lại kết quả ngây thơ với các điểm nổi xấu ở giữa, thử frange(0,1.1,0.1)và thậm chí nhiều hơn với các lựa chọn nhưfrange(0,1.05,0.1)
alancalvitti

@alancalvitti: Định nghĩa của bạn về điểm nổi "xấu" là gì? Có, kết quả có thể không in độc đáo, nhưng frange () cung cấp tập hợp các giá trị cách đều nhau gần nhất trong giới hạn của biểu diễn dấu phẩy động. bạn sẽ cải thiện nó như thế nào?
sợ hãi_fool

điểm hay, tôi đã quá quen với ngôn ngữ cấp cao, nơi bạn sẽ vượt qua các con số hợp lý cho một nhiệm vụ như vậy, mà Py cảm thấy giống như lắp ráp.
alancalvitti

Hội,, tổ hợp? Hrrumph! ;) Dĩ nhiên Python có thể cung cấp đại diện chính xác với phân số: docs.python.org/3/library/fractions.html
fearless_fool

Đúng, cảm ơn, nhưng ví dụ, ngôn ngữ tôi thích tự động chuyển đổi các loại này, vì vậy 1/2 là hợp lý, trong khi 1 2.0 là trôi nổi, không cần phải khai báo chúng như vậy - hãy để lại các khai báo cho Java, thậm chí còn nhiều hơn thấp hơn / lắp ráp hơn Py.
alancalvitti

2

Điều này có thể được thực hiện với numpy.arange (bắt đầu, dừng lại, từng bước)

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]

Lưu ý 1: Từ các cuộc thảo luận trong phần bình luận ở đây, "không bao giờ sử dụng numpy.arange()(tài liệu numpy đề xuất chống lại nó). Sử dụng numpy.linspace theo khuyến nghị của wim hoặc một trong những gợi ý khác trong câu trả lời này"

Lưu ý 2: Tôi đã đọc các cuộc thảo luận trong một vài bình luận ở đây, nhưng sau khi trở lại câu hỏi này lần thứ ba, tôi cảm thấy thông tin này nên được đặt ở vị trí dễ đọc hơn.


2

Như kichik đã viết, điều này không nên quá phức tạp. Tuy nhiên mã này:

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

Không phù hợp vì ảnh hưởng tích lũy của lỗi khi làm việc với phao. Đó là lý do tại sao bạn nhận được một cái gì đó như:

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

Trong khi hành vi dự kiến ​​sẽ là:

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

Giải pháp 1

Lỗi tích lũy đơn giản có thể được giảm bằng cách sử dụng biến chỉ số. Đây là ví dụ:

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

Ví dụ này hoạt động như mong đợi.

Giải pháp 2

Không có chức năng lồng nhau. Chỉ một lúc và một biến đếm:

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

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

Chức năng này cũng sẽ hoạt động tốt, ngoại trừ các trường hợp khi bạn muốn phạm vi đảo ngược. Ví dụ:

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

Giải pháp 1 trong trường hợp này sẽ hoạt động như mong đợi. Để làm cho chức năng này hoạt động trong các tình huống như vậy, bạn phải áp dụng một hack, tương tự như sau:

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

Với bản hack này, bạn có thể sử dụng các chức năng này với các bước tiêu cực:

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

Giải pháp 3

Bạn có thể đi xa hơn với thư viện tiêu chuẩn đơn giản và soạn một hàm phạm vi cho hầu hết các loại số:

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

Trình tạo này được điều chỉnh từ sách Fluent Python (Chương 14. Iterables, Iterators và Generator). Nó sẽ không hoạt động với phạm vi giảm. Bạn phải áp dụng một hack, như trong giải pháp trước.

Bạn có thể sử dụng trình tạo này như sau, ví dụ:

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

Và tất nhiên bạn có thể sử dụng nó với floatint là tốt.

Hãy cẩn thận

Nếu bạn muốn sử dụng các chức năng này với các bước phủ định, bạn nên thêm một kiểm tra cho dấu hiệu bước, ví dụ:

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

Lựa chọn tốt nhất ở đây là nâng cao StopIteration , nếu bạn muốn bắt chướcrange chính chức năng đó.

Phạm vi bắt chước

Nếu bạn muốn bắt chước rangegiao diện chức năng, bạn có thể cung cấp một số kiểm tra đối số:

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

Tôi nghĩ rằng, bạn đã có điểm. Bạn có thể sử dụng bất kỳ chức năng nào trong số này (ngoại trừ chức năng đầu tiên) và tất cả những gì bạn cần cho chúng là thư viện chuẩn python.


1

tôi đã viết một hàm trả về một tuple của một dãy các số dấu phẩy động chính xác kép mà không có bất kỳ vị trí thập phân nào ngoài hàng trăm. nó chỉ đơn giản là vấn đề phân tích các giá trị phạm vi như chuỗi và tách phần thừa. Tôi sử dụng nó để hiển thị phạm vi để chọn trong giao diện người dùng. Tôi hy vọng người khác thấy nó hữu ích.

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

Sử dụng

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

Để làm tròn mỗi bước đến N vị trí thập phân

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]

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

Tại sao chọn câu trả lời này?

  • Nhiều câu trả lời khác sẽ treo khi được yêu cầu đếm ngược.
  • Nhiều câu trả lời khác sẽ cho kết quả làm tròn không chính xác.
  • Các câu trả lời khác dựa trên np.linspacehit-and-miss, chúng có thể hoặc không thể hoạt động do khó khăn trong việc chọn số lượng phân chia chính xác. np.linspacethực sự vật lộn với số gia thập phân là 0,1 và thứ tự các phép chia trong công thức để chuyển đổi số gia thành một số lần chia có thể dẫn đến mã chính xác hoặc bị hỏng.
  • Các câu trả lời khác dựa trên np.arangebị phản đối.

Nếu nghi ngờ, hãy thử bốn trường hợp thử nghiệm ở trên.


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)

Xin lưu ý chữ cái đầu tiên của Phạm vi là vốn. Phương pháp đặt tên này không được khuyến khích cho các hàm trong Python. Bạn có thể thay đổi Range thành một cái gì đó như drange hoặc frange nếu bạn muốn. Hàm "Phạm vi" hoạt động giống như bạn muốn. Bạn có thể kiểm tra hướng dẫn sử dụng tại đây [ http://reference.wolfram.com/lingu/ref/Range.html ].


0

Tôi nghĩ rằng có một câu trả lời rất đơn giản thực sự mô phỏng tất cả các tính năng của phạm vi nhưng cho cả số float và số nguyên. Trong giải pháp này, bạn chỉ cần giả sử rằng xấp xỉ của bạn theo mặc định là 1e-7 (hoặc cái bạn chọn) và bạn có thể thay đổi nó khi bạn gọi hàm.

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

Tất nhiên sẽ có một số lỗi làm tròn, vì vậy điều này không hoàn hảo, nhưng đây là những gì tôi thường sử dụng cho các ứng dụng, không đòi hỏi độ chính xác cao. Nếu bạn muốn làm cho điều này chính xác hơn, bạn có thể thêm một đối số bổ sung để chỉ định cách xử lý lỗi làm tròn. Có lẽ việc truyền một hàm làm tròn có thể làm cho điều này có thể mở rộng và cho phép lập trình viên chỉ định cách xử lý các lỗi làm tròn.

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

Nếu tôi viết:

arange(0, 1, 0.1)

Nó sẽ xuất ra:

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

-1

Có một phạm vi () tương đương với số float trong Python không? KHÔNG sử dụng cái này:

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
Giải pháp khá tệ, hãy thử f_range(0.01,0.02,0.001)... Đối với hầu hết các mục đích thực tế, arangetừ Numpy là một giải pháp đơn giản, an toàn và nhanh chóng.
Bart

Bạn đúng rồi. Với numpy là 1,8 nhanh hơn mã của tôi.
Grigor Kolev

Bạn đúng rồi. Với numpy là 1,8 nhanh hơn mã của tôi. Nhưng hệ thống nơi tôi làm việc hoàn toàn đóng cửa. Chỉ có Python và pyserial không còn nữa.
Grigor Kolev

-2

Có một số câu trả lời ở đây không xử lý các trường hợp cạnh đơn giản như bước âm, khởi động sai, dừng, v.v ... Đây là phiên bản xử lý nhiều trường hợp này có hành vi giống hệt như bản địa 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  

Lưu ý rằng điều này sẽ xảy ra lỗi bước = 0 giống như bản địa range . Một sự khác biệt là phạm vi gốc trả về đối tượng có thể lập chỉ mục và có thể đảo ngược trong khi ở trên không.

Bạn có thể chơi với mã này và các trường hợp thử nghiệm ở đây.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.