• Programming

    Game of Life

    Kemaren waktu nyari info tentang “map”nya Java di stackoverflow.com, di samping kanan websitenya ada bagian “Hot Network Questions” yg isinya adalah pertanyaan2 dari berbagai bidang (dan dari berbagai website), ada yg dari superuser.com, math.stackexchange.com, codegolf.stackexchange.com, english.stackexchange.com, codereview.stackexchange.com, dll (detailnya liat langsung aja di link ‘Hot Network Questions’ di atas).

    Nah, dari code review, ada yg nanya tentang Conway’s Game of Life. Penasaran, akhirnya malah bikin sendiri deh, pake javascript aja yg gampang. Informasinya saya dapet dari wikipedia. Dunia dari game of life adalah sel kotak yang hidup dalam grid dua dimensi yang tak berhingga luasnya. Tiap sel dari grid cuma punya salah satu status, yaitu hidup atau mati. Tiap kali perubahan memiliki hukum seperti ini:

    1. Sel hidup yang punya tetangga hidup kurang dari 2 akan menjadi mati.
    2. Sel hidup yang punya tetangga hidup 2 atau 3 tetap akan hidup.
    3. Sel hidup yang punya tetangga hidup lebih dari 3 akan mati.
    4. Sel yang mati tapi punya 3 tetangga hidup akan menjadi sel hidup.

    Langsung aja, ini source-code nya:

    [code lang=”HTML” title=”Source-code: conways.html”]
    <html>
    <head>
    <title>Conway’s Game of Life</title>
    <style type="text/css">
    table.cgol { border-collapse:collapse; border: 1px solid #333; border-spacing: 0px; }
    table.cgol td { text-align:center; width:10px; height:10px; border: 1px solid #333; overflow: hidden; }
    </style>
    <script type="text/javascript" src="conways.js"></script>
    <script type="text/javascript">
    var gol;
    var lop;
    function $(id) { return document.getElementById(id); }
    function getRandomInt(min, max) { return Math.floor(Math.random()*(max-min+1)+min); }
    function isOdd(num) { return (num % 2) == 1; }

    function draw() { $(‘output’).innerHTML = gol.draw(); $(‘stp’).innerHTML = gol.steps; }
    function doCreate() {
    if (typeof lop != "undefined") clearInterval(lop);
    clearInterval(lop);
    gol = new CGOL($(‘we’).value,$(‘ha’).value,"gol");
    draw();
    }
    function doRandom() { gol.initRandom(); draw(); }
    function doStep() { gol.step(); draw(); }
    function doStepClick() { if (typeof lop != "undefined") clearInterval(lop); clearInterval(lop); doStep(); }
    function doLoop() { if (typeof lop != "undefined") clearInterval(lop); lop = setInterval(doStep, 150); }
    </script>
    </head>
    <body>
    <h2>Conway’s Game of Life</h2>
    <div>
    Width <input type="text" id="we" value="50">
    Height <input type="text" id="ha" value="50">
    <input type="button" value="Create" onclick="doCreate()">
    <input type="button" value="Random" onclick="doRandom()">
    <input type="button" value="Step" onclick="doStepClick()">
    <input type="button" value="Auto" onclick="doLoop()">
    </div>
    <br>
    Steps : <span id="stp"></span>
    <br>
    <div id="output"></div>
    </body>
    </html>
    [/code]

    [code lang=”Javascript” title=”Source-code: conways.js”]
    function CGOL (wid, hig, nam) {
    this.width = wid;
    this.height = hig;
    this.cells = [];
    this.varname = nam; // little hack for board designer
    this.steps = 0;

    this.init();
    }

    CGOL.prototype.alive = function(x,y) { return this.cells[(this.width*y)+x]; }
    CGOL.prototype.toggle = function(x,y) { this.cells[(this.width*y)+x] = !this.alive(x,y); }

    CGOL.prototype.countLiveNeighboursSimple = function(x,y) {
    /*
    * The simplest strategy is simply to assume that every cell outside the array
    * is dead. This is easy to program, but leads to inaccurate results when the
    * active area crosses the boundary.
    */
    var live = 0;
    var atas = false;
    var bawa = false;
    var kiri = false;
    var kana = false;
    if (x>0) kiri = true;
    if (x<this.width-1) kana = true;
    if (y>0) atas = true;
    if (y<this.height-1) bawa = true;

    if (kiri) {
    if (this.alive(x-1,y)) live++;
    if (atas) { if (this.alive(x-1,y-1)) live++; }
    if (bawa) { if (this.alive(x-1,y+1)) live++; }
    }
    if (kana) {
    if (this.alive(x+1,y)) live++;
    if (atas) { if (this.alive(x+1,y-1)) live++; }
    if (bawa) { if (this.alive(x+1,y+1)) live++; }
    }
    if (atas) { if (this.alive(x,y-1)) live++; }
    if (bawa) { if (this.alive(x,y+1)) live++; }
    return live;
    }

    CGOL.prototype.countLiveNeighbours = function(x,y) {
    /*
    * A more sophisticated trick is to consider the left and right edges of the
    * field to be stitched together, and the top and bottom edges also, yielding
    * a toroidal array. The result is that active areas that move across a field
    * edge reappear at the opposite edge. Inaccuracy can still result if the
    * pattern grows too large, but at least there are no pathological edge
    * effects.
    */
    var live = 0;
    var atas = false;
    var bawa = false;
    var kiri = false;
    var kana = false;
    if (x>0) kiri = true;
    if (x<this.width-1) kana = true;
    if (y>0) atas = true;
    if (y<this.height-1) bawa = true;

    if (kiri) {
    if (this.alive(x-1,y)) live++;
    if (atas) { if (this.alive(x-1,y-1)) live++; } else { if (this.alive(x-1,this.height-1)) live++; }
    if (bawa) { if (this.alive(x-1,y+1)) live++; } else { if (this.alive(x-1,0)) live++; }
    } else {
    if (this.alive(this.width-1,y)) live++;
    if (atas) { if (this.alive(this.width-1,y-1)) live++; } else { if (this.alive(this.width-1,this.height-1)) live++; }
    if (bawa) { if (this.alive(this.width-1,y+1)) live++; } else { if (this.alive(this.width-1,0)) live++; }
    }
    if (kana) {
    if (this.alive(x+1,y)) live++;
    if (atas) { if (this.alive(x+1,y-1)) live++; } else { if (this.alive(x+1,this.height-1)) live++; }
    if (bawa) { if (this.alive(x+1,y+1)) live++; } else { if (this.alive(x+1,0)) live++; }
    } else {
    if (this.alive(0,y)) live++;
    if (atas) { if (this.alive(0,y-1)) live++; } else { if (this.alive(0,this.height-1)) live++; }
    if (bawa) { if (this.alive(0,y+1)) live++; } else { if (this.alive(0,0)) live++; }
    }
    if (atas) { if (this.alive(x,y-1)) live++; } else { if (this.alive(x,this.height-1)) live++; }
    if (bawa) { if (this.alive(x,y+1)) live++; } else { if (this.alive(x,0)) live++; }
    return live;
    }

    CGOL.prototype.step = function() {
    var newcell = [];
    for (var y=0; y<this.height; y++) {
    for (var x=0; x<this.width; x++) {
    var t = this.countLiveNeighbours(x,y);
    newcell.push(this.alive(x,y));
    var i = newcell.length-1;
    //Any live cell with fewer than two live neighbours dies, as if caused by under-population.
    if (this.alive(x,y) && (t<2)) newcell[i]=false;
    //Any live cell with two or three live neighbours lives on to the next generation.
    if (this.alive(x,y) && t>=2 && t<=3) newcell[i]=true;
    //Any live cell with more than three live neighbours dies, as if by overcrowding.
    if (t>3) newcell[i]=false;
    //Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
    if (!this.alive(x,y) && t==3) newcell[i]=true;
    }
    }
    this.cells = [];
    for (var z=0; z<newcell.length; z++) {
    this.cells.push(newcell[z]);
    }
    this.steps++;
    }

    CGOL.prototype.init = function() {
    this.cells = [];
    for (var x=0; x<this.width*this.height; x++) {
    this.cells.push(false);
    }
    this.steps = 0;
    }

    CGOL.prototype.initRandom = function() {
    var min = 0;
    var max = 9;
    this.cells = [];
    for (var x=0; x<this.width*this.height; x++) {
    var c = (((Math.floor(Math.random() * (max – min + 1) + min)) % 4) == 1);
    this.cells.push(c);
    }
    this.steps = 0;
    }

    CGOL.prototype.draw = function() {
    var tbl = "<table class=’cgol’>";
    for (var y=0; y<this.height; y++) {
    tbl += "<tr>";
    for (var x=0; x<this.width; x++) {
    var warna = "#000";
    if (this.alive(x,y)) warna = "#0f0";
    tbl += "<td style=’background-color:" + warna + "; ‘ "+
    "onclick=’"+this.varname+".toggle("+x+","+y+"); draw();’></td>";
    }
    tbl += "</tr>";
    }
    tbl += "</table>";
    return tbl;
    }
    [/code]

    Seperti keterangan pada wikipedia, ada metode paling simple untuk menghitung jumlah “tetangga hidup” yaitu dengan menganggap sel diluar array adalah sel mati. Metode ini dianggap sangat tidak akurat. Di conways.js, saya sertakan metode ini dengan nama “countLiveNeighboursSimple”.

    Lalu ada metode lainnya yaitu yang menganggap bahwa bagian kiri dan kanan grid adalah menyatu, begitu pula bagian atas dan bawah. Kalau di visualisasikan, akan terbentuk torodial. Ini yang jadi default cara menghitung “tetangga hidup” dalam conways.js.

  • Programming

    Optimasi Query

    Beberapa hari lalu saya diminta untuk membuat ulang program penghitung harga rata-rata pada tiap akhir hari perdagangan di back-office. Program sebelumnya selalu menghitung pembelian terlebih dahulu baru mengurangi balance saat penjualan, tentu saja hasilnya akan berbeda jika perhitungan dilakukan menurut waktu terjadinya transaksi.

    Yang pertama kali saya lakukan adalah membuat Stored Procedure yang kerjanya mengambil data transaksi, lalu mengupdate/insert (data disimpan per tanggal) tabel saldo yg berisi Saldo, AvgPrice, dll, terurut menurut waktu terjadinya transaksi. Stored procedure yang saya buat berjalan lambat, karena pada tiap transaksi stored procedure akan menulis ke table yang berarti akan ada aktivitas harddisk untuk menulis, dan dalam 1 hari bisa ada ribuan (antara 2 sampai 4 ribu) frekuensi transaksi.

    Stored procedure saya ubah dengan menambahkan variabel dengan tipe “table”, saya melakukan semua insert ke variabel tabel, lalu di akhir proses saya lakukan bulk insert ke tabel aslinya. Hasilnya stored procedure berjalan 10x lebih cepat.

    Table temporary di memory memang cepat, tapi ada kasus lain yang ternyata kebalikannya. Saya punya 1 table yang akan saya bulk update berdasarkan sebuah multi-statement table-valued function (fungsi yang outputnya berupa table). Begitu saya coba jalankan, query update itu berjalan lebih dari 13 menit. Googling sebentar dapet informasi dari MSDN :

    Joining to a multistatement table valued function in a FROM clause is possible, but can give poor performance. SQL Server is unable to use all the optimized techniques against some statements that can be included in a multistatement function, resulting in a suboptimal query plan. To obtain the best possible performance, whenever possible use joins between base tables instead of functions.

    Saya ubah lagi querynya dengan menyimpan terlebih dahulu hasil fungsi ke tabel fisik dengan menggunakan select into, lalu melakukan bulk-update dan terakhir menghapus kembali tabel penyimpanan sementara itu. Hasilnya query berjalan 39 detik untuk mengupdate 54.827 row data.

    Selalu cari jalan tercepat, jangan puas hanya dengan hasil yang benar.

  • Programming,  Teori

    Bikin Program Pencatat Koleksi

    Sebagai seorang pencinta buku, sudah tentu buku yang saya punya cukup banyak, dan pencatatan menjadi cukup penting. Selain mengoleksi buku, saya juga punya koleksi koin dan koleksi film. Sudah lama saya mencari program yang tugasnya hanya mencatat benda koleksi seseorang, tapi tidak pernah dapat yang benar2 pamungkas. Karena saya memprioritaskan satu program dengan banyak tipe koleksi, berarti program itu harus bisa mengelola data koleksi buku dan film, padahal field yang digunakan dalam dua koleksi itu sangat jauh berbeda (apalagi kalau koleksi koin).

    Sesudah istri saya meminta saya untuk mendokumentasikan seluruh tas miliknya (istri saya senang mengoleksi tas), saya jadi berpikir untuk bikin sendiri program pengelola koleksi itu. Akhirnya tercetus sebuah ide dalam benak saya untuk membuat koleksi dengan field dinamis, user bisa mendefinisikan sendiri field2 apa saja yang mau disimpan. Atau dalam istilah OOP, si user bisa mendefinisikan property apa saja yang terdapat dalam sebuah objek koleksi. Dan untuk mengakomodir user2 malas seperti saya, bisa disiapkan template untuk berbagai macam koleksi yang bisa diedit.

    Karena koleksi semacem ini biasanya cuma dipake oleh 1 orang, maka yang paling gampang pake SQLite.

    Ini dia struktur table nya:

    [table]KOLEKSI[attr colspan=”2″]
    ID,Auto Number
    Nama,Nama Koleksi
    Catatan,”Catatan koleksi (tanggal dimulai, dll)”
    BarisAkhir,Nomor baris yang terakhir dipakai (pada table BARIS)
    [/table]

    [table]KOLOM[attr colspan=”2″]
    ID,Auto Number
    Koleksi_ID,Nomor ID dari table KOLEKSI
    Nama,Nama field
    Tipe,”Tipe data field (string, numeric, date)”
    Urutan,Urutan posisi field
    Referensi,Referensi nilai. Untuk tipe field yang isinya sudah ditentukan dari daftar
    [/table]

    [table]BARIS[attr colspan=”2″]
    ID,Auto Number
    Koleksi_ID,Nomor ID dari table KOLEKSI
    Kolom_ID,Nomor ID dari table KOLOM
    Nilai,Isi field
    NoBaris,”Nomor urut data (tidak boleh dobel, sekuen bisa terputus apabila ada penghapusan data)”
    [/table]

    [table]LAMPIRAN[attr colspan=”2″]
    ID,Auto Number
    Koleksi_ID,Nomor ID dari table KOLEKSI
    NoBaris,Nomor urut data. Satu nomor urut bisa mempunyai lebih dari satu lampiran
    NamaFile,Nama file lampiran
    Berkas,Isi file lampiran
    [/table]

    Contoh datanya kira2 seperti ini:

    [table caption=”KOLEKSI”]ID,Nama,Catatan,BarisAkhir
    1,Koleksi Tas,-,2
    [/table]

    [table caption=”KOLOM”]ID,Koleksi_ID,Nama,Tipe,Urutan,Referensi
    1,1,Merk,string,1,Charles & Keith|Gucci|Channel
    2,1,Warna,string,2,
    3,1,Harga,numeric,3,
    4,1,Tanggal beli,date,4,
    [/table]

    [table caption=”BARIS”]ID,Koleksi_ID,Kolom_ID,Nilai,NoBaris
    1,1,1,Charles & Keith,1
    2,1,2,Hijau Pupus,1
    3,1,3,630000,1
    4,1,4,2011-03-24,1
    5,1,1,Gucci,2
    6,1,2,Merah,2
    7,1,3,2100000,2
    8,1,4,2012-01-19,2
    [/table]

    [table caption=”LAMPIRAN”]ID,Koleksi_ID,NoBaris,NamaFile,Berkas
    1,1,1,foto-1.jpg,–blob–
    2,1,2,tas.png,–blob–
    [/table]

    Nantinya akan ditampilkan seperti ini:

    [table colalign=”center|left|left|right|center”]No,Merk,Warna,Harga,Tanggal beli
    1,Charles & Keith,Hijau Pupus,630.000,2011-03-24
    2,Gucci,Merah,2.100.000,2012-01-19
    [/table]

    Jadi deh program pencatat koleksi yang dinamis cuma pake 4 table. Tinggal pinter2 aja nge-query. Kalau perlu bikin view untuk setiap koleksi yang ada, misal untuk koleksi ID=1 viewnya “view_koleksi_1”, dst.

  • Programming

    Scripting (lagi)

    Kemaren lagi iseng pengen tau nilai kurs USD-IDR, karena dari tiap website beda2, akhirnya bikin script BASH untuk ngambil info kurs dari beberapa website. Bekelnya cuma WGET, CAT, GREP, CUT. Ini source-code nya:

    [code lang=”BASH” title=”Source-code: kurs.sh”]
    #!/bin/bash

    # Info Kurs Ekstraktor
    # Agustus 2013 oleh sedjat1
    # Lisensi : GNU GPL v.3.0

    merah="\e[31m"
    hijau="\e[32m"
    kuning="\e[33m"
    biru="\e[34m"
    magenta="\e[35m"
    cyan="\e[36m"
    normal="\e[39m"

    mtu="$cyan"
    svc="$kuning"

    echo -e "Info kurs ${mtu}USD${normal}"

    echo -n -e "${svc}Bank Mandiri ${normal}= "
    wget -q -Osrc.txt "http://www.bankmandiri.co.id/resource/kurs.asp"
    readarray kurs < <(cat src.txt | grep ‘USD</option>’ | cut -d"\"" -f2)
    te="Beli "
    for item in "${kurs[@]}"
    do
    usd=`echo "${item/$’\n’/}" | cut -d. -f1`
    echo -n -e "${te}${usd}"
    te=" ; Jual ${merah}"
    done
    echo -n -e "${normal} ; "
    cat src.txt | grep ‘Last Updated:’ | cut -d"<" -f2 | cut -d">" -f2
    rm src.txt

    echo -n -e "${svc}Yahoo! Finance ${normal}= "
    wget -q -Osrc.txt "http://download.finance.yahoo.com/d/quotes.csv?s=USDIDR=X&f=sl1d1t1c1ohgv&e=.csv"
    usd=`cat src.txt | cut -d, -f2 | cut -d. -f1`
    tgl=`cat src.txt | cut -d"\"" -f4`
    jam=`cat src.txt | cut -d"\"" -f6`
    echo -e "${merah}${usd} ${normal}@ $tgl $jam"
    rm src.txt

    echo -n -e "${svc}Google Finance ${normal}= "
    wget -q –no-check-certificate -Osrc.txt "https://www.google.com/finance?q=USDIDR"
    usd=`cat src.txt | grep ‘1 USD =’ | cut -d. -f1 | cut -d">" -f2`
    jam=`cat src.txt | grep ‘class=time’ | cut -d">" -f2 | cut -d"<" -f1`
    echo -e "${merah}${usd} ${normal}@ $jam"
    rm src.txt
    [/code]

    Hasilnya begini:
    kurs.sh

  • Programming

    SeRem

    Ini paket program yang baru saya selesaikan versi beta-nya. SeRem adalah kependekan dari Semi Remote. Kenapa Semi Remote? Karena paket program yang saya buat ini bukan benar2 me-remote komputer dari jauh, melainkan menjalankan beberapa fungsi yang telah didaftarkan dulu sebelumnya. Fungsi2 tersebut bisa di-trigger melalui email.

    Kenapa saya buat SeRem. Karena kadang kala saya perlu melakukan sesuatu di komputer kantor saat saya tidak berada di kantor. Tetapi karena di kantor banyak lapisan security nya, saya tidak bisa (dan tidak mau) berbuat banyak, saya tidak mau membuka celah keamanan sedikitpun.

    Sepengetahuan saya, bisa saja saya menjalankan TeamViewer. Tapi kok rasanya kurang pas, karena terus terang saja koneksi internet saya amat sangat terbatas. Dengan menggunakan SeRem, saya bisa menggunakan email client untuk mengirim perintah ke komputer lain dengan “ongkos” yang minim.

    Perintah dikirim lewat email, dan hasilnya pun diterima lewat email, karena “remote” nya tidak secara langsung (real-time) maka saya namakan “Semi Remote”. Awalnya saya mau buat dengan melakukan query ke sebuah website (bikin sendiri) untuk mengambil request dan mengepost hasil eksekusinya, tapi terlalu banyak yang harus saya kerjakan, karena saya programmer males akhirnya jadi deh yg sekarang ini. Awalnya pengen dikasih nama M2M-RC (Mail To Mail Remote Control), tapi karena “lebih Indonesia” kata2 SeRem ya udah jadinya SeRem aja.

    Pada versi beta ini, saya cuma mendaftarkan 3 perintah: Screenshot, File List, Youtube Downloader. Masing2 perintah mempunyai file eksekusi-nya sendiri2, makanya saya katakan SeRem sebagai Paket Program. Ini dia isinya:

    1. Progam GETMAIL. Ini adalah program console yang fungsinya untuk mengambil email dari server pop3, dan menyimpannya dalam format txt. Sebenarnya saya bisa bikin sendiri, tapi males, udah ada yang bikin bagus.
    2. Progam sendEmail yang saya buat sendiri. Kebalikan dari GETMAIL (pop3) program console ini mengirim email (smtp) untuk menjawab/response dari perintah yang diterima. Yang ini sudah saya buat lama (waktu saya masih rajin)
    3. Youtube-dl.py yaitu script python untuk mendownload video dari beberapa server video terkenal termasuk Youtube, Metacafe, dll. Tentu saja program ini membutuhkan instalasi Python.
    4. Program Screenshot (dibuat pake AutoIt3) tentu saja fungsinya untuk mengambil screenshot komputer dan mengirimkan hasilnya lewat email.
    5. Program FileList (dibuat pake AutoIt3) sesuai namanya untuk mengambil list file di folder tertentu dan mengirimkan hasilnya lewat email.
    6. Program yang saya namai Commander (dibuat pake AutoIt3) yang fungsinya untuk mengecek email dan memparsing isinya, lalu menjalankan program sesuai instruksi dari dalam email tsb.

    Nantinya koleksi program untuk menjalankan perintahnya akan ditambah, dan tentunya SeRem akan menjadi lebih komplit lagi. Karena ini kumpulan program yang sebagian besar jalan di console (dengan output yang tidak menarik) dan sebagian lagi memang tanpa GUI, maka saya tidak bisa memberikan screenshotnya.