ช่วง () สำหรับลอย


140

มีสิ่งที่range()เทียบเท่ากับการลอยใน 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

1
นั่นไม่ใช่เศษส่วน แต่ก็ลอย และทุ่นลอย ... ก็น่าจะให้ผลลัพธ์ที่แตกต่างจากที่คุณคาดไว้

6
วิธีแก้ปัญหาอย่างรวดเร็วคือการใช้จำนวนเต็มเป็นทศนิยมเช่น: range(5, 50, 5)แล้วก็หารทุกตัวเลขด้วย 10
NullUserException

@delnan - อัปเดต ฉันยินดีที่จะยอมรับความไม่ถูกต้องไม่กี่นาทีเพื่อความสะดวกในการมีช่วงโฟลต
Jonathan

2
อาจเป็นไปได้ซ้ำกับช่วงค่า Python ทศนิยม () ค่า
Jonathan

@NullUserException - นี้เป็นเพียงตัวอย่าง - รหัสที่แท้จริงคือของพาราแน่นอน :)
โจนาธาน

คำตอบ:


97

ฉันไม่รู้จักฟังก์ชันในตัว แต่การเขียนแบบนี้ไม่ควรซับซ้อนเกินไป

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

จากความคิดเห็นที่กล่าวถึงสิ่งนี้อาจให้ผลลัพธ์ที่ไม่คาดคิดเช่น:

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

เพื่อให้ได้ผลลัพธ์ที่คาดหวังคุณสามารถใช้หนึ่งในคำตอบอื่น ๆ ในคำถามนี้หรือตามที่ @Tadhg พูดถึงคุณสามารถใช้decimal.Decimalเป็นjumpอาร์กิวเมนต์ ตรวจสอบให้แน่ใจว่าได้เริ่มต้นด้วยสตริงแทนการลอย

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

หรือแม้กระทั่ง:

import decimal

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

แล้ว:

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

34
คำขวัญของงูหลามเป็นจริงควรจะมี one-- และโดยเฉพาะอย่างยิ่งทางเดียวเท่านั้นที่ --obvious ที่จะทำมัน แต่อย่างไรก็ตามไพ ธ อนก็ยังดีอยู่ :)
Jonathan

3
>>> print list(frange(0,100,0.1))[-1]==100.0จะเป็นFalse
Volodimir Kopey

frangeสามารถทำงานได้โดยไม่คาดคิด เนื่องจากการสาปแช่งของลอยเลขคณิตจุดเช่นfrange(0.0, 1.0, 0.1)อัตราผลตอบแทน 11 0.9999999999999999ค่าที่ค่าสุดท้ายคือ การปรับปรุงการปฏิบัติจะwhile x + sys.float_info.epsilon < y:ถึงแม้ว่าสิ่งนี้อาจล้มเหลวด้วยจำนวนมาก
Akseli Palén

10
-1 กรุณาอย่าใช้รหัสนี้อย่างน้อยไม่ได้อยู่ในซอฟต์แวร์ที่อาจส่งผลกระทบต่อชีวิตของฉัน ไม่มีวิธีที่จะทำให้มันทำงานได้อย่างน่าเชื่อถือ อย่าใช้คำตอบของ Akseli Palénเช่นกัน ใช้คำตอบของ Xaerxess หรือ wim (ยกเว้นส่วนที่ไม่สนใจเกี่ยวกับ arange)
benrg

3
มันใช้งานได้ดีถ้าคุณใช้decimal.Decimalเป็นขั้นตอนแทนการลอย
Tadhg McDonald-Jensen

112

คุณสามารถใช้:

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

หรือใช้แลมบ์ดา / แผนที่:

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

1
และอาร์เรย์ (ช่วง (5,50,15)) / 10.0 เป็นอาร์เรย์ numpy มีตัวดำเนินการสำหรับการจัดการการหารการคูณและอื่น ๆ
edvaldig

2
@ edvaldig: ถูกต้องฉันไม่รู้เกี่ยวกับสิ่งนี้ ... อย่างไรก็ตามฉันคิดว่าarange(0.5, 5, 1.5)IMO สามารถอ่านได้มากขึ้น
Xaerxess

2
ฉันชอบคำตอบนี้มากกว่าคำตอบที่ได้รับการยอมรับเพราะสองคำตอบแรกที่นำเสนอขึ้นอยู่กับการวนซ้ำของจำนวนเต็มและได้มาซึ่งการลอยสุดท้ายจากจำนวนเต็ม นี่มันแข็งแกร่งกว่านี้ หากคุณทำด้วยการลอยโดยตรงคุณมีความเสี่ยงที่จะเกิดข้อผิดพลาดครั้งเดียวอันเนื่องมาจากการลอยตัวภายใน ตัวอย่างเช่นหากคุณลองlist(frange(0, 1, 0.5))ใช้งานได้ผลและ 1 ถูกแยกออก แต่ถ้าคุณลองlist(frange(0, 1, 0.1))ค่าสุดท้ายที่คุณได้รับจะใกล้เคียงกับ 1.0 ซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ วิธีแก้ไขปัญหาที่นำเสนอที่นี่ไม่มีปัญหานี้
blubberdiblub

3
อย่าใช้ numpy.arange (เอกสารแนะนำตัวเองของ numpy แนะนำ) ใช้ numpy.linspace ตามที่แนะนำโดย wim หรือหนึ่งในคำแนะนำอื่น ๆ ในคำตอบนี้
benrg

79

ฉันเคยใช้numpy.arangeแต่มีภาวะแทรกซ้อนบางอย่างที่ควบคุมจำนวนองค์ประกอบที่ส่งคืนเนื่องจากข้อผิดพลาดของจุดลอยตัว ดังนั้นตอนนี้ฉันใช้linspaceเช่น:

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

ยังคงมีข้อผิดพลาดของจุดแบบลอยตัวโดยไม่มีการใช้decimalเช่น:np.linspace(-.1,10,num=5050)[0]
ทีเอ็นที

2
@TNT ไม่นั่นไม่ใช่ข้อผิดพลาด คุณจะพบว่าnp.linspace(-.1,10,num=5050)[0] == -.1เป็นจริง มันเป็นเพียงrepr(np.float64('-0.1'))ตัวเลขที่แสดงจำนวนมากขึ้น
Wim

1
ในขณะที่ตัวอย่างนั้นแสดงให้เห็นว่าไม่มีข้อผิดพลาดในการปัดเศษส่วนเกินมีกรณีความล้มเหลว ตัวอย่างเช่นprint(numpy.linspace(0, 3, 148)[49])พิมพ์เมื่อผลที่เหมาะจะเป็น0.9999999999999999 ทำงานได้ดีกว่ามาก แต่ไม่รับประกันว่าจะเกิดข้อผิดพลาดในการปัดเศษขั้นต่ำสุดที่เป็นไปได้ 1.0linspacearange
user2357112 รองรับ Monica

มันจะรับประกันว่าจะดำเนินการจัดการอุปกรณ์ปลายทางที่ถูกต้องและมักจะผลิตว่าจำนวนการร้องขอขององค์ประกอบ
user2357112 รองรับ Monica

40

Pylab มีfrange(เสื้อคลุม, จริง, เพื่อ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 เลิกใช้แล้วตั้งแต่ matplotlib เวอร์ชั่น 2.2 ควรใช้ numpy.arange
kuzavas

13

ประเมินอย่างกระตือรือร้น (2.x range):

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

ประเมินแบบ Lazily (2.x xrange, 3.x range):

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

อีกวิธีหนึ่งคือ:

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; แต่ทำไมไม่(x * .5 for x in range(10))เป็นนิพจน์เครื่องกำเนิดไฟฟ้าสำหรับการประเมินผลที่ขี้เกียจ?
Tim Pietzcker

2
เพราะมันง่ายเกินไปฉันเดา? :)
Karl Knechtel

11

ใช้itertools: ประเมินช่วงจุดลอยตัวอย่างเฉื่อยชา:

>>> 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 itertools.takewhileสำหรับใช้ อย่างไรก็ตามได้itertools.count(start, step)รับความทุกข์จากข้อผิดพลาดจุดลอย (ประเมินtakewhile(lambda x: x < 100, count(0, 0.1))ตัวอย่าง) ฉันจะเขียนtakewhile(lambda x: x < stop, (start + i * step for i in count()))แทน
musiphil

6

ฉันช่วยเพิ่มฟังก์ชั่นnumeric_rangeกับแพคเกจมากขึ้น itertools

more_itertools.numeric_range(start, stop, step) ทำหน้าที่เหมือนช่วงฟังก์ชั่นในตัว แต่สามารถรองรับชนิดทศนิยมทศนิยมและเศษส่วนได้

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

4

ไม่มีฟังก์ชั่นในตัว แต่คุณสามารถใช้รหัสต่อไปนี้ (รหัส Python 3) เพื่อทำงานอย่างปลอดภัยตามที่ 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))

คุณสามารถตรวจสอบทั้งหมดได้ด้วยการยืนยันสองสามข้อ:

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

รหัสที่มีอยู่ในGitHub


4

เหตุใดจึงไม่มีการดำเนินการช่วงทศนิยมในไลบรารีมาตรฐาน

range()ในฐานะที่ทำให้เห็นได้ชัดโดยข้อความทั้งหมดที่นี่ไม่มีรุ่นจุดลอยของ ที่กล่าวว่าการละเว้นนั้นเหมาะสมถ้าเราพิจารณาว่าrange()ฟังก์ชั่นมักใช้เป็นดัชนี (และแน่นอนนั่นหมายถึงตัวสร้างaccessor ) ดังนั้นเมื่อเราโทรrange(0,40)เราจะบอกว่าเราต้องการค่า 40 ค่าเริ่มต้นที่ 0 ถึง 40 แต่ไม่รวมค่า 40

เมื่อเราพิจารณาว่าการสร้างดัชนีนั้นมีจำนวนมากเกี่ยวกับดัชนีเท่าที่เป็นค่าของพวกเขาการใช้การดำเนินการลอยตัวของrange()ในไลบรารีมาตรฐานทำให้รู้สึกน้อยลง ตัวอย่างเช่นถ้าเราเรียกฟังก์ชันfrange(0, 10, 0.25)นี้เราคาดว่าจะรวมทั้ง 0 และ 10 ไว้ด้วย แต่นั่นจะทำให้ได้เวกเตอร์ที่มี 41 ค่า

ดังนั้นfrange()ฟังก์ชั่นที่ขึ้นอยู่กับการใช้งานมักจะแสดงพฤติกรรมที่เข้าใจง่าย มันมีค่ามากเกินไปตามการรับรู้จากมุมมองของการจัดทำดัชนีหรือไม่รวมจำนวนที่ควรส่งคืนจากมุมมองทางคณิตศาสตร์อย่างสมเหตุสมผล

กรณีใช้คณิตศาสตร์

ด้วยที่กล่าวว่าตามที่กล่าวไว้numpy.linspace()ดำเนินการสร้างด้วยมุมมองทางคณิตศาสตร์อย่าง:

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

กรณีการใช้การจัดทำดัชนี

และสำหรับมุมมองการจัดทำดัชนีฉันได้เขียนแนวทางที่แตกต่างออกไปเล็กน้อยด้วยเวทมนตร์สตริงที่ซับซ้อนซึ่งทำให้เราสามารถระบุจำนวนทศนิยมได้

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

ในทำนองเดียวกันเรายังสามารถใช้roundฟังก์ชันในตัวและระบุจำนวนทศนิยม:

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

การเปรียบเทียบอย่างรวดเร็ว & ประสิทธิภาพ

แน่นอนจากการอภิปรายข้างต้นฟังก์ชั่นเหล่านี้มีกรณีการใช้งานที่ จำกัด อย่างไรก็ตามนี่เป็นการเปรียบเทียบด่วน:

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)

ผลลัพธ์เหมือนกันสำหรับแต่ละ:

-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

และกำหนดเวลา:

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

ดูเหมือนว่าวิธีการจัดรูปแบบสตริงชนะโดยทรงผมในระบบของฉัน

ข้อ จำกัด

และสุดท้ายเป็นการสาธิตจุดจากการสนทนาข้างบนและข้อ จำกัด สุดท้าย:

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

นอกจากนี้เมื่อskipพารามิเตอร์ไม่สามารถหารด้วยstopค่าอาจมีช่องว่างที่หาวได้เนื่องจากปัญหาหลัง:

# 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

มีวิธีการแก้ไขปัญหานี้ แต่ในตอนท้ายของวันวิธีที่ดีที่สุดน่าจะเป็นการใช้ Numpy


นี่เป็นข้อโต้แย้งที่ค่อนข้างบิดเบี้ยว range () ควรดูที่ตัวสร้างการวนซ้ำและใช้ในการวนซ้ำหรือทำดัชนีสิ่งที่ควรปล่อยให้แก่ผู้โทร ผู้คนใช้การลอยในวงวนพันปีและการอ้างเหตุผลข้างต้นเป็นเรื่องไร้สาระ ผู้คนในคณะกรรมการ Python ต่างพากันตะลุมบอนกันครั้งใหญ่และการโต้เถียงที่ดีอาจจะถูกทำให้หมดไปโดยเหตุผลที่บิดเบี้ยวเช่นนี้ มันธรรมดาและเรียบง่าย ขณะนี้มีการตัดสินใจมากเกินไปอย่างที่กล่าวไว้ข้างต้นในภาษาไพ ธ อน
Shital Shah

3

แก้ปัญหาได้โดยไม่ต้องพึ่งพา numpy ฯลฯถูกจัดให้โดย kichik แต่เนื่องจากเลขคณิตจุดลอยก็มักจะทำงานโดยไม่คาดคิด ตามที่ระบุไว้โดยฉันและblubberdiblubองค์ประกอบเพิ่มเติมแอบเข้าไปในผลลัพธ์ได้อย่างง่ายดาย ตัวอย่างเช่นnaive_frange(0.0, 1.0, 0.1)จะให้ผล0.999...เป็นค่าสุดท้ายดังนั้นจึงให้ผลรวม 11 ค่า

รุ่นที่แข็งแกร่งมีให้ที่นี่:

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

เนื่องจากการคูณข้อผิดพลาดในการปัดเศษจะไม่สะสม การใช้epsilonการดูแลข้อผิดพลาดในการปัดเศษที่เป็นไปได้ของการคูณแม้ว่าปัญหาของหลักสูตรอาจเพิ่มขึ้นในปลายที่เล็กมากและมีขนาดใหญ่มาก ตอนนี้ตามที่คาดไว้:

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

และด้วยจำนวนที่ค่อนข้างใหญ่กว่า:

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

รหัสยังมีอยู่เป็นGitHub สรุปสาระสำคัญ


สิ่งนี้ล้มเหลวด้วย frange (2.0, 17.0 / 6.0, 1.0 / 6.0) ไม่มีทางที่จะทำให้แข็งแกร่งได้
benrg

@benrg ขอบคุณที่ชี้นำสิ่งนี้! มันทำให้ฉันตระหนักว่าเอปไซลอนควรขึ้นอยู่กับการกระโดดดังนั้นฉันจึงตรวจสอบอัลกอริทึมและซ่อมแซมปัญหา เวอร์ชั่นใหม่นี้แข็งแกร่งกว่านี้ใช่ไหม?
Akseli Palén

2

เวอร์ชันที่ไม่มีไลบรารีที่ง่ายกว่า

อ๊ะเฮ้ - ฉันจะโยนเวอร์ชั่นไลบรารี่แบบง่ายๆ อย่าลังเลที่จะปรับปรุง [*]:

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

แนวคิดหลักคือnstepsจำนวนขั้นตอนในการให้คุณตั้งแต่ต้นจนหยุดและrange(nsteps)ปล่อยจำนวนเต็มเสมอดังนั้นจึงไม่มีความสูญเสียความแม่นยำ ขั้นตอนสุดท้ายคือการทำแผนที่ [0..nsteps] เป็นเส้นตรงไปยัง [start ..stop]

แก้ไข

หากเช่นเดียวกับalancalvittiคุณต้องการให้ซีรี่ส์มีตัวแทนที่มีเหตุผลอย่างแน่นอนคุณสามารถใช้เศษส่วนได้เสมอ:

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

[*] โดยเฉพาะfrange()คืนค่ารายการไม่ใช่ตัวสร้าง แต่มันก็เพียงพอสำหรับความต้องการของฉัน


หากคุณต้องการรวมค่าหยุดในเอาท์พุทโดยเพิ่ม stop + jump วิธีนี้จะเปลี่ยนกลับไปเป็นผลลัพธ์ที่ไร้เดียงสาโดยมีจุดลอยตัวที่ไม่ดีอยู่ตรงกลางลองfrange(0,1.1,0.1)อีกครั้งกับตัวเลือกอื่น ๆ อีกมากมายfrange(0,1.05,0.1)
alancalvitti

@alancalvitti: คำจำกัดความของคุณเกี่ยวกับจุดลอยตัว "เลวร้าย" คืออะไร? ใช่ผลลัพธ์อาจไม่พิมพ์อย่างสวยงาม แต่ frange () ให้ชุดของค่าที่เว้นระยะเท่ากันมากที่สุดภายในขอบเขตของการเป็นตัวแทนจุดลอยตัว คุณจะปรับปรุงมันอย่างไร
fearless_fool

จุดที่ดีฉันคุ้นเคยกับภาษาระดับสูงที่คุณจะใช้ตัวเลขที่มีเหตุผลสำหรับงานดังกล่าวซึ่ง Py รู้สึกเหมือนการชุมนุม
alancalvitti

สภา? Hrrumph! ;) แน่นอน Python สามารถให้การแทนที่แน่นอนด้วย Fractions: docs.python.org/3/library/fractions.html
fearless_fool

ถูกต้องขอบคุณ แต่เช่นภาษาที่ฉันชอบแปลงประเภทเหล่านี้โดยอัตโนมัติดังนั้น 1/2 เป็นเหตุผลขณะที่ 1 / 2.0 เป็นแบบลอยไม่มีความจำเป็นที่จะต้องประกาศให้เป็นเช่นนั้น - ปล่อยการประกาศไปยัง Java ซึ่งเป็นมากกว่า ต่ำกว่า / ประกอบกว่า Py
alancalvitti

2

สิ่งนี้สามารถทำได้ด้วย numpy.arange (เริ่มหยุดทำขั้นตอน)

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]

หมายเหตุ 1: จากการอภิปรายในส่วนความคิดเห็นที่นี่ "ไม่เคยใช้numpy.arange()(เอกสารคู่มือ numpy แนะนำให้ต่อต้านมัน) ใช้ numpy.linspace ตามที่แนะนำโดย wim หรือหนึ่งในคำแนะนำอื่น ๆ ในคำตอบนี้"

หมายเหตุ 2: ฉันได้อ่านการอภิปรายในความคิดเห็นไม่กี่ที่นี่ แต่หลังจากกลับมาที่คำถามนี้เป็นครั้งที่สามตอนนี้ฉันรู้สึกว่าข้อมูลนี้ควรอยู่ในตำแหน่งที่สามารถอ่านได้มากขึ้น


2

ตามที่kichikเขียนสิ่งนี้ไม่ควรซับซ้อนเกินไป อย่างไรก็ตามรหัสนี้:

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

ไม่เหมาะสมเนื่องจากผลของข้อผิดพลาดสะสมเมื่อทำงานกับการลอย นั่นคือเหตุผลที่คุณได้รับสิ่งที่ชอบ:

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

ในขณะที่พฤติกรรมที่คาดหวังจะเป็น:

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

โซลูชันที่ 1

ข้อผิดพลาดสะสมสามารถลดลงได้ง่ายๆโดยใช้ตัวแปรดัชนี นี่คือตัวอย่าง:

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

ตัวอย่างนี้ทำงานได้ตามที่คาดไว้

โซลูชันที่ 2

ไม่มีฟังก์ชั่นที่ซ้อนกัน เพียงชั่วขณะและตัวแปรตัวนับ:

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

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

ฟังก์ชั่นนี้จะทำงานได้ดีเช่นกันยกเว้นกรณีที่เมื่อคุณต้องการช่วงย้อนกลับ เช่น:

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

โซลูชันที่ 1 ในกรณีนี้จะทำงานตามที่คาดไว้ ในการทำให้ฟังก์ชั่นนี้ใช้งานได้ในสถานการณ์เช่นนี้คุณต้องทำการแฮ็กโดยคล้ายกับสิ่งต่อไปนี้:

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

ด้วยแฮ็คนี้คุณสามารถใช้ฟังก์ชั่นเหล่านี้ด้วยขั้นตอนเชิงลบ:

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

โซลูชันที่ 3

คุณสามารถก้าวต่อไปได้ด้วยไลบรารีมาตรฐานธรรมดาและเขียนฟังก์ชันช่วงสำหรับประเภทตัวเลขส่วนใหญ่:

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

เครื่องกำเนิดไฟฟ้านี้ดัดแปลงมาจากหนังสือ Fluent Python (บทที่ 14 Iterables, Iterators และ generators) มันจะไม่ทำงานกับช่วงที่ลดลง คุณต้องทำการแฮ็กเช่นเดียวกับในโซลูชันก่อนหน้า

คุณสามารถใช้ตัวสร้างนี้ได้เช่น:

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

และแน่นอนคุณสามารถใช้กับลอยและintเช่นกัน

ระวัง

หากคุณต้องการใช้ฟังก์ชั่นเหล่านี้พร้อมกับขั้นตอนเชิงลบคุณควรเพิ่มการตรวจสอบสำหรับเครื่องหมายขั้นตอนเช่น:

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

ตัวเลือกที่ดีที่สุดที่นี่คือการเพิ่มStopIterationถ้าคุณต้องการเลียนแบบrangeฟังก์ชั่นของตัวเอง

เลียนแบบช่วง

หากคุณต้องการเลียนแบบrangeฟังก์ชั่นอินเทอร์เฟซคุณสามารถให้การตรวจสอบข้อโต้แย้งบางอย่าง:

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

ฉันคิดว่าคุณมีจุด คุณสามารถไปกับฟังก์ชั่นใด ๆ เหล่านี้ (ยกเว้นฟังก์ชั่นแรก) และสิ่งที่คุณต้องการสำหรับพวกมันคือไลบรารี่มาตรฐานของไพ ธ อน


1

ฉันเขียนฟังก์ชันที่คืนค่า tuple ของช่วงของจำนวนจุดลอยตัวที่มีความแม่นยำสองเท่าโดยไม่มีทศนิยมใด ๆ มันเป็นเพียงเรื่องของการแยกค่าช่วงเช่นสตริงและแยกส่วนเกิน ฉันใช้เพื่อแสดงช่วงเพื่อเลือกจากภายใน UI ฉันหวังว่าคนอื่นจะพบว่ามีประโยชน์

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

การใช้

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

เพื่อปัดเศษแต่ละขั้นตอนให้เป็นทศนิยม 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

ทำไมต้องเลือกคำตอบนี้

  • คำตอบอื่น ๆ อีกมากมายจะแขวนเมื่อขอให้นับถอยหลัง
  • คำตอบอื่น ๆ อีกมากมายจะให้ผลลัพธ์ที่ไม่ถูกต้อง
  • คำตอบอื่น ๆ ที่อิงจากnp.linspaceการอ่านแล้วพลาดซึ่งอาจเป็นไปได้หรืออาจไม่ได้ผลเนื่องจากความยากลำบากในการเลือกจำนวนแผนกที่ถูกต้อง np.linspaceการต่อสู้กับทศนิยมที่เพิ่มขึ้น 0.1 จริง ๆ และลำดับของแผนกในสูตรเพื่อแปลงการเพิ่มขึ้นเป็นจำนวนของการแยกอาจส่งผลให้รหัสถูกต้องหรือแตก
  • คำตอบอื่น ๆ ที่อิงตามnp.arangeจะถูกคัดค้าน

หากมีข้อสงสัยลองทดสอบสี่กรณีข้างต้น


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)

โปรดทราบว่าอักษรตัวแรกของ Range คือตัวพิมพ์ใหญ่ วิธีการตั้งชื่อนี้ไม่ได้รับการสนับสนุนสำหรับฟังก์ชั่นใน Python คุณสามารถเปลี่ยน Range เป็นบางอย่างเช่น drange หรือ frange ถ้าคุณต้องการ ฟังก์ชัน "ช่วง" ทำงานตามที่คุณต้องการ คุณสามารถตรวจสอบคู่มือได้ที่นี่ [ http://reference.wolfram.com/language/ref/Range.html ]


0

ฉันคิดว่ามีคำตอบง่าย ๆ ที่เลียนแบบคุณลักษณะทั้งหมดของช่วง แต่สำหรับทั้งทศนิยมและจำนวนเต็ม ในโซลูชันนี้คุณเพียงสมมติว่าค่าประมาณของคุณคือ 1e-7 (หรือที่คุณเลือก) และคุณสามารถเปลี่ยนได้เมื่อคุณเรียกใช้ฟังก์ชัน

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

แน่นอนว่าจะมีข้อผิดพลาดในการปัดเศษดังนั้นจึงไม่สมบูรณ์ แต่นี่คือสิ่งที่ฉันใช้โดยทั่วไปสำหรับแอปพลิเคชันซึ่งไม่ต้องการความแม่นยำสูง หากคุณต้องการทำให้แม่นยำยิ่งขึ้นคุณสามารถเพิ่มอาร์กิวเมนต์พิเศษเพื่อระบุวิธีจัดการกับข้อผิดพลาดในการปัดเศษ บางทีการส่งผ่านฟังก์ชันการปัดเศษอาจทำให้สิ่งนี้สามารถขยายได้และอนุญาตให้โปรแกรมเมอร์ระบุวิธีจัดการข้อผิดพลาดในการปัดเศษ

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

ถ้าฉันเขียน:

arange(0, 1, 0.1)

มันจะออก:

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

-1

มีช่วง () เทียบเท่ากับลอยใน Python หรือไม่ ห้ามใช้สิ่งนี้:

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
วิธีแก้ปัญหาที่แย่มากลองf_range(0.01,0.02,0.001)... สำหรับจุดประสงค์การใช้งานจริงarangeจาก Numpy เป็นวิธีแก้ปัญหาที่ง่ายปลอดภัยและรวดเร็ว
บาร์ต

คุณพูดถูก ด้วย numpy คือ 1.8 เร็วกว่ารหัสของฉัน
Grigor Kolev

คุณพูดถูก ด้วย numpy คือ 1.8 เร็วกว่ารหัสของฉัน แต่ระบบที่ฉันทำงานปิดสนิท เฉพาะ Python และ pyserial เท่านั้น
Grigor Kolev

-2

มีคำตอบมากมายที่นี่ที่ไม่จัดการกับกรณีแบบง่าย ๆ เช่นขั้นตอนเชิงลบการเริ่มผิดพลาดหยุด ฯลฯ นี่คือเวอร์ชันที่จัดการกรณีเหล่านี้จำนวนมากที่ให้พฤติกรรมเช่นเดียวกับเนทิฟอย่างถูกต้อง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  

โปรดทราบว่านี่จะทำให้เกิดข้อผิดพลาดในขั้นตอน = 0 เช่นเดียวกับ native rangeเช่นเดียวกับชาวพื้นเมือง ข้อแตกต่างประการหนึ่งคือช่วงเนทีฟส่งคืนออบเจกต์ที่สามารถทำดัชนีได้

คุณสามารถเล่นกับรหัสนี้และกรณีทดสอบที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.