Mengapa rentang (mulai, akhir) tidak termasuk akhir?

305
>>> range(1,11)

Memberi anda

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

Kenapa tidak 1-11?

Apakah mereka hanya memutuskan untuk melakukannya secara acak atau memiliki nilai yang tidak saya lihat?

MetaGuru
sumber
11
baca Dijkstra, ewd831
SilentGhost
11
Pada dasarnya Anda memilih satu set bug off-by-one untuk bug lainnya. Satu set lebih cenderung menyebabkan loop Anda berakhir lebih awal, yang lain cenderung menyebabkan Pengecualian (atau buffer overflow dalam bahasa lain). Setelah Anda menulis banyak kode, Anda akan melihat bahwa pilihan perilaku lebih range()masuk akal
John La Rooy
32
Tautan ke Dijkstra, ewd831: cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF
unutbu
35
@unutbu Artikel Djikstra itu sering dikutip dalam topik ini tetapi tidak memberikan nilai apa pun di sini, orang menggunakannya hanya sebagai daya tarik bagi otoritas. Satu-satunya alasan semu yang relevan yang ia berikan untuk pertanyaan OP adalah ia kebetulan merasa bahwa termasuk batas atas menjadi "tidak wajar" dan "jelek" dalam kasus tertentu di mana urutan kosong - itu adalah posisi yang sepenuhnya subjektif dan mudah diperdebatkan, jadi itu tidak membawa banyak ke meja. "Percobaan" dengan Mesa tidak banyak nilainya baik tanpa mengetahui kendala khusus mereka atau metode evaluasi.
sundar - Reinstate Monica
6
@andreasdr Tetapi bahkan jika argumen kosmetik valid, bukankah pendekatan Python memperkenalkan masalah baru keterbacaan? Dalam bahasa Inggris yang umum digunakan, istilah "rentang" menyiratkan bahwa sesuatu berkisar dari sesuatu ke sesuatu - seperti interval. Itu len (daftar (rentang (1,2))) mengembalikan 1 dan len (daftar (kisaran (2))) mengembalikan 2 adalah sesuatu yang Anda benar-benar harus belajar untuk dicerna.
armin

Jawaban:

245

Karena itu lebih umum untuk memanggil range(0, 10)yang mengembalikan [0,1,2,3,4,5,6,7,8,9]yang berisi 10 elemen yang sama len(range(0, 10)). Ingat bahwa programmer lebih suka pengindeksan berbasis 0.

Pertimbangkan juga cuplikan kode umum berikut:

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

Bisakah Anda melihat bahwa jika range()naik tepat len(li)bahwa ini akan bermasalah? Programmer akan perlu secara eksplisit mengurangi 1. ini juga mengikuti tren umum dari programmer lebih memilih for(int i = 0; i < 10; i++)lebih for(int i = 0; i <= 9; i++).

Jika Anda sering memanggil rentang dengan permulaan 1, Anda mungkin ingin mendefinisikan fungsi Anda sendiri:

>>> def range1(start, end):
...     return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
moinudin
sumber
48
Jika itu alasannya, bukankah parameternya range(start, count)?
Mark Ransom
3
@shogun Nilai awal default ke 0, yaitu range(10)setara dengan range(0, 10).
moinudin
4
Anda range1tidak akan bekerja dengan rentang yang memiliki ukuran langkah berbeda dari 1.
dimo414
6
Anda menjelaskan bahwa rentang (x) harus dimulai dengan 0 dan x akan menjadi "panjang rentang". BAIK. Tetapi Anda tidak menjelaskan mengapa rentang (x, y) harus dimulai dengan x dan diakhiri dengan y-1. Jika programmer menginginkan for-loop dengan saya mulai dari 1 hingga 3, ia harus secara eksplisit menambahkan 1. Apakah ini benar-benar tentang kenyamanan?
armin
7
for i in range(len(li)):lebih merupakan antipattern. Orang harus menggunakan enumerate.
hans
27

Meskipun ada beberapa penjelasan algoritmik yang berguna di sini, saya pikir mungkin membantu untuk menambahkan beberapa alasan 'kehidupan nyata' yang sederhana tentang mengapa ia bekerja dengan cara ini, yang menurut saya berguna ketika memperkenalkan subjek tersebut kepada pendatang baru:

Dengan sesuatu seperti 'rentang (1,10)' kebingungan dapat timbul dari pemikiran bahwa sepasang parameter mewakili "awal dan akhir".

Ini sebenarnya mulai dan "berhenti".

Sekarang, jika itu adalah nilai "akhir" maka, ya, Anda mungkin berharap nomor itu akan dimasukkan sebagai entri terakhir dalam urutan. Tapi itu bukan "akhir".

Yang lain secara keliru menyebut parameter itu "menghitung" karena jika Anda hanya menggunakan 'rentang (n)' maka itu tentu saja, ulangi 'n' kali. Logika ini rusak ketika Anda menambahkan parameter awal.

Jadi intinya adalah mengingat namanya: " stop ". Itu berarti itu adalah titik di mana, ketika tercapai, iterasi akan segera berhenti. Tidak setelah titik itu.

Jadi, sementara "mulai" memang mewakili nilai pertama yang akan dimasukkan, pada mencapai nilai "berhenti" itu 'rusak' daripada terus memproses 'yang juga' sebelum berhenti.

Satu analogi yang saya gunakan dalam menjelaskan ini kepada anak-anak adalah, ironisnya, itu lebih baik daripada anak-anak! Itu tidak berhenti setelah seharusnya - berhenti segera tanpa menyelesaikan apa yang dilakukannya. (Mereka mendapatkan ini;))

Analogi lain - ketika Anda mengendarai mobil Anda tidak melewati tanda berhenti / menyerah / 'memberi jalan' dan berakhir dengan itu duduk di suatu tempat di sebelah, atau di belakang, mobil Anda. Secara teknis Anda masih belum mencapainya ketika Anda berhenti. Itu tidak termasuk dalam 'hal-hal yang Anda lewati dalam perjalanan Anda'.

Saya harap beberapa dari itu membantu dalam menjelaskan kepada Pythonitos / Pythonitas!

dingle
sumber
Penjelasan ini lebih intuitif. Terima kasih
Fred
Penjelasan anak-anak sangat lucu!
Antony Hatchkins
1
Anda mencoba memberi lipstik pada babi. Perbedaan antara "berhenti" dan "berakhir" tidak masuk akal. Jika saya beralih dari 1 ke 7, saya belum lulus 7. Ini hanyalah cacat Python untuk memiliki konvensi yang berbeda untuk posisi start dan stop. Dalam bahasa lain, termasuk bahasa manusia, "dari X ke Y" berarti "dari X ke Y". Dalam Python, "X: Y" berarti "X: Y-1". Jika Anda mengadakan rapat dari 9 hingga 11, apakah Anda memberi tahu orang-orang itu dari 9 hingga 12, atau dari 8 hingga 11?
bzip2
24

Kisaran eksklusif memang memiliki beberapa manfaat:

Untuk satu hal, setiap item dalam range(0,n)adalah indeks yang valid untuk daftar panjang n.

Juga range(0,n)memiliki panjang n, bukan n+1kisaran inklusif.

sepp2k
sumber
18

Ini bekerja dengan baik dalam kombinasi dengan pengindeksan berbasis nol dan len(). Misalnya, jika Anda memiliki 10 item dalam daftar x, mereka diberi nomor 0-9. range(len(x))memberi Anda 0-9.

Tentu saja, orang akan mengatakan kepada Anda bahwa itu lebih Pythonic untuk dilakukan for item in xatau for index, item in enumerate(x)bukan for i in range(len(x)).

Mengiris juga berfungsi: foo[1:4]adalah item 1-3 dari foo(ingat bahwa item 1 sebenarnya item kedua karena pengindeksan berbasis nol). Untuk konsistensi, keduanya harus bekerja dengan cara yang sama.

Saya menganggapnya sebagai: "angka pertama yang Anda inginkan, diikuti oleh angka pertama yang tidak Anda inginkan." Jika Anda ingin 1-10, angka pertama yang tidak Anda inginkan adalah 11, jadi begitu range(1, 11).

Jika menjadi rumit dalam aplikasi tertentu, cukup mudah untuk menulis fungsi pembantu kecil yang menambahkan 1 ke indeks akhir dan panggilan range().

baik hati
sumber
1
Setuju untuk mengiris. 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
nobar
mungkin contoh enumerate harus dibaca for index, item in enumerate(x)untuk menghindari kebingungan
seans
@seans Terima kasih, sudah diperbaiki.
ramah
12

Ini juga berguna untuk rentang pemisahan; range(a,b)dapat dibagi menjadi range(a, x)dan range(x, b), sedangkan dengan rentang inklusif Anda akan menulis salah satu x-1atau x+1. Meskipun Anda jarang perlu membagi rentang, Anda cenderung untuk membagi daftar cukup sering, yang merupakan salah satu alasan mengiris daftar l[a:b]termasuk elemen a tapi bukan b. Kemudian rangememiliki properti yang sama membuatnya konsisten.

xuanji
sumber
11

Panjang rentang adalah nilai teratas dikurangi nilai bawah.

Ini sangat mirip dengan sesuatu seperti:

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

dalam bahasa gaya-C.

Juga menyukai kisaran Ruby:

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

Namun, Ruby mengakui bahwa berkali-kali Anda ingin memasukkan nilai terminal dan menawarkan sintaks alternatif:

1..10 #this is also a range from 1 to 10
Skilldrick
sumber
17
Gah! Saya tidak menggunakan Ruby, tetapi saya bisa membayangkan bahwa 1..10vs 1...10sulit dibedakan antara ketika membaca kode!
moinudin
6
@marcog - ketika Anda tahu dua bentuk ada mata Anda selaras dengan perbedaan :)
Skilldrick
11
Operator jangkauan Ruby sangat intuitif. Bentuk yang lebih panjang memberi Anda urutan yang lebih pendek. batuk
Russell Borogove
4
@Russell, mungkin 1 ............ 20 harus memberikan rentang yang sama dengan 1..10. Sekarang itu akan menjadi gula sintaksis yang berharga untuk ditukar. ;)
kevpie
4
@Russell Titik ekstra meremas item terakhir di luar jangkauan :)
Skilldrick
5

Pada dasarnya dalam python range(n)iterates ntimes, yang bersifat eksklusif itulah sebabnya ia tidak memberikan nilai terakhir ketika sedang dicetak, kita dapat membuat fungsi yang memberikan nilai inklusif artinya juga akan mencetak nilai terakhir yang disebutkan dalam range.

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()
Ashish Dixit
sumber
4

Pertimbangkan kodenya

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

Idenya adalah bahwa Anda mendapatkan daftar panjang y-x, yang Anda dapat (seperti yang Anda lihat di atas) beralih.

Baca pada python docs untuk rentang - mereka menganggap for-loop iteration sebagai usecase utama.

Robert
sumber
1

Dalam banyak kasus hanya lebih mudah untuk dipikirkan.

Pada dasarnya, kita bisa menganggap rentang sebagai interval antara startdan end. Jika start <= end, panjang interval di antara mereka adalah end - start. Jika lensebenarnya didefinisikan sebagai panjang, Anda harus:

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

Namun, kami menghitung bilangan bulat yang termasuk dalam rentang alih-alih mengukur panjang interval. Agar properti di atas benar, kita harus memasukkan salah satu titik akhir dan mengecualikan yang lain.

Menambahkan stepparameter seperti memperkenalkan satuan panjang. Dalam hal ini, yang Anda harapkan

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

panjangnya. Untuk mendapatkan penghitungan, Anda hanya menggunakan divisi integer.

Arseny
sumber
Pertahanan dari ketidakkonsistenan Python ini lucu. Jika saya ingin interval antara dua angka, mengapa saya menggunakan pengurangan untuk mendapatkan perbedaan daripada interval? Tidak konsisten untuk menggunakan konvensi pengindeksan yang berbeda untuk posisi awal dan akhir. Mengapa Anda perlu menulis "5:22" untuk mendapatkan posisi 5 hingga 21?
bzip2
Ini bukan Python, itu cukup umum di seluruh papan. Di C, Java, Ruby, sebut saja
Arseny
Saya bermaksud mengatakan bahwa itu umum untuk pengindeksan, bukan karena bahasa lain memiliki jenis objek yang persis sama
Arseny