Jumat, 05 Maret 2010

Pemrograman Dengan Array

Array merupakan jenis struktur data yang sangat dasar dan sangat penting. Teknik pengolahan array merupakan teknik pemrograman yang paling penting yang kita harus kuasai. Dua jenis teknik pengolahan array -- pencarian dan pengurutan -- akan dibahas kemudian. Bagian ini akan memperkenalkan beberapa ide dasar pengolahan array secara umum.

Dalam banyak hal, pengolahan array berarti menggunakan operasi yang sama kepada setiap elemen di dalam array. Biasanya sering dilakukan dengan perulangan for. Perulangan untuk mengolah semua elemen dalam array A dapat ditulis dalam bentuk :

// lakukan inisialiasi yang diperlukan sebelumnya
for (int i = 0; i < A.length; i++) {
. . . // proses A[i]
}

Misalnya, A adalah array dengan tipe double[]. Misalnya kita ingin menjumlah semua nilai dalam array tersebut. Algoritma umum untuk melakukannya adalah :

Mulai dengan 0;
Tambah A[0]; (proses elemen pertama di dalam A)
Tambah A[1]; (proses elemen kedua di dalam A)
.
.
.
Tambah A[ A.length - 1 ]; (proses elemen terakhir di dalam A)

Dengan menggunakan pengetahuan yang kita telah pelajari tentang perulangan, kita bisa ubah algoritma di atas menjadi bentuk perulangan for seperti berikut:

double jumlah; // Jumlah nilai di dalam A
jumlah = 0; // Mulai dengan 0
for (int i = 0; i < A.length; i++)
jumlah += A[i]; // tambah A[i] ke dalam jumlah untuk i = 0, 1, ..., A.length - 1

Lihat bahwa kondisi kelanjutan "i < A.length" menyatakan bahwa nilai i terakhir yang akan diolah adalah A.length - 1 yaitu elemen terakhir dalam array. Ingat bahwa kita menggunakan "<" bukan "<=" karena dengan "<=" komputer akan memberikan kesalahan indeks di luar batas.

Pada akhirnya, nanti Anda akan bisa membuat perulangan seperti di atas di luar kepala. Kita akan lihat beberapa contohnya. Di sini perulangan akan menghitung banyaknya elemen di dalam array A yang nilainya kurang dari nol :

int hitung; // Untuk menghitung elemen
hitung = 0; // Mulai dengan nol
for (int i = 0; i < A.length; i++) {
if (A[i] < 0.0) // Jika elemen ini kurang dari nol
hitung++; // tambah hitung dengan 1
}
// Di sini nilai "hitung" adalah banyaknya elemen yang kurang dari 0.

Kita bisa mengganti "A[i] < 0.0" jika kita ingin menghitung banyaknya elemen di dalam array yang memiliki sifat tertentu. Variasinya akan memiliki tema yang sama. Misalnya kita ingin menghitung banyaknya elemen di dalam array A yang sama dengan elemen sesudahnya. Elemen setelah A[i] adalah A[i+1], sehingga kita bisa mengganti klausa if dengan "if (A[i] == A[i+1])". Akan tetapi tunggu dulu : Tes ini tidak bisa digunakan apabila A[i] adalah elemen terakhir dalam array, karena tidak ada lagi array sesudahnya. Komputer akan menolak pernyataan ini. Sehingga kita harus berhenti satu elemen sebelum array terakhir, sehingga menjadi

int hitung = 0;
// lihat kondisi for berubah dibandingkan dengan contoh sebelumnya
for (int i = 0; i < A.length - 1; i++) {
if (A[i] == A[i+1])
hitung++;
}

Masalah umum lainnya adalah mencari nilai terbesar di dalam array A. Strateginya adalah lihat semua isi array, catat nilai terbesar saat itu. Kita akan simpan nilai terbesar yang kita temui dalam variabel maks. Pada saat kita melihat elemen array satu per satu, kapanpun kita melihat nilai elemen tersebut lebih besar dari maks kita akan mengganti nilai maks dengan nilai yang lebih besar tersebut. Setelah semua elemen array diproses, maka maks merupakan nilai elemen terbesar di dalam array tersebut. Pertanyaannya adalah, apa nilai awal maks? Salah satu kemungkinannya adalah mulai dengan nilai maks sama dengan A[0], baru kemudian melihat isi elemen array lainnya mulai dengan A[1]. Misalnya,

double maks = A[0]; // nilai maks berisi elemen array pertama
for (int i = 1; i < A.length; i++) { // i mulai dari elemen kedua
if (A[i] > maks)
max = A[i];
}
// Di sini maks berisi nilai elemen array yang paling besar

(Ada masalah yang lebih penting di sini. Java membolehkan array memiliki panjang nol. Artinya bahkan A[0] pun tidak ada di dalam array, sehingga memanggil A[0] akan menghasilkan kesalahan indeks keluar batas. Akan tetapi array biasanya array dengan panjang nol biasanya sesuatu yang kita ingin hindarkan dalam kehidupan sehari-hari. Lagian apa artinya mencari nilai terbesar di dalam suatu array yang panjangnya nol?)

Contoh terakhir dari operasi array, misalnya kita ingin mengkopi suatu array. Untuk mengkopi array A, tidak cukup untuk menggunakan perintah

double[] B = A;

karena perintah ini tidak membuat objek array baru. Yang dibuat di sini adalah variabel baru yang merujuk pada objek yang sama dengan A. (Sehingga perubahan yang terjadi pada A[i] akan juga menyebabkan B[i] berubah). Untuk membuat array baru yang merupakan kopi dari array A, kita harus membuat objek array baru, dan mengkopi isinya satu per satu dari array A ke array baru, sehingga

// Buat objek array baru, yang panjangnya sama dengan panjang A
double[] B = new double[A.length];

for (int i = 0; i < A.length; i++)
B[i] = A[i]; // Kopi setiap elemen dari A ke B

Mengkopi nilai dari satu array ke array yang lain adalah operasi umum sehingga Java memiliki subrutin untuk melakukannya, yaitu System.arraycopy(), yang merupakan subrutin anggota statik dari kelas standar System. Deklarasinya memiliki bentuk seperti :

public static void arraycopy(Object arraySumber, int indeksAwalSumber,
Object arrayTujuan, int indeksAwalTujuan, int jumlah)

di mana arraySumber dan arrayTujuan bisa berbentuk array dengan tipe apapun. Nilai akan dikopi dari arraySumber ke arrayTujuan. jumlah adalah berapa banyak elemen yang akan dikopi. Nilai akan dikopi dari arraySumber mulai dari posisi indeksAwalSumber dan akan disimpan pada arrayTujuan mulai dari posisi indeksAwalTujuan. Misalnya kita akan mengkopi array A, maka kita bisa menggunakan perintah

double B = new double[A.length];
System.arraycopy( A, 0, B, 0, A.length );

Suatu tipe array, misalnya double[] adalah tipe Java biasa, sehingga kita bisa menggunakannya seperti tipe-tipe Java lainnya. Termasuk juga digunakan sebagai parameter formal di dalam suatu subrutin. Juga bisa digunakan sebagai tipe keluaran suatu fungsi. Misalnya, kita bisa menulis fungsi yang membuat kopi array dengan tipe double sebagai berikut :

double[] kopi( double[] sumber ) {
// Membuat dan mengembalikan kopi array sumber
// Jika sumber null, maka kembalikan null
if ( sumber == null )
return null;
double[] kpi; // Kopi array sumber
kpi = new double[sumber.length];
System.arraycopy( sumber, 0, kpi, 0, sumber.length );
return kpi;
}

Rutin main() memiliki parameter dengan tipe String[] yang merupakan array String. Ketika sistem memanggil rutin main(), string di dalam array ini adalah parameter dari baris perintah. Jika kita menggunakan konsol, user harus mengetikkan perintah untuk menjalankan program. User bisa menambahkan input tambahan dalam perintah ini setelah nama program yang akan dijalankan.

Misalnya, jika kelas yang memiliki rutin main() bernama programKu, maka user bisa menjalankan kelas tersebut dengan perintah "java programKu" di konsol. Jika kita tulis dengan "java programKu satu dua tiga", maka parameter dari baris perintahnya adalah "satu", "dua", dan "tiga". Sistem akan memasukkan parameter-parameter ini ke dalam array String[] dan memberikan array ini pada rutin main().

Berikut ini adalah contoh program sederhana yang hanya mencetak parameter dari baris perintah yang dimasukkan oleh user.

public class CLDemo {
public static void main(String[] args) {
System.out.println("Anda memasukkan " + args.length
+ " parameter dari baris perintah");
if (args.length > 0) {
System.out.println("Parameter tersebut adaah :");
for (int i = 0; i < args.length; i++)
System.out.println(" " + args[i]);
}
} // akhir main()
} // akhir kelas CLDemo

Perhatikan bahwa parameter args tidak mungkin null meskipun tidak ada parameter yang dimasukkan. Jika tidak ada parameter dari baris perintah yang dimasukkan, maka panjang array ini adalah nol.

Hingga sekarang, contoh yang telah diberikan adalah bagaimana mengolah array dengan mengakses elemennya secara berurutan (sequential access). Artinya elemen-elemen array diproses satu per satu dalam urutan dari awal hingga akhir. Akan tetapi salah satu keuntungan array adalah bahwa array bisa digunakan untuk mengakses elemennya secara acak, yaitu setiap elemen bisa diakses kapan saja secara langsung.

Misalnya, kita ambil contoh suatu masalah yang disebut dengan masalah ulang tahun: Misalnya ada N orang di dalam suatu ruangan. Berapa kemungkinan dua orang di dalam ruangan tersebut memiliki ulang tahun yang sama (yang dilahirkan pada tanggal dan bulan yang sama, meskipun tahunnya berbeda)? Kebanyakan orang salah menerka jawabannya. Sekarang kita lihat dengan versi masalah yang berbeda: Misalnya kita memilih orang secara acak dan menanyakan ulang tahunnya. Berapa orang yang Anda harus tanya untuk mendapatkan hari ulang tahun yang sama dengan orang sebelumnya?

Tentunya jawabannya akan tergantung pada faktor yang bersifat acak, akan tetapi kita bisa simulasikan dengan program komputer dan menjalankan beberapa kali hingga kita tahu berapa kira-kira orang harus dicek.

Untuk mensimulasikan percobaan ini, kita harus mencatat semua ulang tahun yang kita sudah tanyakan. Ada 365 kemungkinan hari ulang tahun (Kita abaikan sementara tahun kabisat). Untuk setiap kemungkinan hari ulang tahun, kita perlu tahu, apakah hari ulang tahun tersebut telah digunakan? Jawabannya adalah nilai boolean true atau false. Untuk menyimpan data ini, kita bisa gunakan array dari 365 nilai boolean:

boolean[] sudahDitanya;
sudahDitanya = new boolean[365];

Tanggal-tanggal pada satu tahun dinomori dari 0 hingga 364. Nilai sudahDitanya[i] akan bernilai true jika orang yang kita tanya berulang tahun pada hari tersebut. Pada awalnya semua nilai pada array sudahDitanya[i] bernilai false. Ketika kita memilih satu orang dan menanyakan hari ulang tahunnya, misalnya i, kita akan mengecek terlebih dahulu apakah sudahDitanya[i] bernilai true. Jika tidak maka orang ini adalah orang kedua dengan ulang tahun yang sama. Artinya kita sudah selesai.

Jika sudahDitanya[i] bernilai false, maka belum ada orang sebelum ini yang memiliki hari ulang tahun tersebut. Kita akan ganti sudahDitanya[i] dengan true, kemudian kita akan tanyakan lagi kepada orang lain, dan begitu seterusnya hingga semua orang di dalam ruangan ditanyakan.

static void masalahUlangTahun() {
// Melakukan simulasi dengan memilih seseorang secara acak
// dan mengecek hari ulang tahunnya. Jika hari ulang tahunnya
// sama dengan orang yang pernah kita tanya sebelumnya,
// hentikan program dan laporkan berapa orang yang sudah dicek

boolean[] sudahDitanya;
// Untuk mencatat ulang tahun yang sudah ditanyakan
// Nilai true pada sudahDitanya[i] berarti orang lain
// sudah ada yang berulang tahun pada hari i

int hitung;
// Jumlah orang yang sudah pernah ditanya

sudahDitanya = new boolean[365];
// Awalnya, semua nilai adalah false

hitung = 0;

while (true) {
// Ambil ulang tahun secara acak dari 0 hingga 364
// Jika ulang tahun telah ditanya sebelumnya, keluar
// Jika tidak catat dalam array

int ultah; // Ulang tahun seseorang
ultah = (int)(Math.random()*365);
hitung++;
if ( sudahDitanya[ultah] )
break;
sudahDitanya[ultah] = true;
}

System.out.println("Ulang tahun yang sama ditemukan setelah menanyakan "
+ hitung + " orang.");

} // akhir masalahUlangTahun()

Subrutin ini menggunakan fakta bahwa array boolean yang baru dibuat memiliki seluruh elemen yang bernilai false. Jika kita ingin menggunakan array yang sama untuk simulasi kedua, kita harus mereset ulang semua elemen di dalamnya menjadi false kembali dengan perulangan for

for (int i = 0; i < 365; i++)
sudahDitanya[i] = false;

Array paralel adalah menggunakan beberapa array dengan indeks yang sama. Misalnya kita ingin membuat beberapa kolom secara paralel -- array x di kolom pertama, array y di kolom kedua, array warna di kolom ketiga, dan seterusnya. Data untuk baris ke-i bisa didapatkan dari masing-masing array ini. Tidak ada yang salah dengan cara ini, akan tetapi cara ini berlawanan dengan filosofi berorientasi objek yang mengumpulkan data yang berhubungan di dalam satu objek. Jika kita mengikuti aturan seperti ini, amaka kita tidak harus membayangkan hubungan data yang satu dan yang lainnya karena semua data akan dikelompokkan di dalam satu tempat.

Misalnya saya menulis kelas seperti

class DataString {
// Data dari salah satu pesan
int x,y; // Posisi pesan
Color warna; // Warna pesan
}

Untuk menyimpan data dalam beberapa pesan, kita bisa menggunakan array bertipe DataString[], yang kemudian dideklarasikan sebagai variabel instansi dengan nama data sehingga

DataString[] data;

Isi dari data bernilai null hingga kita membuat array baru, misalnya dengan

data = new DataString[JUMLAH_PESAN];

Setelah array ini dibuat, nilai setiap elemen array adalah null. Kita ingin menyimpan data di dalam objek yang bertipe DataString, akan tetapi tidak ada objek yang dibuat. Yang kita sudah buat hanyalah kontainernya saja. Elemen di dalamnya berupa objek yang belum pernah kita buat. Untuk itu elemen di dalamnya bisa kita buat dengan perulangan for seperti :

for (int i = 0; i < JUMLAH_PESAN; i++)
data[i] = new DataString();

Sekarang kita bisa mengambil data setiap pesan dengan data[i].x, data[i].y, dan data[i].warna.

Terakhir berkaitan dengan pernyataan switch. Misalnya kita memiliki nilai bulan dari 0 hingga 11, yang melambangkan bulan dalam satu tahun dari Januari hingga Desember. Kita ingin mencetaknya di layar, dengan perintah

switch (bulan) {
case 0:
bulanString = "Januari";
break;
case 1:
bulanString = "Februari";
break;
case 2:
bulanString = "Maret";
break;
case 3:
bulanString = "April";
break;
.
.
.
case 11:
bulanString = "Desember";
break;
default:
bulanString = "Salah bulan";
}

Kita bisa mengganti keseluruhan perintah switch tersebut dengan menggunakan array, misalnya dengan array namaBulan yang dideklarasikan sebagai berikut :

static String[] namaBulan = { "Januari", "Februari", "Maret",
"April", "Mei", "Juni", "Juli", "Agustus", "September",
"Oktober", "November", "Desember" };

Kemudian kita bisa ganti keseluruhan switch di atas dengan

bulanString = namaBulan[bulan];

Tidak ada komentar:

Posting Komentar