เหตุใดช่วง (เริ่มต้นสิ้นสุด) จึงไม่รวมจุดสิ้นสุด


305
>>> range(1,11)

ให้คุณ

[1,2,3,4,5,6,7,8,9,10]

ทำไมไม่ 1-11

พวกเขาตัดสินใจที่จะทำอย่างนั้นแบบสุ่มหรือมีคุณค่าที่ฉันไม่เห็นหรือเปล่า


11
อ่าน Dijkstra, ewd831
SilentGhost

11
โดยพื้นฐานแล้วคุณกำลังเลือกบั๊กแบบ off-by-one หนึ่งชุดสำหรับอีกชุดหนึ่ง ชุดหนึ่งมีแนวโน้มที่จะทำให้ลูปของคุณสิ้นสุดเร็วกว่าอีกชุดหนึ่งอาจทำให้เกิดข้อยกเว้น (หรือบัฟเฟอร์ล้นในภาษาอื่น) เมื่อคุณเขียนโค้ดจำนวนมากคุณจะเห็นว่าการเลือกพฤติกรรมrange()ทำให้รู้สึกบ่อยขึ้น
John La Rooy

32
เชื่อมโยงไปยัง Dijkstra, ewd831: cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF
unutbu

35
@unutbu บทความ Djikstra นั้นอ้างถึงในหัวข้อนี้ แต่ไม่ได้ให้คุณค่าอะไรเลยที่นี่ผู้คนใช้มันเพื่อดึงดูดความสนใจ เหตุผลหลอกที่เกี่ยวข้องเพียงอย่างเดียวที่เขาให้กับคำถามของ OP คือเขารู้สึกว่าการรวมขอบเขตบนกลายเป็น "ผิดธรรมชาติ" และ "น่าเกลียด" ในกรณีเฉพาะที่ลำดับว่างเปล่า - นั่นเป็นตำแหน่งส่วนตัวทั้งหมดและเป็นที่ถกเถียงได้ง่าย ดังนั้นมันจึงไม่ได้นำมาที่โต๊ะมากนัก "การทดลอง" กับ Mesa นั้นมีค่าไม่มากนักโดยไม่ทราบถึงข้อ จำกัด หรือวิธีการประเมินโดยเฉพาะ
sundar - Reinstate Monica

6
@ andreasdr แต่แม้ว่าอาร์กิวเมนต์เครื่องสำอางนั้นถูกต้องแล้ววิธีการของ Python ไม่ได้นำเสนอปัญหาใหม่ในการอ่านหรือไม่? ในการร่วมกันใช้งานภาษาอังกฤษคำว่า "ช่วง" หมายความว่าบางสิ่งบางช่วงจากบางสิ่งบางอย่างเพื่ออะไร - เหมือนช่วงเวลา len (รายการ (ช่วง (1,2))) ส่งคืน 1 และ len (รายการ (ช่วง (2))) ส่งคืน 2 เป็นสิ่งที่คุณต้องเรียนรู้ที่จะย่อย
armin

คำตอบ:


245

เพราะมันเป็นเรื่องปกติมากขึ้นที่จะเรียกrange(0, 10)ซึ่งผลตอบแทน[0,1,2,3,4,5,6,7,8,9]ที่มีองค์ประกอบ 10 len(range(0, 10))ซึ่งเท่ากับ จำไว้ว่าโปรแกรมเมอร์ต้องการดัชนีแบบ 0

นอกจากนี้ให้พิจารณาข้อมูลโค้ดทั่วไปต่อไปนี้:

for i in range(len(li)):
    pass

คุณจะเห็นว่าถ้าrange()ไปถึงlen(li)ที่ว่านี้จะเป็นปัญหาได้หรือไม่ โปรแกรมเมอร์จะต้องชัดเจนลบ 1 นี้ยังเป็นไปตามแนวโน้มทั่วไปของโปรแกรมเมอร์พอใจมากกว่าfor(int i = 0; i < 10; i++)for(int i = 0; i <= 9; i++)

หากคุณกำลังโทรหาช่วงที่เริ่มต้น 1 บ่อยครั้งคุณอาจต้องการกำหนดฟังก์ชั่นของคุณเอง:

>>> def range1(start, end):
...     return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

48
ถ้านั่นคือเหตุผลที่ว่าพารามิเตอร์จะrange(start, count)ไม่?
Mark Ransom

3
@shogun ค่าเริ่มต้นค่าเริ่มต้นให้เป็น 0 คือเทียบเท่ากับrange(10) range(0, 10)
moinudin

4
คุณจะไม่ทำงานกับช่วงที่มีขนาดขั้นตอนที่แตกต่างกว่าrange1 1
dimo414

6
คุณอธิบายว่าช่วง (x) ควรเริ่มต้นด้วย 0 และ x จะเป็น "ความยาวของช่วง" ตกลง. แต่คุณไม่ได้อธิบายว่าทำไมช่วง (x, y) ควรเริ่มต้นด้วย x และลงท้ายด้วย y-1 หากโปรแกรมเมอร์ต้องการ for-loop กับ i ตั้งแต่ 1 ถึง 3 เขาต้องเพิ่ม 1 อย่างชัดเจนนี่คือความสะดวกสบายจริง ๆ หรือไม่?
armin

7
for i in range(len(li)):ค่อนข้างเป็นปฏิปักษ์ enumerateหนึ่งควรใช้
ฮันส์

27

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

ด้วยความสับสนเช่น 'ช่วง (1,10)' อาจเกิดขึ้นจากการคิดว่าพารามิเตอร์คู่หมายถึง

เป็นจริงเริ่มและ "หยุด"

ทีนี้ถ้ามันเป็นค่า "จบ" แล้วใช่คุณอาจคาดหวังว่าจำนวนนั้นจะถูกรวมเป็นรายการสุดท้ายในลำดับ แต่มันไม่ใช่ "ตอนจบ"

คนอื่นเรียกพารามิเตอร์นั้นว่า "นับ" โดยไม่ได้ตั้งใจเพราะถ้าคุณใช้ 'range (n)' เท่านั้นแน่นอนว่ามันทำซ้ำ 'n' ครั้ง ตรรกะนี้ผิดพลาดเมื่อคุณเพิ่มพารามิเตอร์เริ่มต้น

ดังนั้นจุดสำคัญคือการจำชื่อของมัน: " หยุด " ซึ่งหมายความว่าเป็นจุดที่เมื่อถึงจุดซ้ำจะหยุดทันที ไม่ใช่หลังจากนั้น

ดังนั้นในขณะที่ "เริ่มต้น" จะแสดงถึงค่าแรกที่จะรวมในการเข้าถึง "หยุด" มูลค่ามัน 'แบ่ง' มากกว่าดำเนินการต่อเพื่อดำเนินการ 'หนึ่งเช่นกัน' ก่อนที่จะหยุด

สิ่งหนึ่งที่ฉันเคยอธิบายเรื่องนี้กับเด็ก ๆ ก็คือมันน่าขันมากกว่าพฤติกรรมของเด็ก! มันไม่หยุดหลังจากที่ควรจะ - มันหยุดทันทีโดยไม่ทำในสิ่งที่มันทำ (พวกเขาได้รับสิ่งนี้;)

การเปรียบเทียบอื่น - เมื่อคุณขับรถคุณจะไม่ผ่านป้ายหยุด / ให้ผลตอบแทน / 'หลีกทาง' และจบลงด้วยการนั่งข้างใดข้างหนึ่งหรือข้างหลังรถของคุณ ในทางเทคนิคคุณยังไม่ถึงเมื่อคุณหยุด ไม่รวมอยู่ใน 'สิ่งที่คุณทำในการเดินทาง'

ฉันหวังว่าสิ่งเหล่านี้จะช่วยอธิบาย Pythonitos / Pythonitas!


คำอธิบายนี้ใช้งานง่ายขึ้น ขอบคุณ
Fred

คำอธิบายสำหรับเด็กเป็นแค่เฮฮา!
Antony Hatchkins

1
คุณกำลังพยายามวางลิปสติกบนหมู ความแตกต่างระหว่าง "หยุด" และ "จบ" นั้นไร้สาระ ถ้าฉันไปจาก 1 ถึง 7 ฉันยังไม่ผ่าน 7 มันเป็นข้อบกพร่องของ Python ที่จะมีแบบแผนต่าง ๆ สำหรับตำแหน่งเริ่มต้นและหยุด ในภาษาอื่นรวมถึงมนุษย์ "จาก X ถึง Y" หมายถึง "จาก X ถึง Y" ใน Python "X: Y" หมายถึง "X: Y-1" หากคุณมีการประชุมตั้งแต่ 9 ถึง 11 คุณบอกคนอื่นว่าเป็น 9 ถึง 12 หรือ 8 ถึง 11 หรือไม่?
bzip2

24

ช่วงพิเศษมีประโยชน์บางอย่าง:

สำหรับสิ่งหนึ่งในแต่ละรายการเป็นดัชนีที่ถูกต้องสำหรับรายการของความยาวrange(0,n)n

นอกจากนี้ยังrange(0,n)มีความยาวnไม่n+1รวมช่วงที่จะ


18

มันทำงานได้ดีร่วมกับการจัดทำดัชนี zero-based len()และ ตัวอย่างเช่นหากคุณมี 10 รายการในรายการxพวกเขาจะมีหมายเลข 0-9 range(len(x))ให้คุณ 0-9

แน่นอนคนจะบอกคุณก็มากขึ้น Pythonic ที่จะทำfor item in xหรือมากกว่าfor index, item in enumerate(x)for i in range(len(x))

การแบ่งส่วนทำงานในลักษณะเดียวกัน: foo[1:4]เป็นรายการที่ 1-3 ของfoo(โปรดจำไว้ว่ารายการที่ 1 เป็นรายการที่สองเนื่องจากการทำดัชนีแบบ zero-based) เพื่อความมั่นคงพวกเขาทั้งคู่ควรทำงานในลักษณะเดียวกัน

ฉันคิดว่ามันเป็น: "หมายเลขแรกที่คุณต้องการตามด้วยหมายเลขแรกที่คุณไม่ต้องการ" ถ้าคุณต้องการที่ 1-10 จำนวนแรกที่คุณไม่ต้องการเป็น 11 range(1, 11)ดังนั้นจึงเป็นเรื่อง

ถ้ามันจะกลายเป็นความยุ่งยากในการประยุกต์ใช้โดยเฉพาะอย่างยิ่งก็พอที่ง่ายต่อการเขียนฟังก์ชั่นช่วยตัวน้อยที่เพิ่ม 1 range()ถึงดัชนีสิ้นสุดและบริการโทร


1
เห็นด้วยกับการหั่น w = 'abc'; w[:] == w[0:len(w)]; w[:-1] == w[0:len(w)-1];
kevpie

def full_range(start,stop): return range(start,stop+1) ## helper function
โนเบิล

บางทีตัวอย่างที่แจกแจงควรอ่านfor index, item in enumerate(x)เพื่อหลีกเลี่ยงความสับสน
seans

@ seans ขอบคุณคง
ใจดี

12

นอกจากนี้ยังมีประโยชน์สำหรับการแยกช่วง range(a,b)สามารถแบ่งออกเป็นrange(a, x)และrange(x, b)ในขณะที่มีช่วงรวมคุณจะเขียนอย่างใดอย่างหนึ่งหรือx-1 x+1ในขณะที่คุณไม่จำเป็นต้องแยกช่วงคุณมักจะแยกรายการบ่อย ๆ ซึ่งเป็นหนึ่งในเหตุผลที่แบ่งรายการl[a:b]มีองค์ประกอบ a-th แต่ไม่ใช่ b-th แล้วrangeมีคุณสมบัติเดียวกันทำให้สอดคล้องกัน


11

ความยาวของช่วงคือค่าสูงสุดลบค่าต่ำสุด

มันคล้ายกับสิ่งที่ชอบมาก:

for (var i = 1; i < 11; i++) {
    //i goes from 1 to 10 in here
}

ในภาษา C-style

ยังชอบช่วงของทับทิม:

1...11 #this is a range from 1 to 10

อย่างไรก็ตาม Ruby ยอมรับว่าหลายครั้งที่คุณต้องการรวมค่าเทอร์มินัลและเสนอไวยากรณ์ทางเลือก:

1..10 #this is also a range from 1 to 10

17
Gah! ฉันไม่ได้ใช้ทับทิม แต่ผมสามารถจินตนาการว่า1..10เทียบ1...10เป็นเรื่องยากที่จะแยกแยะความแตกต่างระหว่างการอ่านรหัส!
moinudin

6
@marcog - เมื่อคุณรู้ว่าทั้งสองรูปแบบที่มีอยู่ดวงตาของคุณปรับตัวให้เข้ากับความแตกต่าง :)
Skilldrick

11
ผู้ประกอบการช่วงของ Ruby ใช้งานง่ายอย่างสมบูรณ์แบบ แบบฟอร์มที่ยาวขึ้นจะทำให้คุณได้รับลำดับที่สั้นลง กระแอม
Russell Borogove

4
@ รัสเซลอาจจะ 1 ............ 20 ควรให้ช่วงเหมือนกับ 1..10 ทีนี้ก็น่าจะเป็นการเปลี่ยนน้ำตาล syntactic ;)
kevpie

4
@Russell จุดพิเศษบีบรายการสุดท้ายออกจากช่วง :)
Skilldrick

5

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

def main():
    for i in inclusive_range(25):
        print(i, sep=" ")


def inclusive_range(*args):
    numargs = len(args)
    if numargs == 0:
        raise TypeError("you need to write at least a value")
    elif numargs == 1:
        stop = args[0]
        start = 0
        step = 1
    elif numargs == 2:
        (start, stop) = args
        step = 1
    elif numargs == 3:
        (start, stop, step) = args
    else:
        raise TypeError("Inclusive range was expected at most 3 arguments,got {}".format(numargs))
    i = start
    while i <= stop:
        yield i
        i += step


if __name__ == "__main__":
    main()

4

พิจารณารหัส

for i in range(10):
    print "You'll see this 10 times", i

แนวคิดก็คือคุณจะได้รับรายการความยาวy-xซึ่งคุณสามารถทำซ้ำได้

อ่านเพิ่มเติมเกี่ยวกับเอกสารของไพ ธ อน - พวกมันพิจารณาการวนซ้ำแบบวนซ้ำโดยใช้ไฟล์หลัก


1

สะดวกกว่าที่จะให้เหตุผลในหลาย ๆ กรณี

โดยทั่วไปเราอาจคิดว่าช่วงเป็นช่วงเวลาระหว่างและstart endถ้าความยาวของช่วงเวลาระหว่างพวกเขาคือstart <= end end - startหากlenกำหนดตามความยาวจริงคุณจะต้อง:

len(range(start, end)) == start - end

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

การเพิ่มstepพารามิเตอร์นั้นเหมือนกับการแนะนำหน่วยของความยาว ในกรณีนี้คุณคาดหวัง

len(range(start, end, step)) == (start - end) / step

สำหรับความยาว ในการรับการนับคุณเพียงแค่ใช้การหารจำนวนเต็ม


การป้องกันของความไม่สอดคล้องของ Python เหล่านี้มีความสนุกสนาน ถ้าฉันต้องการช่วงเวลาระหว่างตัวเลขสองตัวทำไมฉันถึงใช้การลบเพื่อให้ได้ความแตกต่างแทนที่จะเป็นช่วง? ไม่สอดคล้องกันที่จะใช้หลักการจัดทำดัชนีที่แตกต่างกันสำหรับตำแหน่งเริ่มต้นและสิ้นสุด ทำไมคุณต้องเขียน "5:22" เพื่อให้ได้ตำแหน่งที่ 5 ถึง 21
bzip2

มันไม่ใช่ Python มันค่อนข้างธรรมดาทั่วกระดาน ใน C, Java, Ruby คุณชื่อมัน
Arseny

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