FILE DATA

BAB 12 FILE DATA

Ketika berhadapan dengan file data, pada umumnya lebih mudah untuk menulis data dibanding untuk membacanya kembali. Ketika kita menulis suatu file, kita mempunyai kendali penuh dari apa yang sedang berlangsung. Ketika kita membaca suatu file, pada sisi lain, kita tidak mengetahui apa yang diharapkan. Di samping bermacam-macam data yang boleh berisi suatu file yang benar, suatu program sempurna juga perlu menangani file yang tidak baik dengan baik. Oleh karena itu, input coding sempurna yang rutin selalu sulit.

Ketika kita melihat di contoh Bagian 10.1, pembangun tabel menyediakan suatu alternatif menarik untuk memformat file. Dengan sedikit kerja tambahan ketika penulisan data, pembacaan menjadi sepele. Tekniknya adalah menulis file data kita sebagai kode Lua, ketika pengeksekusian, Ketika kita melihat di contoh Bagian 10.1, pembangun tabel menyediakan suatu alternatif menarik untuk memformat file. Dengan sedikit kerja tambahan ketika penulisan data, pembacaan menjadi sepele. Tekniknya adalah menulis file data kita sebagai kode Lua, ketika pengeksekusian,

Seperti biasanya, mari kita lihat suatu contoh untuk membuat semua jelas. Jika file data kita adalah di dalam suatu format yang sudah dikenal, seperti CSV (Comma-Separated Values), kita hanya punya sedikit pilihan. (Di Bab 20, kita akan lihat bagaimana cara membaca CSV di Lua.) Bagaimanapun, jika kita akan menciptakan file untuk digunakan kemudian, kita dapat menggunakan Lua pembangun sebagai format kita, sebagai ganti CSV. Di dalam format ini, kita menghadirkan masing-masing data record sebagai Lua pembangun. Sebagai contoh penulisan seperti

Donald E. Knuth,Literate Programming,CSLI,1992 Jon Bentley,More Programming Pearls,Addison-Wesley,1990

di dalam file data kita, kita tulis

Entry{"Donald E. Knuth", "Literate Programming", "CSLI", 1992}

Entry{"Jon Bentley", "More Programming Pearls", "Addison-Wesley", 1990}

Ingat bahwa Entry{...} sama halnya dengan Entry({...}), yaitu, panggilan untuk fungsi Entry dengan suatu tabel sebagai argumentasi tunggalnya. Oleh karena itu, potongan data yang sebelumnya adalah program Lua. Untuk membaca file ini, kita hanya perlu mengeksekusinya, dengan suatu definisi yang masuk akal untuk Entry. Sebagai contoh, program berikut menghitung banyaknya masukan di dalam suatu file data:

local count = 0 function Entry (b) count = count + 1 end dofile("data") print("number of entries: " .. count)

Kumpulan program berikutnya di dalam seperangkat nama dari semua pengarang ditemukan di dalam file, dan kemudian mencetaknya. (Nama pengarang adalah field pertama pada setiap masukan; maka, jika b adalah suatu nilai masukan, b[1] adalah pengarangnya.)

local authors = {} -- a set to collect authors function Entry (b) authors[b[1]] = true end dofile("data") for name in pairs(authors) do print(name) end

Pesan pendekatan event-driven di program ini membagi: fungsi entry bertindak sebagai suatu fungsi callback, yang dipanggil sepanjang dofile untuk masing-masing masukan dalam file data.

Ketika ukuran file tidak menjadi suatu perhatian besar, kita dapat menggunakan sepasang name-value untuk penyajian kita:

Entry{ author = "Donald E. Knuth", Entry{ author = "Donald E. Knuth",

Entry{ author = "Jon Bentley", title = "More Programming Pearls", publisher = "Addison-Wesley", year = 1990 }

(Jika format ini mengingatkanmu pada BibTeX, itu bukan suatu kebetulan. BibTeX adalah satu inspirasi untuk sintaks pembangun di dalam Lua.) Format ini adalah apa yang kita sebut format data self-describing , sebab masing-masing potongan data telah dikaitkan dengan suatu uraian singkat tentang artinya . Data self-describing jadi lebih menarik (dengan manusia, sedikitnya) dibanding CSV atau notasi ringkas lain; mereka mudah untuk mengedit dengan tangan, ketika perlu; dan mereka mengijinkan kita untuk membuat modifikasi kecil tanpa harus mengubah file data. Sebagai contoh, jika kita menambahkan suatu field baru yang kita perlukan hanya suatu peluang kecil di dalam pembacaan program, sedemikian sehingga tersedia suatu nilai default ketika field tidak ada.

Dengan format name-value, program untuk mengumpulkan pengarang kita menjadi

local authors = {} -- a set to collect authors function Entry (b) authors[b.author] = true end dofile("data") for name in pairs(authors) do print(name) end

Sekarang order field tidak relevan. Sekalipun beberapa masukan tidak mempunyai suatu pengarang, kita hanya harus mengubah Entry:

function Entry (b) if b.author then authors[b.author] = true end end

Lua tidak hanya mengeksekusi dengan cepat, tetapi juga menyusun dengan cepat. Sebagai contoh, program di atas untuk daftar pengarang mengeksekusi kurang dari satu detik/second untuk

2 MB data. Dan lagi, ini bukan suatu kebetulan. Data deskripsi telah menjadi salah satu dari aplikasi Lua yang utama sejak diciptakan dan kita mengambil perhatian yang baik untuk membuat compiler nya cepat untuk potongan besar.

12.1 Serialization

Sesering mungkin kita harus menyerialisasi beberapa data, yaitu, untuk mengkonversi data ke dalam suatu arus bytes atau karakter, sedemikian sehingga kita dapat menyimpannya ke dalam suatu file atau mengirimkannya melalui suatu koneksi jaringan. Kita dapat menghadirkan data yang dijadikan serial sebagai kode Lua, sedemikian sehingga, ketika kita menjalankan kode itu, kode itu merekonstruksi nilai-nilai yang telah disimpan ke dalam pembacaan program.

Pada umumnya, jika kita ingin menyimpan kembali nilai dari suatu variabel global, potongan kita akan menjadi sesuatu seperti varname= <exp>, dimana <exp> adalah kode Lua untuk menciptakan nilai itu. Varname adalah bagian yang mudah, jadi mari kita lihat bagaimana cara menulis kode yang menciptakan suatu nilai. Untuk suatu nilai numeric, tugas itu mudah:

function serialize (o) if type(o) == "number" then function serialize (o) if type(o) == "number" then

Untuk suatu nilai string, suatu pendekatan naif akan seperti berikut

if type(o) == "string" then io.write("'", o, "'")

Bagaimanapun, jika string berisi karakter khusus (seperti tanda kutip atau newlines) hasil kode tidak akan menjadi suatu program Lua sah. Di sini, anda mungkin tergoda untuk memecahkan masalah yang mengubah tanda kutip ini:

if type(o) == "string" then io.write("[[", o, "]]")

Jangan lakukan itu! Kurung besar ganda dimaksudkan untuk string-string yang ditulisan tangan, bukan untuk yang dihasilkan secara otomatis. Jika seorang pemakai yang jahat mengatur secara langsung programmu untuk menyimpan sesuatu seperti " ]. os.execute('rm*')..[[" (sebagai contoh, dia dapat menyediakan string itu sebagai alamatnya), potongan akhirmu nantinya

varname = [[ ]]..os.execute('rm *')..[[ ]]

Kamu akan mempunyai suatu kejutan buruk yang berusaha untuk mengisi "data" ini. Untuk mengutip suatu string yang berubah-ubah dengan cara yang aman, fungsi format, dari perpustakaan string standard, menawarkan pilihan "% q". Hal itu mengelilingi string dengan tanda kutip ganda dan dengan baik melepas tanda kutip ganda, newlines, dan beberapa karakter lain di dalam string itu. Penggunaan fitur ini, serialisasi fungsi kita sekarang kelihatan seperti ini:

function serialize (o) if type(o) == "number" then io.write(o) elseif type(o) == "string" then io.write(string.format("%q", o)) else ... end

12.1.1 Tabel Penyimpanan Tanpa Siklus

Tugas berikut kami (dan lebih keras) adalah menyimpan tabel. Ada beberapa jalan untuk melakukannya, menurut pembatasan apa yang kita asumsikan tentang struktur tabel. Tidak ada algoritma tunggal yang sesuai dengan semua kasus. Tabel sederhana tidak hanya memerlukan algoritma yang lebih sederhana, tetapi hasil file dapat lebih estetis juga. Usaha pertama kita sebagai berikut:

function serialize (o) if type(o) == "number" then io.write(o) elseif type(o) == "string" then io.write(string.format("%q", o)) elseif type(o) == "table" then function serialize (o) if type(o) == "number" then io.write(o) elseif type(o) == "string" then io.write(string.format("%q", o)) elseif type(o) == "table" then

end io.write("}\n")

else error("cannot serialize a " .. type(o)) end end

Di samping kesederhanaannya, fungsi tersebut mengerjakan suatu pekerjaan yang layak. Bahkan penanganan tabel bersarang (yaitu, tabel di dalam tabel lain), sepanjang struktur tabel adalah suatu pohon (yaitu, tidak ada pembagian sub-tabel dan tidak ada siklus). Suatu peningkatan estetis kecil akan menjadi masukan berkala tabel bersarang; anda dapat mencobanya sebagai suatu latihan. (Saran: Tambahkan suatu parameter ekstra untuk menyerialisasi dengan lekukan string.)

Fungsi sebelumnya mengasumsikan bahwa semua kunci di dalam suatu tabel adalah identifier yang sah. Jika suatu tabel mempunyai kunci numerik, atau kunci string yang bukan merupakan identifier sintaks Lua yang sah, maka kita dalam masalah. Suatu cara sederhana untuk memecahkan kesulitan ini adalah mengubah baris

io.write(" ", k, " = ")

menjadi

io.write(" [") serialize(k) io.write("] = ")

Dengan perubahan ini , kita tingkatkan pertahanan dari fungsi kita, dengan mengorbankan estetis dari hasil file. Bandingkan:

-- result of serialize{a=12, b='Lua', key='another "one"'} -- first version {

a = 12,

b = "Lua", key = "another \"one\"", } -- second version {

["a"] = 12, ["b"] = "Lua", ["key"] = "another \"one\"",

Kita dapat meningkatkan hasil ini dengan pengujian untuk masing-masing kasus apakah memerlukan kurung besar; dan lagi, kita akan meninggalkan peningkatan ini sebagai suatu latihan.

12.1.2 Penyimpanan Tabel dengan Siklus

Untuk menangani tabel dengan topologi umum (yaitu, dengan siklus dan pembagian sub- tabel) kita memerlukan suatu pendekatan berbeda. Pembangun tidak bisa menghadirkan tabel seperti itu , maka kita tidak akan menggunakannya. Untuk menghadirkan siklus kita memerlukan nama, jadi fungsi berikutnya akan memperoleh argumentasi nilai yang namanya disimpan lebih. Lebih dari itu, kita harus tetap melacak nama dari tabel yang telah disimpan, untuk menggunakannya kembali ketika kita mendeteksi suatu siklus. Kita akan menggunakan suatu tabel ekstra untuk pelacakan ini. Tabel ini akan mempunyai tabel sebagai indices dan namanya seperti nilai-nilai yang dihubungkan.

Kita akan tetap membatasi tabel yang ingin kita simpan yakni hanya string atau number sebagai kunci. Fungsi berikut menyerialisasi tipe dasar ini, mengembalikan hasil :

function basicSerialize (o) if type(o) == "number" then return tostring(o) else -- assume it is a string return string.format("%q", o) end end

Fungsi berikutnya mengerjakan pekerjaan berat itu. Parameter saved adalah tabel yang tetap melacak tabel telah disimpan:

function save (name, value, saved) saved = saved or {} -- initial value io.write(name, " = ") if type(value) == "number" or type(value) == "string" then

io.write(basicSerialize(value), "\n") elseif type(value) == "table" then if saved[value] then -- value already saved? io.write(saved[value], "\n") -- use its previous name else saved[value] = name -- save name for next time io.write("{}\n") -- create a new table for k,v in pairs(value) do -- save its fields

string.format("%s[%s]", name, basicSerialize(k))

local fieldname =

save(fieldname, v, saved) end end else error("cannot save a " .. type(value)) end end

Sebagai contoh, jika kita membangun suatu tabel seperti

a = {x=1, y=2; {3,4,5}} a[2] = a -- cycle a = {x=1, y=2; {3,4,5}} a[2] = a -- cycle

kemudian pemanggilan save('a', a) akan menyimpannya sebagai berikut :

a = {} a[1] = {} a[1][1] = 3 a[1][2] = 4 a[1][3] = 5

a[2] = a a["y"] = 2 a["x"] = 1 a["z"] = a[1]

(Order yang nyata dari tugas ini boleh berlainan, tergantung pada suatu tabel traversal. Meskipun demikian, algoritma memastikan bahwa simpul manapun sebelumnya diperlukan di dalam suatu definisi baru yang telah didefinisikan.)

Jika kita ingin menyimpan beberapa nilai dengan pembagian komponen, kita dapat membuat panggilan ke save menggunakan tabel saved yang sama. Sebagai contoh, jika kita menciptakan dua tabel berikut,

a = {{"one", "two"}, 3}

b = {k = a[1]}

dan menyimpannya sebagai berikut,

save('a', a) save('b', b)

hasilnya tidak akan mempunyai komponen umum :

a = {} a[1] = {} a[1][1] = "one" a[1][2] = "two" a[2] = 3

b = {} b["k"] = {} b["k"][1] = "one" b["k"][2] = "two"

Bagaimanapun, jika kita menggunakan tabel saved yang sama untuk masing-masing panggilan ke save,

local t = {} save('a', a, t) save('b', b, t)

kemudian hasil akan berbagi komponen umum : kemudian hasil akan berbagi komponen umum :

b = {} b["k"] = a[1]

Seperti halnya di dalam Lua, ada beberapa alternatif lain. Di antaranya, kita dapat menyimpan suatu nilai tanpa memberinya suatu nama global (contohnya, potongan itu membangun suatu nilai lokal dan mengembalikannya); kita dapat menangani fungsi (dengan membangun suatu tabel yang menghubungkan masing-masing fungsi ke namanya) dan lain-lain. Lua memberimu kuasa; anda bangun mekanisme itu.

Latihan

1. Perhatikan kode berikut, apa keluaran darinya ? A = {}

B = “C” C = “B”

D={ [A] = {B = C}, [B] = {[C] = B}, [C] = {[A] = A}} print(D.C[“B”])

2. By default, table.sort menggunakan < untuk membandingak elemen array, sehingga ia hanya dapat mengurut array dari bilangan dan array dari string. Tuliskan suatu fungsi pembanding yang mengizinkan table.sort menngurut array dari tipe campuran. Pada array terurut, seluruh nilai array dikelompokan menurut tipenya. Untuk setiap kelompok, bilangan dan string harus di- urut seperti biasa, dan tipe-tipe lain diurut sembarang tetapi dengan cara yang konsisten. Ujilah fungsi pada array seperti berikut:

{{}, {}, {}, “”, “a”, “b”, “c”, 1, 2, 3, -100, 1.1, function() end, function() end, false, false, true}

3. Fungsi ‘print’ mengkonversi seluruh argument-argumennya menjadi string, memisahkan mereka dengan karakter tab, dan menampilkannya, sekaligus berpindah baris.Tulis sebuah fungsi yang mengembalikan sebagai suatu string, sehingga sebagai berikut:

Sprint(“Hi”, {}, nil)

mengembalikan: “Hi\ttable: 0x484048\tnil\n”

yang jika dicetak menhasilkan keluaran seperti berikut: Hi table: 0x484048 nil

4. Pada ring.lua, metode RotateL hanya memutar obyeknya satu elemen ke kiri, seperti tampak berikut ini:

-- Rotates self to the left: function Methods:RotateL() if #self > 0 then self.Pos = OneMod(self.Pos + 1, #self) end end

Tulis ulang kode di atas dengan menggunakan argument numeric optional (default 1) dan memutar obyek dengan banyak elemen tersebut. (Tidak diperlukan baik suatu loop atau suatu rekursi recursion.)

5. Tuliskan suatu pembangkit iterasi, SortedPairs, yang berperilaku seperti halnya pasangan, kecuali bahwa ia berfungsi dengan pasangan nilai-kunci yang terurut berdasarkan kunci. Gunakan fungso ‘CompAll’ untuk mengurut kunci.