Saya menjalani latihan di Ruby Koans dan saya dikejutkan oleh Ruby quirk berikut yang saya temukan benar-benar tidak dapat dijelaskan:
array = [:peanut, :butter, :and, :jelly]
array[0] #=> :peanut #OK!
array[0,1] #=> [:peanut] #OK!
array[0,2] #=> [:peanut, :butter] #OK!
array[0,0] #=> [] #OK!
array[2] #=> :and #OK!
array[2,2] #=> [:and, :jelly] #OK!
array[2,20] #=> [:and, :jelly] #OK!
array[4] #=> nil #OK!
array[4,0] #=> [] #HUH?? Why's that?
array[4,100] #=> [] #Still HUH, but consistent with previous one
array[5] #=> nil #consistent with array[4] #=> nil
array[5,0] #=> nil #WOW. Now I don't understand anything anymore...
Jadi mengapa array[5,0]
tidak sama dengan array[4,0]
? Apakah ada alasan mengapa array yang mengiris berperilaku aneh ini ketika Anda mulai di (panjang + 1) th posisi ??
Jawaban:
Mengiris dan mengindeks adalah dua operasi yang berbeda, dan menyimpulkan perilaku satu dari yang lain adalah tempat masalah Anda.
Argumen pertama dalam slice mengidentifikasi bukan elemen tetapi tempat di antara elemen, mendefinisikan rentang (dan bukan elemen itu sendiri):
4 masih dalam array, nyaris saja; jika Anda meminta 0 elemen, Anda mendapatkan ujung kosong array. Tetapi tidak ada indeks 5, jadi Anda tidak bisa memotongnya dari sana.
Ketika Anda melakukan indeks (seperti
array[4]
), Anda menunjuk pada elemen itu sendiri, sehingga indeks hanya naik dari 0 menjadi 3.sumber
ini ada hubungannya dengan fakta bahwa slice mengembalikan array, dokumentasi sumber yang relevan dari Array # slice:
yang menunjukkan kepada saya bahwa jika Anda memberikan permulaan yang di luar batas, ia akan mengembalikan nol, jadi dalam contoh Anda
array[4,0]
meminta elemen ke-4 yang ada, tetapi meminta untuk mengembalikan array elemen nol. Sementaraarray[5,0]
meminta indeks di luar batas sehingga mengembalikan nol. Ini mungkin lebih masuk akal jika Anda ingat bahwa metode slice mengembalikan array baru , bukan mengubah struktur data asli.EDIT:
Setelah meninjau komentar saya memutuskan untuk mengedit jawaban ini. Slice memanggil potongan kode berikut ketika nilai arg adalah dua:
jika Anda melihat di
array.c
kelas di manarb_ary_subseq
metode didefinisikan, Anda melihat bahwa itu mengembalikan nihil jika panjangnya di luar batas, bukan indeks:Dalam hal ini inilah yang terjadi ketika 4 dilewatkan, ia memeriksa bahwa ada 4 elemen dan dengan demikian tidak memicu pengembalian nol. Kemudian berjalan dan mengembalikan array kosong jika arg kedua diatur ke nol. sementara jika 5 dilewatkan, tidak ada 5 elemen dalam array, sehingga mengembalikan nil sebelum arg nol dievaluasi. kode di sini di baris 944.
Saya percaya ini adalah bug, atau setidaknya tidak dapat diprediksi dan bukan 'Prinsip Terkejut'. Ketika saya mendapatkan beberapa menit, saya setidaknya akan mengirimkan patch tes yang gagal ke inti ruby.
sumber
Paling tidak perhatikan bahwa perilakunya konsisten. Dari 5 ke atas semuanya bertindak sama; keanehan hanya terjadi pada
[4,N]
.Mungkin pola ini membantu, atau mungkin saya hanya lelah dan tidak membantu sama sekali.
Di
[4,0]
, kami menangkap ujung array. Saya benar-benar merasa agak aneh, sejauh keindahan dalam pola, jika yang terakhir kembalinil
. Karena konteks seperti ini,4
merupakan opsi yang dapat diterima untuk parameter pertama sehingga array kosong dapat dikembalikan. Namun, begitu kita mencapai angka 5 dan ke atas, metode tersebut kemungkinan langsung keluar dengan sifat sepenuhnya dan sepenuhnya di luar batas.sumber
Ini masuk akal ketika Anda mempertimbangkan bahwa irisan array dapat menjadi nilai yang valid, bukan hanya nilai:
Hal ini tidak akan mungkin jika
array[4,0]
kembalinil
bukan[]
. Namun,array[5,0]
kembalinil
karena di luar batas (memasukkan setelah elemen ke-4 dari array 4-elemen bermakna, tetapi memasukkan setelah elemen ke-5 dari array 4 elemen tidak).Baca sintaks slice
array[x,y]
sebagai "memulai setelahx
elemen dalamarray
, pilih hinggay
elemen". Ini hanya bermakna jikaarray
memiliki setidaknyax
elemen.sumber
Ini tidak masuk akal
Anda harus dapat menetapkan irisan-irisan itu, sehingga mereka didefinisikan sedemikian rupa sehingga awal dan akhir string memiliki ekspresi panjang nol bekerja.
sumber
array[5,0]=:foo # array is now [:peanut, :butter, :and, :jelly, nil, :foo]
[26] pry(main)> array[4,5] = [:love, :hope, :peace] => [:peanut, :butter, :and, :jelly, :love, :hope, :peace]
array = [:a, :b, :c, :d, :e]; array[1,2] = :x, :x; array => [:a, :x, :x, :d, :e]
Saya menemukan penjelasan oleh Gary Wright juga sangat membantu. http://www.ruby-forum.com/topic/1393096#990065
Jawaban oleh Gary Wright adalah -
http://www.ruby-doc.org/core/classes/Array.html
Dokumen tentu bisa lebih jelas tetapi perilaku yang sebenarnya konsisten dan bermanfaat. Catatan: Saya mengasumsikan versi 1.9.X dari String.
Ini membantu untuk mempertimbangkan penomoran dengan cara berikut:
Kesalahan umum (dan dapat dimengerti) terlalu berasumsi bahwa semantik indeks argumen tunggal sama dengan semantik argumen pertama dalam dua skenario argumen (atau rentang). Mereka bukan hal yang sama dalam praktiknya dan dokumentasi tidak mencerminkan hal ini. Kesalahannya jelas ada dalam dokumentasi dan bukan dalam implementasi:
argumen tunggal: indeks mewakili posisi karakter tunggal dalam string. Hasilnya adalah string karakter tunggal yang ditemukan di indeks atau nihil karena tidak ada karakter pada indeks yang diberikan.
dua argumen integer: argumen mengidentifikasi bagian dari string untuk diekstraksi atau diganti. Secara khusus, bagian lebar nol dari string juga dapat diidentifikasi sehingga teks dapat dimasukkan sebelum atau setelah karakter yang ada termasuk di bagian depan atau akhir string. Dalam hal ini, argumen pertama tidak mengidentifikasi posisi karakter tetapi malah mengidentifikasi ruang antar karakter seperti yang ditunjukkan pada diagram di atas. Argumen kedua adalah panjang, yang bisa 0.
Perilaku rentang cukup menarik. Titik awal sama dengan argumen pertama ketika dua argumen diberikan (seperti yang dijelaskan di atas) tetapi titik akhir rentang dapat berupa 'posisi karakter' seperti dengan pengindeksan tunggal atau "posisi tepi" seperti dengan dua argumen integer. Perbedaannya ditentukan oleh apakah rentang titik ganda atau rentang titik tiga digunakan:
Jika Anda kembali melalui contoh-contoh ini dan bersikeras dan menggunakan semantik indeks tunggal untuk contoh pengindeksan ganda atau rentang Anda hanya akan bingung. Anda harus menggunakan penomoran alternatif yang saya tunjukkan dalam diagram ascii untuk memodelkan perilaku aktual.
sumber
Saya setuju bahwa ini tampak seperti perilaku aneh, tetapi bahkan dokumentasi resmi tentang
Array#slice
menunjukkan perilaku yang sama seperti pada contoh Anda, dalam "kasus khusus" di bawah:Sayangnya, bahkan deskripsi mereka
Array#slice
tampaknya tidak menawarkan wawasan mengapa itu bekerja seperti ini:sumber
Penjelasan yang diberikan oleh Jim Weirich
sumber
Pertimbangkan array berikut:
Anda bisa memasukkan item ke awal (kepala) array dengan menugaskannya
a[0,0]
. Untuk meletakkan elemen di antara"a"
dan"b"
, gunakana[1,0]
. Pada dasarnya, dalam notasia[i,n]
,i
merupakan indeks dann
sejumlah elemen. Kapann=0
, itu menentukan posisi antara elemen-elemen array.Sekarang jika Anda berpikir tentang akhir array, bagaimana Anda bisa menambahkan item ke ujungnya menggunakan notasi yang dijelaskan di atas? Sederhana, tetapkan nilainya
a[3,0]
. Ini adalah ekor dari array.Jadi, jika Anda mencoba mengakses elemen di
a[3,0]
, Anda akan mendapatkannya[]
. Dalam hal ini Anda masih dalam kisaran array. Tetapi jika Anda mencoba mengaksesa[4,0]
, Anda akan mendapatkannil
nilai pengembalian, karena Anda tidak lagi berada dalam jangkauan array.Baca lebih lanjut di http://mybrainstormings.wordpress.com/2012/09/10/arrays-in-ruby/ .
sumber
tl; dr: dalam kode sumbernya
array.c
, fungsi yang berbeda dipanggil tergantung pada apakah Anda memberikan 1 atau 2 argumen untukArray#slice
menghasilkan nilai pengembalian yang tidak terduga.(Pertama-tama, saya ingin menunjukkan bahwa saya tidak kode dalam C, tetapi telah menggunakan Ruby selama bertahun-tahun. Jadi jika Anda tidak terbiasa dengan C, tetapi Anda membutuhkan waktu beberapa menit untuk membiasakan diri dengan dasar-dasar fungsi dan variabel benar-benar tidak sulit untuk mengikuti kode sumber Ruby, seperti yang ditunjukkan di bawah ini. Jawaban ini didasarkan pada Ruby v2.3, tetapi kurang lebih sama dengan kembali ke v1.9.)
Skenario 1
array.length == 4; array.slice(4) #=> nil
Jika Anda melihat kode sumber untuk
Array#slice
(rb_ary_aref
), Anda melihat bahwa ketika hanya satu argumen yang dilewati ( baris 1277-1289 ),rb_ary_entry
dipanggil, meneruskan nilai indeks (yang bisa positif atau negatif).rb_ary_entry
kemudian menghitung posisi elemen yang diminta dari awal array (dengan kata lain, jika indeks negatif dilewatkan, itu menghitung setara positif) dan kemudian panggilanrb_ary_elt
untuk mendapatkan elemen yang diminta.Seperti yang diharapkan,
rb_ary_elt
kembalinil
ketika panjang arraylen
adalah kurang dari atau sama dengan indeks (di sini disebutoffset
).Skenario # 2
array.length == 4; array.slice(4, 0) #=> []
Namun ketika 2 argumen dilewatkan (yaitu indeks awal
beg
, dan panjang slicelen
),rb_ary_subseq
disebut.Dalam
rb_ary_subseq
, jika indeks mulaibeg
adalah lebih besar dari panjang arrayalen
,nil
dikembalikan:Kalau tidak, panjang irisan yang dihasilkan
len
dihitung, dan jika itu ditentukan menjadi nol, array kosong dikembalikan:Jadi karena indeks awal 4 tidak lebih besar dari
array.length
, array kosong dikembalikan bukannil
nilai yang mungkin diharapkan.Pertanyaan dijawab?
Jika pertanyaan aktual di sini bukan "Kode apa yang menyebabkan ini terjadi?", Tetapi, "Mengapa Matz melakukannya dengan cara ini?", Anda hanya perlu membelikannya secangkir kopi di RubyConf berikutnya dan Tanyakan dia.
sumber