Tugas PPB Pertemuan 6
Nathanael Valen Susilo
5025231099
Aplikasi Kalkulator Sederhana
Kode
@Composable
fun CalculatorScreen(modifier: Modifier = Modifier) {
var expression by remember { mutableStateOf("") }
var result by remember { mutableStateOf("") }
Column(
modifier = modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.padding(16.dp)
) {
// Display Area
Column(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(bottom = 32.dp),
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.End
) {
if (result.isNotEmpty()) {
Text(
text = expression,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.End,
modifier = Modifier.fillMaxWidth()
)
}
Text(
text = if (result.isNotEmpty()) result else expression.ifEmpty { "0" },
style = MaterialTheme.typography.displayLarge,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.End,
modifier = Modifier.fillMaxWidth()
)
}
// Buttons Grid
val buttons = listOf(
listOf("C", "(", ")", "/"),
listOf("7", "8", "9", "*"),
listOf("4", "5", "6", "-"),
listOf("1", "2", "3", "+"),
listOf("0", ".", "DEL", "=")
)
buttons.forEach { row ->
Row(
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
row.forEach { label ->
CalculatorButton(
label = label,
modifier = Modifier.weight(1f),
onClick = {
when (label) {
"C" -> { expression = ""; result = "" }
"DEL" -> {
if (result.isNotEmpty()) {
expression = ""; result = ""
}
else if (expression.isNotEmpty()) {
expression = expression.dropLast(1)
}
}
"=" -> if (expression.isNotEmpty()) result = evaluate(expression)
else -> {
if (result.isNotEmpty()) {
expression = if (label in "+-*/") result + label else label
result = ""
} else {
expression += label
}
}
}
}
)
}
}
}
}
}
CalculatorScreen memisahkan area visual menjadi dua zona utama menggunakan komponen Column. Area atas pada layar berfungsi sebagai layar output yang bersifat dinamis; ia menampilkan ekspresi saat ini dan hasil perhitungan dengan perataan teks ke kanan (Alignment.End) untuk meniru tampilan aplikasi kalkulator umum. Di bagian bawah, tombol-tombol antarmuka disusun secara sistematis dalam sebuah kisi (grid) menggunakan perulangan forEach pada baris dan kolom, di mana penggunaan Modifier.weight(1f) memastikan setiap tombol memiliki ukuran yang proporsional dan responsif terhadap lebar layar perangkat.Fungsi ini mengandalkan manajemen state untuk mengontrol siklus hidup data input. Alur dimulai ketika pengguna menekan tombol, yang memicu pembaruan pada variabel
expression. Jika tombol sama dengan (=) ditekan, sistem menjalankan fungsi evaluasi untuk mengisi variabel result, yang secara otomatis mengubah tampilan layar dari mode input ke mode hasil. Logika ini juga menangani transisi cerdas, seperti tombol DEL yang mampu mendeteksi apakah harus menghapus satu karakter atau membersihkan seluruh layar, serta kemampuan untuk langsung menyambung hasil perhitungan terakhir dengan operator baru untuk perhitungan berkelanjutan.@Composable
fun CalculatorButton(label: String, modifier: Modifier, onClick: () -> Unit) {
val isOp = label in "+-*/="
Button(
onClick = onClick,
modifier = modifier.aspectRatio(1f),
shape = CircleShape,
colors = ButtonDefaults.buttonColors(
containerColor = if (isOp) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant,
contentColor = if (isOp) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurfaceVariant
),
contentPadding = PaddingValues(0.dp)
) {
Text(text = label, fontSize = 22.sp, fontWeight = FontWeight.Bold)
}
}
CalculatorButton adalah komponen UI reusable yang berfungsi menciptakan tombol interaktif berbentuk lingkaran sempurna dengan tata letak dan pewarnaan yang adaptif. Fungsi ini menggunakan logika internal untuk membedakan identitas tombol; jika label berupa operator matematika, maka warna tombol akan berubah menjadi lebih kontras menggunakan skema primary, sedangkan untuk angka akan menggunakan warna netral guna memberikan hierarki visual yang jelas bagi pengguna. Selain aspek estetika yang responsif melalui penggunaan aspect ratio tetap, fungsi ini juga bertugas menghubungkan interaksi klik pengguna langsung ke logika perhitungan utama melalui callback onClick.fun evaluate(expr: String): String = try {
val tokens = Regex("""[0-9.]+|[+\-*/()]""").findAll(expr).map { it.value }
val values = Stack<Double>()
val ops = Stack<String>()
val prec = mapOf("+" to 1, "-" to 1, "*" to 2, "/" to 2)
fun apply() {
val b = values.pop(); val a = values.pop()
values.push(when (ops.pop()) {
"+" -> a + b; "-" -> a - b; "*" -> a * b; "/" -> a / b else -> 0.0
})
}
tokens.forEach { t ->
when {
t == "(" -> ops.push(t)
t == ")" -> { while (ops.peek() != "(") apply(); ops.pop() }
t in prec -> {
while (ops.isNotEmpty() && ops.peek() != "(" && (prec[ops.peek()] ?: 0) >= prec[t]!!) apply()
ops.push(t)
}
else -> values.push(t.toDouble())
}
}
while (ops.isNotEmpty()) apply()
val res = values.pop()
if (res % 1 == 0.0) res.toLong().toString() else "%.4f".format(Locale.US, res).trimEnd('0').trimEnd('.')
} catch (e: Exception) { "Error" }
Fungsi evaluate mengimplementasikan algoritma Shunting-yard yang dimodifikasi untuk langsung menghitung hasil ekspresi matematika menggunakan dua struktur data Stack. Berikut adalah penjelasan detail logikanya:
1. Tokenisasi dan Persiapan
Proses dimulai dengan memecah string input menjadi unit-unit kecil (token) menggunakan Regex. Regex tersebut memisahkan antara angka (termasuk desimal) dan operator atau tanda kurung. Selain itu, didefinisikan sebuah Map bernama prec untuk menentukan presedensi atau derajat kekuatan operator, di mana perkalian (*) dan pembagian (/) memiliki nilai lebih tinggi (2) dibandingkan penjumlahan (+) dan pengurangan (-) yang bernilai 1.
2. Mekanisme Dua Stack
Algoritma ini bekerja dengan dua struktur data utama:
Values Stack: Menyimpan angka-angka dalam format
Double.Ops Stack: Menyimpan operator dan tanda kurung yang sedang menunggu untuk diproses.
Fungsi
apply(): Setiap kali fungsi ini dipanggil, ia akan mengambil dua angka teratas darivaluesdan satu operator teratas dariops, menghitung hasilnya, lalu memasukkan kembali hasil tersebut ke dalamvalues.
3. Logika Iterasi Token
Setiap token diproses satu per satu dengan aturan berikut:
Angka: Langsung dimasukkan ke dalam
valuesstack.Tanda Kurung Buka
(: Dimasukkan ke dalamops.Tanda Kurung Tutup
): Memaksa prosesapply()terus menerus hingga ditemukan tanda kurung buka yang sesuai.Operator: Sebelum operator baru masuk ke
ops, algoritma mengecek apakah operator di puncak stack memiliki presedensi yang lebih tinggi atau sama. Jika ya, operator lama dieksekusi terlebih dahulu menggunakanapply()sebelum operator baru disimpan. Ini memastikan aturan matematika (seperti perkalian didahulukan daripada penjumlahan) terpenuhi.
Setelah semua token dibaca, sisa operator yang masih ada di dalam stack akan dieksekusi hingga habis. Hasil akhir diambil dari values. Fungsi ini juga melakukan pengecekan format jika hasilnya adalah bilangan bulat (seperti 5.0), maka diubah menjadi teks 5. Jika berupa desimal, diformat hingga 4 angka di belakang koma dengan menghapus nol yang tidak perlu. Jika terjadi kesalahan input (seperti pembagian nol atau kurung tidak lengkap), blok try-catch akan mengembalikan pesan "Error".

Komentar
Posting Komentar