ITERASI DAN PENGGUNAAN FOR
BAB 7 ITERASI DAN PENGGUNAAN FOR
Pada bab ini,kita akan belajar bagaimana menulis iterasi menggunakan for. Kita mulai dengan iterasi sederhana,kemudian kita belajar bagaimana menggunakan seluruh kemampuan for untuk penulisan iterasi yang lebih efisien.
7.1 Iterators dan Closures
Sebuah iterator adalah susunan yang membolehkan kamu untuk melakukan perulangan kumpulan elemen. Dalam Lua, kita biasanya menggambarka iterator dengan fungsi: setiap kali kita memanggil fungsi,hasilnya akan dilanjutkan ke elemen selanjutnya dari kumpulan tersebut.
Closures memberikan sebuah mekanisme yang sempurna untuk pekerjaan ini. Perlu diingat bahwa closure adalah sebuah fungsi yang mengakses satu atau lebih variabel lokal dari fungsi tertutupnya. Tentu saja,untuk membuat sebuah closure baru kita juga harus membuat variabel lokal diluar fungsi. Oleh karena itu,penyusunan sebuah closure biasanya terdiri dari 2 fungsi: closurenya sendiri dan sebuah fungsi induk yang membuat closure tersebut.
Sebagai contoh sederhana,kita menulis sebuah iterator sederhana untuk sebuah daftar. Tidak seperti ipairs,iterator tidak mengembalikan setiap elemen,hanya nilai: Sebagai contoh sederhana,kita menulis sebuah iterator sederhana untuk sebuah daftar. Tidak seperti ipairs,iterator tidak mengembalikan setiap elemen,hanya nilai:
i=i+1 if i <= n then return t[i] end
end end
Pada contoh ini, list_iter adalah fungsi induk,setiap kali kita memanggil ini,kita membuat sebuah closure baru(iterator itu sendiri). Closure tersebut melindungi setiap langkah dengan variabel luar(t,i dan n) jadi,setiap kita memanggilnya,closure akan mengembalikan nilai selanjutnya dari daftar t. Ketika tidak ada lagi nilai didaftar,iterator mengembalikan nil.
Kita dapat menggunakan beberapa iterator dengan while:
t = {10, 20, 30} iter = list_iter(t) -- creates the iterator while true do
local element = iter() -- calls the iterator if element == nil then break end print(element)
end
Tetapi,akan lebih mudah jika menggunakan for. Karena,memang dirancang untuk iterasi:
t = {10, 20, 30} for element in list_iter(t) do
print(element) end
Penggunaan for selalu terlindungi dari sebuah perulangan iterasi. Ini akan memanggil iterator induk. Melindungi fungsi iterator yang ada didalmnya,jadi kita tidak butuh variabel iter. Memanggil iterator disetiap iterasi baru dan menghentikan loop ketika iterator mengembalikan nil.
Sebagai contoh selanjutnya,kita akan menulis sebuah iterator untuk mendapatkan seluruh kata dari input file untuk melakukan pencarian ini,kita butuh 2 nilai.yaitu baris dan posisi. Dengan data itu,kita selalul mendapatkan kata selanjutnya. Untuk itu kita menggunakan 2 variabel yaitu line dan pos:
f unction allwords () local line = io.read() -- current line local pos = 1 -- current position in the line return function () -- iterator function
while line do -- repeat while there are lines
local s, e = string.find(line, "%w+", pos) if s then -- found a word?
pos = e + 1 -- next position is after this
word
return string.sub(line, s, e) -- return the
word else
line = io.read() -- word not found; try next
line line
end end
Bagian utama dari fungsi iterator adalah pemanggilan fungsi string.find. Pemanggilan ini mencari kata yang ada dalam baris,dimulai dari awal posisi baris tersebut. Ini menggambarkan sebuah “word” menggunakan bentuk ‘%w+’,yang menyamakan satu atau lebih karakter alphanumeric. Jika ini ditemukan dalam kata,fungsi memperbaharui posisi ke karakter pertama setelah kata dan mengembalikan kata tersebut. Jika tidak,iterator membaca sebuah baris baru dan kembali melakukan pencarian. Jika tidak ada baris lagi,fungsi akan mengembalikan nil sebagai tanda berakhirnya iterasi.
Bergantung pada kompleksitas,berapa banyak allwords yang dikerjakan:
for word in allwords() do
print(word) end
Ini adalah situasi yang biasa dengan iterator. Ini mungkin sulit untuk ditulis,tetapi mudah digunakan. Ini bukan masalah besar: Paling sering,pengguna Lua tidak mendefinisikan iterator,tetapi langsung menggunakannya dalam aplikasi.
7.2 Tata Bahasa Pernyataan For
Pada iterator sebelumnya kita butuh untuk membuat sebuah closure untuk setiap perulangan baru. Untuk kebanyakan situasi,ini bukan masalah nyata. Sebagai contoh,dalam iterator allwords,biaya untuk membuat sebuah closure tidak begitu berarti jika dibandingkan dengan membaca sebuah file. Tetapi,dalam beberapa situasi,hal ini tidak diinginkan terjadi. Dalam beberapa pilihan,kita dapat menggunakan for untuk menjaga iterasi.
Kita lihat kegunaan for dalam iterator fungsi internal,selama perulangan biasanya,terdiri dari
3 nilai: fungsi iterator, invariant state dan sebuah variabel control. Bentuk Umum For:
for <var-list> in <exp-list> do <body> end
Dimana <var-list> adalah daftar satu atau lebih nama variabel,dipisahkan oleh koma dan <exp-list> adalah sebuah daftar satu atau lebih ekspresi,yang juga dipisahkan oleh koma. Biasanya,daftar ekspresi terdiri dari satu elemen,sebuah panggilan untuk iterator induk,sebagai contoh kode dibawah ini:
for k, v in pairs(t) do print(k, v) end
Daftar variabel adalah v,k: daftar ekspresi mempunyai satu elemen yaitu pairs(t). Sering daftar variabel hanya punya satu variabel juga seperti dibawah ini: Daftar variabel adalah v,k: daftar ekspresi mempunyai satu elemen yaitu pairs(t). Sering daftar variabel hanya punya satu variabel juga seperti dibawah ini:
Kita panggil variabel pertama dalam daftar variabel control nilai ini tidak pernah nil selama loop,karena ketika nil loop akan berhenti. Hal pertama kali yang dievaluasi oleh for adalah ekspresi setelah in. Ekspresi ini seharusnya menghasilkan 3 nilai:fungsi iterator, invariant state dan nilai awal untuk variabel kontrol. Seperti dalam sebuah multiple assignment,hanya elemen terakhir dari daftar yang dapat menghasilkan nilai lebih dari satu:jumlah nilai diatur menjadi 3,nilai tambahan dihapus atau nil dimasukkan sebagai kebutuhan.
Setelah langkah inisialisasi,for memanggil fungsi iterator dengan 2 argumen yaitu invariant state dan variabel kontrol. Kemudian for memberikan nilai yang dikembalikan dari fungsi iterator ke variabel yang dideklarasikan dalam daftar variabel. Jika nilai pertama yang dikembalikan adalah nil,maka perulangan dihentikan. Jika tidak,for mengeksekusi badan program dan memanggil fungsi iterasi lagi hingga proses selesai.
Bentuk umum yang lebih tepat lagi dari for:
for var_1, ..., var_n in explist do block end
Sama dengan kode dibawah ini:
do local _f, _s, _var = explist while true do
local var_1, ... , var_n = _f(_s, _var) _var = var_1 if _var == nil then break end block
end end
Jadi jika fungsi iterator adalah f,invariant state adalah s,dan initial value untuk variabel control adalah a 0 ,variabel control akan diulang dengan a 1 =f(s,a 0 ),a2=f(s,a 1 ) dan seterusnya hingga a i =nil. Jika for memiliki variabel lain,dengan langkah sederhana kita bisa mendapatkan nilai tambah setiap memanggil f.
7.3 Stateless Iterators
Sesuai dengan namanya,stateless iterator adalah sebuah iterator yang tidak melindungi dirinya sendiri. Oleh karena itu,kita boleh menggunakan stateless iterator yang sama dengan multiple loop,untuk menghindari kerugian lebih baik membuat closure baru.
Dalam setiap iterasi.for memanggil fungsi iterator dengan 2 argumen: invariant state dan variabel kontrol. Sebuah stateless iterator membuat elemen selanjutnya untuk iterasi hanya menggunakan 2 argumen ini. Sebuah contoh dibawah ini menggunakan iterator ipairs,yang mengiterasi seluruh elemen dalam array:
a = {"one", "two", "three"} for i, v in ipairs(a) do print(i, v) end
Bagian dari iterasi adalah tabel yang diubah,ditambah indeks. Ipairs dan iterator dikembalikan dengan sederhana; Kita dapat menulis dalam Lua sebagai berikut:
function iter (a, i) i=i+1 local v = a[i] if v then
return i, v
end end
function ipairs (a) return iter, a, 0 end
Ketika lua memanggil ipairs(a) dalam sebuah looping,akan menghasilkan 3 nilai yaitu fungsi iter sebagai iterator, a sebagai invariant state,dan nol sebagai nilai awal untuk variabel control. Kemudian,Lua memanggil iter(a,0) yang akan menghasilkan 1,a[1]. Pada iterasi kedua,fungsi memanggil iter(a,1), hasilnya 2,a[2] dan seterusnya,hingga elemen nil pertama.
Fungsi pairs,mengiterasi seluruh elemen dalam sebuah tabel,ini sama,kecuali fungsi iterator adalah fungsi next,yang mana adalah fungsi primitive di Lua:
function pairs (t) return next, t, nil end
Pemanggilan next(t,k) dimana k adalah sebuah kunci tabel t,mengembalikan sebuah kunci dalam tabel,dalam sebuah pengurutan acak. Pemanggilan next(t,nil) mengembalikan pasangan pertama ketika tidak ada pasangan lagi,next mengembalikan nil.
Beberapa orang memilih untuk menggunakan next secara langsung,tanpa harus memanggil pairs:
for k, v in next, t do ... end
Perlu diingat daftar ekspresi dari looping menghasilkan 3 hasil,jadi Lua mendapat next,t,dan nil,tentunya apa yang kita dapatkan ketika memanggil pairs(t).
7.4 Iterator dengan bagian yang kompleks
Sering,sebuah iterator butuh banyak bagian dibandingkan invariant state tunggal dan variabel kontrol. Solusi yang paling sederhana adalah menggunakan closure. Solusi lain adalah memaketkan segala sesuatu yang dibutuhkan kedalam sebuah tabel dan menggunakan tabel tersebut sebagai invariant state untuk iterasi. Menggunakan sebuah tabel,sebuah iterator dapat menerima banyak data selama loop. Dan lagi,iterator dapat merubah data tersebut. Meskipun bagian selalu pada tabel yang sama,isi tabel berubah selama loop. Karena iterator punya seluruh data dalam setiap bagian,mereka biasanya menggunakan argument kedua yaitu for.
Sebagai contoh dari teknik ini,kita akan menulis kembali iterator allwords,yang akan mencari semua kata dalam setiap file input. Kali ini,kita akan menjaga setiap bagian menggunakan sebuah tabel dengan 2 field, line dan pos.
Fungsi dimulai dengan iterasi sederhana ini harus mengembalikan fungsi iterator dan bagian awal:
local iterator -- to be defined later
function allwords () local state = {line = io.read(), pos = 1} return iterator, state
end
Fungsi iterator harus bekerja dengan benar:
function iterator (state) while state.line do -- repeat while there are lines -- search for next word local s, e = string.find(state.line, "%w+", state.pos) if s then -- found a word?
-- update next position (after this word) state.pos = e + 1
return string.sub(state.line, s, e) else -- word not found state.line = io.read() -- try next line... state.pos = 1 -- ... from first position
end end return nil -- no more lines: end loop
end
Kapan saja ini mungkin. Kamu seharusnya mencoba untuk menulis stateless iterators ,memisahkan setiap bagian dengan for. Dengan ini,kamu tidak bisa membuat objek baru ketika kamu memulai loop. Jika kamu tidak bisa memasukkan iterasi kedalam model tersebut,kamu seharusnya mencoba closure. Disamping lebih rapih,biasanya closure lebih efisien dibandingkan iterator dengan tabel. Pertama,lebih murah untuk membuat closure dibandingkan membuat sebuah tabel. Kedua,mengakses nilai naik lebih cepat dibandingkan mengakses field tabel. Kemudian kita akan melihat jalur lain untuk menulis iterators dengan coroutines. Ini solusi yang sangat baik,tetapi sedikit agak mahal.
7.5 Iterator yang sebenarnya
Penamaan iterator adalah kesalahan persepsi,karena iterator kami tidak untuk mengiterasi. Apa yang diterasi adalah perulangan for. Iterator hanya memberikan hasil akhir dari iterasi. Mungkin nama yang lebih baik adalah generator. Tetapi iterator sudah dikenal dengan baik dalam bahasa lain seperti Java.
Tetapi,ada jalan lain untuk membuat iterator dimana iterator biasanya melakukan iterasi. Ketika kita menggunakan iterator kita tidak perlu menulis sebuah perulangan: kita cukup memanggil iterator dengan sebuah argument yang menggambarkan apa yang harus iterator lakukan setiap iterasi. Lebih spesifik lagi,iterator menerima sebagai argument fungsi yang dipanggil.
Sebagai contoh nyata,mari kita tulis kembali iterator allwords menggunakan gaya ini: Sebagai contoh nyata,mari kita tulis kembali iterator allwords menggunakan gaya ini:
-- repeat for each word in the line for w in string.gfind(l, "%w+") do
-- call the function f(w)
end
end end
Untuk menggunakan iterator,kita harus memberikan badan loop sebagai sebuah fungsi. Jika kita hanya ingin mencetak setiap kata,kita cukup menggunakan print:
allwords(print)
Paling sering kita menggunakan fungsi tanpa nama sebagai badan fungsi. Untuk contoh,bagian kode selanjutnya akan menghitung berapa kali kata “hello” ditemukan dalam input file:
local count = 0 allwords(function (w)
if w == "hello" then count = count + 1 end end) print(count)
Dengan pekerjaan yang sama,Kita tulis dengan gaya iterator sebelumnya yang tidak jauh berbeda:
local count = 0 for w in allwords() do
if w == "hello" then count = count + 1 end end print(count)
True iterator popular di Lua versi lama,ketika bahasa tidak mempunyai statemen for. Bagaimana membandingkan antara generator dengan bentuk iterator? Kedua gaya punya kemampuan rata-rata: pemanggilan sebuah fungsi per iterasi. Namun,lebih mudah menulis iterator dengan gaya kedua,tetapi gaya generator lebih fleksibel. Pertama,ini membolehkan 2 atau lebih iterasi paralel. Kedua,membolehkan penggunaan break dan return dalam badan iterator.
Latihan
1. Tuliskan suatu loop “for” yang mencatk bilangan genap antara 2 dan 10, dengan urutan dari besar
ke kecil.