Array dan slice

Mengelola buffer kontigu berukuran tetap dan tampilan Span beserta batas pengindeksannya.

Daftar isi

Array adalah buffer kontigu (contiguous) berukuran tetap yang cocok untuk data numerik dengan jumlah elemen yang diketahui di muka. Topik ini membahas pembuatan Array dengan parameter compile-time, inisialisasi lewat fill dan literal, tampilan Span yang membaca buffer tanpa memegang kepemilikannya, serta batas pengindeksan yang diperiksa compiler maupun saat program berjalan. Setelah topik ini dibaca, pilihan antara Array dan List dapat ditentukan dengan mengetahui konsekuensi masing-masing.

Array berukuran tetap#

Array dinyatakan dengan dua parameter compile-time, yaitu tipe elemen dan jumlah elemen, seperti Array[Float64, 4] untuk empat nilai Float64. Jumlah elemen tersebut menjadi bagian dari tipenya, sehingga panjang Array diketahui compiler dan tidak pernah berubah selama program berjalan. Tipe Array tidak berada di prelude, sehingga berkas yang menggunakannya memuat from std.collections.array import Array terlebih dahulu.

Konstruktor fill mengisi seluruh elemen dengan nilai awal yang sama, misalnya Array[Float64, 4](fill=0.0). Setelah itu, setiap elemen ditulis dan dibaca lewat indeks dalam kurung siku dengan penomoran mulai dari nol. Perulangan for-in membaca elemen dari awal ke akhir, dan fungsi len mengembalikan panjang yang selalu sama dengan parameter kedua tipe tersebut.

Berkas buffer.mojo mengisi satu buffer bacaan sensor, lalu menghitung rata-ratanya.

mojo buffer.mojo
from std.collections.array import Array

def main():
    var bacaan = Array[Float64, 4](fill=0.0)
    bacaan[0] = 21.5
    bacaan[1] = 22.0
    bacaan[2] = 20.5
    bacaan[3] = 23.0
    print(len(bacaan))
    for x in bacaan:
        print(x)

    var total = 0.0
    for x in bacaan:
        total += x
    print(total / 4)
bash
# jalankan program buffer berukuran tetap
mojo run buffer.mojo
text Keluaran
4
21.5
22.0
20.5
23.0
21.75

Keluaran menampilkan 4 sebagai panjang buffer, empat nilai bacaan yang ditulis satu per satu, dan 21.75 sebagai rata-rata keempat nilai itu. Jumlah elemen yang perlu diisi dan dibaca terbatas pada ukuran yang dideklarasikan, sehingga penjumlahan di atas ditulis tanpa berjaga-jaga terhadap buffer kosong. Array berukuran tetap cocok untuk kerangka data yang polanya sudah mantap, misalnya satu blok sampel audio atau satu baris matriks.

Elemen awal juga dapat ditulis lewat literal ketika tipenya menyebutkan Array secara eksplisit. Deklarasi var target: Array[Int, 3] = [1, 2, 3] menghasilkan Array bertipe Int berisi tiga elemen, dan panjang Array tersebut dapat diakses dengan len seperti biasa. Bentuk ini berguna ketika isi buffer sudah diketahui sejak awal tanpa pengisian per indeks.

mojo literal.mojo
from std.collections.array import Array

def main():
    var target: Array[Int, 3] = [1, 2, 3]
    print(target[1])
    print(len(target))
bash
# jalankan program Array dari literal
mojo run literal.mojo
text Keluaran
2
3

Keluaran 2 berasal dari indeks target[1], sedangkan 3 menegaskan panjang Array sesuai parameter tipe Array[Int, 3]. Literal inilah yang secara bawaan diperoleh apabila ekspresi [1, 2, 3] ditulis tanpa anotasi tipe apa pun. Sifat tersebut menjelaskan mengapa deklarasi List selalu menyertakan anotasi tipe agar literalnya menjadi List yang dapat tumbuh.

Span: tampilan tanpa kepemilikan#

Span adalah tampilan (view) atas deret elemen yang menunjuk memori buffer aslinya tanpa memiliki buffer tersebut. Tipe Span berada di modul terpisah, sehingga berkas yang menggunakannya memuat from std.collections.span import Span. Pembuatan Span(bacaan) membentuk tampilan atas seluruh elemen Array yang diberikan, dan tampilan itu dapat diindeks serta diperulangkan seperti buffer aslinya.

Berkas span-tampilan.mojo membentuk Span atas buffer bacaan, kemudian mengubah satu elemen buffer setelah tampilannya dibuat.

mojo span-tampilan.mojo
from std.collections.array import Array
from std.collections.span import Span

def main():
    var bacaan = Array[Float64, 4](fill=0.0)
    bacaan[0] = 21.5
    bacaan[3] = 23.0
    var tampilan = Span(bacaan)
    print(len(tampilan))
    print(tampilan[0], tampilan[3])

    bacaan[1] = 22.0
    print(tampilan[1])
    for x in tampilan:
        print(x)
bash
# jalankan program tampilan Span atas buffer
mojo run span-tampilan.mojo
text Keluaran
4
21.5 23.0
22.0
21.5
22.0
0.0
23.0

Keluaran menunjukkan panjang tampilan 4 beserta elemen pada indeks 0 dan 3, lalu 22.0 yang terbaca lewat tampilan setelah nilai tersebut ditulis ke bacaan. Perubahan pada Array langsung tampak melalui Span tersebut karena keduanya menunjuk memori yang sama, dan empat baris terakhir adalah hasil perulangan atas tampilan itu. Sifat non-owning inilah yang menjadikan Span pilihan apabila suatu fungsi perlu membaca data buffer tanpa menyalinnya dan tanpa menyerahkan kepemilikannya.

Batas pengindeksan#

Batas pengindeksan Array diperiksa lebih dahulu oleh compiler karena ukurannya bagian dari tipe. Indeks bacaan[9] pada Array bertipe Array[Float64, 4] berada di luar rentang 0 sampai 3, sehingga programnya bahkan tidak selesai dikompilasi. Berkas indeks-lewat.mojo memuat akses semacam itu.

mojo indeks-lewat.mojo
from std.collections.array import Array

def main():
    var bacaan = Array[Float64, 4](fill=0.0)
    print(bacaan[9])
bash
# indeks di luar batas ditolak saat kompilasi
mojo run indeks-lewat.mojo
text Keluaran
/home/kal/my-project/content/mojo/koleksi-dan-string/array-dan-slice/indeks-lewat.mojo:3:5: error: function instantiation failed
def main():
    ^
/home/kal/my-project/content/mojo/koleksi-dan-string/array-dan-slice/indeks-lewat.mojo:5:17: note: call expansion failed
    print(bacaan[9])
                ^
Mojo/stdlib/std/collections/array.mojo:781:9: note: constraint failed: index is out of bounds
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to run the pass manager

Pesan constraint failed: index is out of bounds muncul pada tahap kompilasi beserta lokasi pengecekan di pustaka standar. Batas pada Span diperiksa pada saat program berjalan, karena panjang deret yang dilihatnya baru diketahui dari buffer yang diberikan. Akses di luar batas pada Span yang membungkus Array[Float64, 4] menghentikan program dengan pesan index 9 is out of bounds, valid range is 0 to 3, sehingga indeks Span perlu dijaga oleh logika program sebelum elemennya diakses.

Kesalahan umum#

Kesalahan pertama adalah memperlakukan Array seperti List, misalnya menambah elemen lewat append. Ukuran Array bagian dari tipenya dan tidak dapat tumbuh, sehingga compiler menolak metode tersebut. Berkas tambah-elemen.mojo mencoba menambahkan elemen pada buffer empat elemen.

mojo tambah-elemen.mojo
from std.collections.array import Array

def main():
    var bacaan = Array[Float64, 4](fill=0.0)
    bacaan.append(5.0)
bash
# Array tidak punya metode append
mojo run tambah-elemen.mojo
text Keluaran
/home/kal/my-project/content/mojo/koleksi-dan-string/array-dan-slice/tambah-elemen.mojo:5:11: error: 'Array[Float64, Int(4)]' value has no attribute 'append'
    bacaan.append(5.0)
    ~~~~~~^
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source module

Pesan 'Array[Float64, Int(4)]' value has no attribute 'append' menyatakan bahwa tipe buffer tersebut tidak menyediakan penambahan elemen. Perbaikannya bergantung pada kebutuhan, yaitu gunakan List apabila jumlah elemen memang berubah seiring waktu, dan pertahankan Array apabila ukurannya memang tetap. Keduanya dapat diperulangkan dan diindeks dengan cara yang sama, sehingga penggantiannya tidak mengubah pola pembacaan data.

Kesalahan kedua adalah menganggap Span menyimpan salinan data. Tampilan hanya membungkus referensi ke memori buffer aslinya, sehingga perubahan pada buffer selalu tampak lewat tampilannya sebagaimana diperagakan pada keluaran span-tampilan.mojo. Apabila fungsi membutuhkan data yang berdiri sendiri dan bebas dari perubahan pemanggil, terima nilainya sebagai Array atau List, bukan sebagai Span.

Ringkasan#

  • Array[T, N] adalah buffer kontigu dengan ukuran sebagai bagian dari tipe, diisi lewat fill atau literal, dan diakses lewat indeks, len, serta perulangan for-in.
  • Span adalah tampilan non-owning atas buffer, dapat diindeks dan diperulangkan, dan selalu memperlihatkan keadaan terbaru buffer aslinya.
  • Indeks di luar batas pada Array ditolak saat kompilasi, sedangkan batas pada Span diperiksa saat program berjalan dan kegagalannya menghentikan program.
  • Array tidak menyediakan append, sehingga kebutuhan ukuran dinamis dilayani oleh List.

Referensi#