Array vs Dictionary

Dua wajah table Lua, pengindeksan berbasis 1, dan kehati-hatian pada operator #.

Daftar isi

Satu nilai table di Lua mampu berperan sebagai array, yaitu bagian urutan dengan kunci bilangan bulat berurutan, sekaligus sebagai dictionary, yaitu bagian kamus dengan kunci sembarang. Topik ini membahas kedua wajah tersebut, konsekuensi pengindeksan berbasis satu, perbedaan ipairs dan pairs, jebakan operator # pada array berlubang, serta praktik yang menjaga bagian urutan tetap dapat diandalkan. Seluruh contoh dapat dijalankan langsung dengan penerjemah Lua dan disertai keluaran aslinya.

Dua wajah table#

Table pada dasarnya adalah struktur asosiatif yang memetakan kunci ke nilai. Kunci boleh berupa nilai apa pun selain nil dan NaN (not a number), sedangkan nilai yang disimpan pun bebas tipe. Wajah array lahir dari konvensi menyimpan data pada kunci bilangan bulat berurutan yang dimulai dari 1, sedangkan wajah dictionary menggunakan kunci sembarang di luar deretan itu. Kedua wajah tersebut tidak saling meniadakan karena keduanya berada dalam satu table yang sama. Contoh berikut menyimpan daftar barang pada kunci 1 sampai 3 dan informasi pengadaan pada kunci string dalam table yang sama.

lua
stok = {"pensil", "buku", "kertas"}
stok.pengadaan = "gudang tengah"
stok[2] = "buku tulis"

print(stok[1], stok[2], stok[3])
print(stok.pengadaan)
print(stok["pengadaan"])
text Keluaran
pensil	buku tulis	kertas
gudang tengah
gudang tengah

Baris pertama keluaran memuat tiga nilai yang dipisahkan karakter tab karena print menerima beberapa argumen sekaligus. Posisi kedua diisi ulang menjadi “buku tulis”, sedangkan pengadaan berdampingan dengan keduanya pada table yang sama. Bentuk stok.pengadaan hanyalah singkatan untuk stok[“pengadaan”], sehingga kedua baris terakhir keluarannya identik. Penulisan dengan tanda titik hanya sah untuk kunci string yang memenuhi aturan penamaan pengenal, sedangkan kunci lain harus ditulis dalam kurung siku.

Pengindeksan berbasis satu#

Bagian urutan table dimulai dari indeks 1, berbeda dengan bahasa seperti C dan Python yang memulai dari 0. Konsekuensinya, elemen pertama selalu ditemukan pada t[1], perulangan numerik berjalan dari 1 sampai jumlah elemen, dan indeks 0 hanyalah kunci biasa yang biasanya tidak berisi. Operator # mengembalikan jumlah elemen bagian urutan sehingga batas akhir perulangan dapat ditulis #kota tanpa penghitungan manual. Contoh berikut menunjukkan ketiga perilaku tersebut pada satu table.

lua
kota = {"Bandung", "Semarang", "Surabaya"}

print(kota[1])
print(kota[0])
print(#kota)

for i = 1, #kota do
  print(i, kota[i])
end
text Keluaran
Bandung
nil
3
1	Bandung
2	Semarang
3	Surabaya

Keluaran menegaskan bahwa kota[1] memuat “Bandung” sedangkan kota[0] bernilai nil karena tidak pernah diisi. Operator # mengembalikan 3 sesuai banyaknya elemen, dan perulangan for menyusuri ketiga kota dengan nomor 1, 2, dan 3. Bagi pembaca yang terbiasa dengan bahasa berbasis 0, kebiasaan mengakses elemen pertama lewat indeks 0 perlu diwaspadai karena hasilnya selalu nil pada table yang baru dibuat.

ipairs dan pairs#

Perulangan for generik menyediakan dua iterator bawaan untuk table. Fungsi ipairs menelusuri bagian urutan mulai dari indeks 1 dan berhenti tepat sebelum nil pertama ditemukan, sehingga hanya cocok untuk bagian yang rapi. Fungsi pairs mengunjungi semua kunci yang tersimpan, termasuk kunci di luar bagian urutan, tetapi tidak memberikan jaminan apa pun tentang urutan kunjungannya. Contoh berikut menggunakan table yang sama untuk membedakan keduanya.

lua
nilai = {80, 75, nil, 90}

for i, v in ipairs(nilai) do
  print(i, v)
end
text Keluaran
1	80
2	75
lua
nilai = {80, 75, nil, 90}

for k, v in pairs(nilai) do
  print(k, v)
end
text Keluaran
1	80
2	75
4	90

ipairs berhenti setelah indeks 2 karena nilai[3] berisi nil. pairs justru tetap mengunjungi kunci 4 sebab kunci 3 memang tidak tersimpan, karena pemberian nilai nil pada sebuah kunci sama artinya dengan menghapusnya. Urutan kunjungan yang tampil pada pairs adalah pilihan implementasi, bukan janji bahasa, sehingga program tidak boleh bergantung padanya. Pada kunci string, urutan kunjungan pairs bahkan dapat berbeda dari satu proses ke proses lainnya.

Operator # dan array berlubang#

Operator # hanya dapat diandalkan ketika bagian urutan tidak memiliki lubang (hole), yaitu nilai nil di tengah deretan kunci bilangan bulat. Bila ada lubang, manual Lua menyatakan hasilnya tidak terdefinisi, dan implementasinya memang bebas mengembalikan batas mana pun yang masuk akal. Contoh berikut membandingkan satu table penuh dengan dua table berlubang pada penerjemah yang sama.

lua
a = {10, 20, 30}
b = {10, 20, nil, 40}
c = {10, 20, nil}

print("a =", #a)
print("b =", #b)
print("c =", #c)
text Keluaran
a =	3
b =	4
c =	2

Hasil #a bernilai 3 dan sesuai harapan. Untuk b, penerjemah mengembalikan 4 meskipun elemen ketiga tidak ada, dan untuk c mengembalikan 2. Nilai-nilai itu bukan kontrak bahasa, melainkan pilihan implementasi yang dapat berubah antarversi maupun antarcara pembuatan table. Program yang menyimpan data penting tidak boleh menaruh nil di bagian urutan.

Praktik aman pada bagian urutan#

Dua kebiasaan menjaga bagian urutan tetap tertib. Pertama, gunakan table.insert dan table.remove alih-alih menugaskan nil, karena keduanya menggeser elemen dan memperbarui batas secara konsisten. Kedua, bila pengelolaan indeks dilakukan sendiri, catat jumlah elemen pada kunci lain dalam table yang sama sehingga batas akhir tidak bergantung pada operator #. Contoh berikut membangun antrean piket sederhana.

lua
antrean = {}

table.insert(antrean, "Ali")
table.insert(antrean, "Budi")
table.insert(antrean, "Citra")

table.remove(antrean, 1)

print(#antrean)
for i, nama in ipairs(antrean) do
  print(i, nama)
end
text Keluaran
2
1	Budi
2	Citra

table.remove menghapus Ali pada posisi pertama lalu menggeser dua nama tersisa, sehingga # mengembalikan 2 dan tidak ada lubang tersisa. Pola kedua menggunakan penghitung pada kunci lain, sebagaimana catatan sensor suhu berikut.

lua
sensor = {jumlah = 0}

local function catat(t, suhu)
  t.jumlah = t.jumlah + 1
  t[t.jumlah] = suhu
end

catat(sensor, 24.5)
catat(sensor, 25.0)
catat(sensor, 23.8)

print(sensor.jumlah)
for i = 1, sensor.jumlah do
  print(i, sensor[i])
end
text Keluaran
3
1	24.5
2	25.0
3	23.8

Kunci jumlah berada di wajah dictionary, sedangkan pengukuran tersimpan pada wajah array table yang sama. Perulangan membaca dari 1 sampai sensor.jumlah sehingga hasilnya pasti tanpa menyentuh operator #. Perhatikan pula bahwa 25.0 dicetak dengan bagian desimal karena penulisan bertitik menghasilkan bilangan pecahan (float).

Kunci di luar string#

Kunci dictionary tidak terbatas pada string. Bilangan, boolean, dan bahkan table atau fungsi dapat menjadi kunci karena pembandingnya adalah identitas nilai, bukan isinya. Dua table yang berbeda selalu dianggap dua kunci yang berbeda sekalipun isinya sama. Contoh berikut menyimpan satu table sebagai kunci lalu mencari nilai dengan kunci table baru yang lain.

lua
konversi = {}
konversi["cm"] = 1
konversi[100] = "satu meter"
konversi[true] = "aktif"

kunci = {nama = "petunjuk"}
konversi[kunci] = "nilai pada kunci table"

print(konversi["cm"])
print(konversi[100])
print(konversi[true])
print(konversi[kunci])
print(konversi[{}])
text Keluaran
1
satu meter
aktif
nilai pada kunci table
nil

Baris terakhir bernilai nil karena penulisan {} membuat table baru dengan identitas yang berbeda dari kunci yang pernah dipasang. Nilai hanya dapat ditemukan kembali melalui kunci yang persis sama, sehingga table yang digunakan sebagai kunci perlu disimpan dalam variabel seperti pada contoh.

Kebutuhan penyajian berurut tetap dapat dipenuhi dengan mengumpulkan kunci ke bagian urutan lalu mengurutkannya dengan table.sort sebelum disusuri kembali.

lua
berat = {Ali = 68, Budi = 75, Citra = 61}

local nama = {}
for k in pairs(berat) do
  nama[#nama + 1] = k
end
table.sort(nama)

for i, v in ipairs(nama) do
  print(v, berat[v])
end
text Keluaran
Ali	68
Budi	75
Citra	61

Ringkasan#

  • Satu table menyimpan bagian urutan pada kunci bilangan bulat mulai dari 1 dan bagian kamus pada kunci sembarang secara bersamaan.
  • ipairs menelusuri bagian urutan dan berhenti tepat sebelum nil pertama, sedangkan pairs mengunjungi semua kunci tanpa jaminan urutan.
  • Hasil # hanya dapat diandalkan pada bagian urutan tanpa lubang, sehingga nil tidak boleh ditugaskan di bagian urutan.
  • table.insert dan table.remove menjaga keutuhan bagian urutan, sedangkan penghitung pada kunci lain memberikan batas akhir yang pasti.

Referensi#