PL Praktikum Pemrograman LanjutLembar Kerja Mahasiswa · Doni Winarso
Universitas Muhammadiyah Riau · Fakultas Ilmu Komputer · Teknik Informatika

Modul Praktikum Pemrograman Lanjut

Lembar Kerja Mahasiswa (LKM) untuk 16 pertemuan praktikum pemrograman fungsional dengan Python. Isi setiap lembar kerja langsung di halaman ini, lalu unduh sebagai PDF atau Word untuk dikumpulkan.

Mata KuliahPraktikum Pemrograman Lanjutan
Kode / SKS / Semester40401502 · 1 SKS · Semester 5
Rumpun Mata KuliahMata Kuliah Prodi (MKP)
CPLCPL 5 — Menguasai konsep teoretis Ilmu Komputer/Informatika untuk mendesain dan menyimulasikan aplikasi multi-platform.
CPL 6 — Menerapkan pemikiran logis, kritis, sistematis, dan inovatif dalam pengembangan atau implementasi IPTEK.
CPL 9 — Menganalisis, merancang, membuat, dan mengevaluasi antarmuka pengguna dan aplikasi interaktif.
Tim Pengembang RPSReny Medikawati Taufiq, S.Kom., MT · Yulia Fatma, S.Kom., M.Cs · Sunanto, S.Kom., M.Kom · Fitri Handayani, S.T., M.Kom · Fauzan Azim, S.Kom., M.Kom
Koordinator RMKEvans Fuad, S.Kom., M.Eng., Ph.D
KaprodiDr. Rahmad Gunawan, S.Kom., MT
Perangkat LunakPython 3 (disarankan 3.10 atau lebih baru), Visual Studio Code + ekstensi Python, terminal/Command Prompt
Perangkat KerasKomputer minimum Core i5

Identitas Mahasiswa

Identitas ini otomatis dicantumkan di setiap lembar kerja yang Anda unduh.

Petunjuk Penggunaan LKM

  1. Baca Tujuan dan Dasar Teori sebelum praktikum dimulai.
  2. Kerjakan Langkah Praktikum secara berurutan di VS Code. Ketik sendiri kodenya, jangan hanya menyalin, lalu jalankan dengan python nama_file.py. Bandingkan keluaran Anda dengan komentar # hasil pada contoh.
  3. Tulis jawaban Tugas pada kotak jawaban. Jawaban tersimpan otomatis di peramban (browser) perangkat ini.
  4. Lampirkan tangkapan layar (screenshot) hasil eksekusi bila diminta. Tempelkan di file Word hasil unduhan.
  5. Unduh LKM dengan tombol Unduh PDF / Unduh Word di setiap pertemuan, lalu kumpulkan sesuai instruksi asisten.
  6. Jawaban hanya tersimpan di browser ini. Jika berganti komputer di lab, unduh dulu file Word Anda sebelum keluar.
Aturan gaya fungsional selama praktikum: kecuali diminta lain, tulis fungsi murni (tanpa print di dalam fungsi, tanpa mengubah variabel global, tanpa mengubah isi list argumen). Gunakan print hanya di bagian program utama untuk menampilkan hasil.
Etika praktikum: Kerjakan tugas secara mandiri. Kode yang identik antar mahasiswa (termasuk nama variabel dan komentar) akan diperiksa ulang oleh asisten. Anda boleh bertanya ke asisten atau teman tentang konsep, bukan menyalin kode.
✓ Tersimpan
Pendahuluan

Peta Pertemuan & Penilaian

Setiap pertemuan berlangsung 100 menit dan memenuhi satu Sub-CPMK. Bobot asesmen di bawah mengikuti RPS.

Capaian Pembelajaran Mata Kuliah (CPMK)

KodeKeteranganCPLBobot
CPMK 069Mampu menerapkan program menggunakan paradigma pemrograman fungsionalCPL 510%
CPMK 070Mampu menerapkan program menggunakan notasi fungsionalCPL 510%
CPMK 071Mampu menerapkan program menggunakan ekspresi fungsionalCPL 640%
CPMK 072Mampu menerapkan paket struktur data (List dan Tree) pada pemrograman fungsionalCPL 940%
Jumlah100%

Sub-CPMK

KodeKemampuan akhir
069.1Menerapkan perbedaan paradigma pemrograman fungsional, prosedural, dan berorientasi objek
069.2Menerapkan paradigma pemrograman berbasis fungsional
070.1Menerapkan pemrograman menggunakan notasi fungsional
070.2Menerapkan pemrograman berbasis notasi fungsional dasar
071.1Menerapkan pemrograman fungsional berbasis ekspresi dasar dan ekspresi kondisional
071.2Menerapkan pemrograman fungsional berbasis ekspresi rekursif dan ekspresi lambda
072.1Menerapkan pemrograman fungsional berbasis list
072.2Menerapkan pemrograman fungsional berbasis tree (pohon)

Rencana 16 Pertemuan

Ke-Sub-CPMKBahan KajianAsesmenBobot*
1069.1Paradigma pemrograman fungsionalNon-asesmen0%
2069.1Paradigma fungsional vs prosedural vs OOPTugas100%
3070.1Notasi dasar fungsionalNon-asesmen0%
4070.1Notasi fungsionalTugas100%
5071.1Ekspresi dasar fungsionalNon-asesmen0%
6071.1Ekspresi kondisionalNon-asesmen0%
7071.1Ekspresi rekursif fungsionalNon-asesmen0%
8071.2Ekspresi lambdaUTS100%
9071.2Fungsi dengan type bentukanNon-asesmen0%
10072.1List dengan elemen sederhana dan karakterNon-asesmen0%
11072.1List integer dan himpunan (set)Non-asesmen0%
12072.1Resume rekursi terhadap listNon-asesmen0%
13072.1Struktur data listNon-asesmen0%
14072.2Tree untuk pohon n-ary dan binerNon-asesmen0%
15072.2Binary search dan balanced searchNon-asesmen0%
16072.2Ujian pemrograman fungsional list dan treeUAS100%

*Bobot asesmen dihitung terhadap CPMK induknya. Contoh: Tugas Pertemuan 2 menyumbang 100% dari nilai CPMK 069; UTS menyumbang 100% dari nilai CPMK 071. Pertemuan non-asesmen tetap wajib dikerjakan karena materinya diujikan pada Tugas, UTS, dan UAS.

Cara Menghitung Nilai

Nilai CPMK 069 = Tugas Pertemuan 2
Nilai CPMK 070 = Tugas Pertemuan 4
Nilai CPMK 071 = UTS (Pertemuan 8)
Nilai CPMK 072 = UAS (Pertemuan 16)

Nilai Akhir = 0,10×CPMK069 + 0,10×CPMK070 + 0,40×CPMK071 + 0,40×CPMK072

Contoh:
  Tugas P2 = 85, Tugas P4 = 80, UTS = 75, UAS = 82
  Nilai Akhir = 8,5 + 8,0 + 30,0 + 32,8 = 79,3

Setiap LKM dinilai dengan skala 0–100 menggunakan rubrik yang tercantum di bagian akhir lembar kerja.

Pendahuluan

Studi Kasus: Data Nilai Mahasiswa

Sebagian besar pertemuan memakai data yang sama, yaitu modul data_mhs.py. Dengan data yang sudah dikenal, Anda bisa fokus pada cara berpikir fungsional, bukan pada memahami data baru setiap minggu.

Struktur Folder Kerja

Buat satu folder kerja dan simpan semua file praktikum di dalamnya:

praktikum_pl/
├── data_mhs.py      ← data studi kasus (dibuat sekali, dipakai terus)
├── p01.py           ← file praktikum pertemuan 1
├── p02.py
├── ...
├── tipe.py          ← type bentukan (Pertemuan 9)
├── daftar.py        ← struktur data list (Pertemuan 13)
└── pohon.py         ← struktur data tree (Pertemuan 14–15)

Isi data_mhs.py

Data disimpan dalam tuple (bukan list) agar tidak dapat diubah secara tidak sengaja. Ini adalah kebiasaan pertama dalam pemrograman fungsional: data bersifat tetap (immutable).

# data_mhs.py
DATA_MHS = (
    {"nim": "2304001", "nama": "Andi Saputra",    "prodi": "TI", "sks": 20, "nilai": 82},
    {"nim": "2304002", "nama": "Bunga Lestari",   "prodi": "SI", "sks": 22, "nilai": 91},
    {"nim": "2304003", "nama": "Citra Dewi",      "prodi": "TI", "sks": 18, "nilai": 55},
    {"nim": "2304004", "nama": "Dimas Pratama",   "prodi": "TI", "sks": 24, "nilai": 74},
    {"nim": "2304005", "nama": "Eka Putri",       "prodi": "SI", "sks": 20, "nilai": 58},
    {"nim": "2304006", "nama": "Fajar Nugroho",   "prodi": "TI", "sks": 21, "nilai": 88},
    {"nim": "2304007", "nama": "Gilang Ramadhan", "prodi": "SI", "sks": 19, "nilai": 79},
)

Aturan Penilaian yang Dipakai

Nilai angkaHuruf mutuBobotKeterangan
≥ 85A4,0Lulus
75 – 84,99B3,0Lulus
60 – 74,99C2,0Lulus
50 – 59,99D1,0Tidak lulus
< 50E0,0Tidak lulus

Seorang mahasiswa dinyatakan lulus jika nilainya ≥ 60.

Hasil yang Dapat Dipakai untuk Cek Mandiri

PertanyaanJawaban benar
Rata-rata nilai prodi TI74,75
Mahasiswa lulusAndi, Bunga, Dimas, Fajar, Gilang (5 orang)
Total SKS seluruh mahasiswa144
Nilai tertinggi91 (Bunga Lestari)
LKM Praktikum PL · Pertemuan 01

Paradigma Pemrograman Fungsional

Sub-CPMKSUB-CPMK 069.1 — Menerapkan perbedaan paradigma pemrograman fungsional, prosedural, dan berorientasi objek
CPMKCPMK 069
Bahan KajianParadigma pemrograman fungsional
MetodeCeramah, diskusi, praktik · 100 menit
AsesmenNon-asesmen · 0% (tetap wajib dikerjakan sebagai bekal Tugas Pertemuan 2)

A. Tujuan Praktikum

  1. Mahasiswa mampu menyiapkan lingkungan kerja Python dan VS Code.
  2. Mahasiswa mampu menjelaskan konsep dasar paradigma fungsional: fungsi murni, imutabilitas, dan fungsi sebagai nilai.
  3. Mahasiswa mampu membedakan kode bergaya imperatif dan bergaya fungsional untuk masalah yang sama.

B. Alat dan Bahan

C. Dasar Teori

Paradigma pemrograman adalah cara pandang dalam menyusun program. Tiga paradigma yang paling sering dijumpai:

ParadigmaFokusContoh bahasa
Prosedural (imperatif)Urutan perintah yang mengubah keadaan (state) program langkah demi langkahC, Pascal
Berorientasi objek (OOP)Objek yang membungkus data dan perilaku (method)Java, C#
FungsionalKomposisi fungsi matematis yang menghasilkan nilai, tanpa mengubah stateHaskell, Scala, F#, Elixir

Python adalah bahasa multi-paradigma: ketiganya bisa ditulis di Python. Konsep kunci paradigma fungsional:

KonsepArti
Fungsi murni (pure function)Hasil hanya bergantung pada argumen, dan tidak menimbulkan efek samping. Masukan sama selalu memberi keluaran sama.
Efek samping (side effect)Perubahan di luar fungsi: mengubah variabel global, mengubah isi list argumen, menulis file, mencetak ke layar.
ImutabilitasData tidak diubah setelah dibuat. Untuk "mengubah", buat data baru.
Fungsi kelas satu (first-class)Fungsi dapat disimpan di variabel, dikirim sebagai argumen, dan dikembalikan dari fungsi lain.
DeklaratifMenyatakan apa yang dihitung, bukan bagaimana langkah-langkahnya.

D. Langkah Praktikum

  1. Periksa instalasi Python. Buka terminal (Command Prompt atau terminal VS Code):
    python --version
    python -c "print('Halo Pemrograman Lanjut')"
  2. Siapkan folder kerja. Buat folder praktikum_pl, buka di VS Code (File → Open Folder), lalu buat file data_mhs.py sesuai bagian Studi Kasus.
  3. Imperatif vs fungsional. Buat file p01.py. Hitung jumlah kuadrat bilangan genap dari 1 sampai 10 dengan dua gaya:
    # p01.py
    angka = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    
    # Gaya imperatif: mengubah variabel total berulang kali
    total = 0
    for x in angka:
        if x % 2 == 0:
            total += x * x
    print("Imperatif :", total)          # hasil: 220
    
    # Gaya fungsional: satu ekspresi, tanpa variabel yang berubah
    total_f = sum(x * x for x in angka if x % 2 == 0)
    print("Fungsional:", total_f)        # hasil: 220
  4. Fungsi murni vs tidak murni. Tambahkan:
    diskon = 0.1                          # variabel global (state)
    
    def harga_akhir_impure(harga):
        return harga - harga * diskon     # bergantung pada variabel di luar fungsi
    
    def harga_akhir_pure(harga, diskon):
        return harga - harga * diskon     # hanya bergantung pada argumen
    
    print(harga_akhir_impure(100_000))    # hasil: 90000.0
    diskon = 0.5
    print(harga_akhir_impure(100_000))    # hasil: 50000.0  -> masukan sama, hasil berbeda!
    print(harga_akhir_pure(100_000, 0.1)) # hasil: 90000.0  -> selalu sama
  5. Efek samping pada list. Amati perbedaan fungsi yang mengubah argumen dan fungsi yang membuat data baru:
    def tambah_mutasi(daftar, x):
        daftar.append(x)          # efek samping: list milik pemanggil ikut berubah
        return daftar
    
    def tambah_murni(daftar, x):
        return daftar + [x]       # membuat list baru, list asli tetap
    
    a = [1, 2]
    b = tambah_murni(a, 3)
    print(a, b)                   # hasil: [1, 2] [1, 2, 3]
    c = tambah_mutasi(a, 3)
    print(a, c)                   # hasil: [1, 2, 3] [1, 2, 3]  -> a ikut berubah
  6. Imutabilitas dengan tuple.
    t = (1, 2, 3)
    # t[0] = 10                   # hapus tanda # lalu jalankan: TypeError
    t2 = (10,) + t[1:]            # "mengubah" = membuat tuple baru
    print(t, t2)                  # hasil: (1, 2, 3) (10, 2, 3)
  7. Fungsi sebagai nilai (first-class function).
    def kuadrat(x):
        return x * x
    
    f = kuadrat                            # fungsi disimpan dalam variabel
    print(f(7))                            # hasil: 49
    print(list(map(kuadrat, [1, 2, 3])))   # hasil: [1, 4, 9]
    
    def terapkan_dua_kali(fungsi, x):      # fungsi menerima fungsi lain
        return fungsi(fungsi(x))
    
    print(terapkan_dua_kali(kuadrat, 3))   # hasil: 81
  8. Gunakan data studi kasus. Ambil nama mahasiswa yang lulus dengan gaya fungsional:
    from data_mhs import DATA_MHS
    
    lulus = tuple(m["nama"] for m in DATA_MHS if m["nilai"] >= 60)
    print(lulus)
    # hasil: ('Andi Saputra', 'Bunga Lestari', 'Dimas Pratama', 'Fajar Nugroho', 'Gilang Ramadhan')
Masalah umum: Jika muncul 'python' is not recognized, Python belum masuk PATH. Instal ulang dan centang Add python.exe to PATH, atau gunakan perintah py. Jika muncul ModuleNotFoundError: No module named 'data_mhs', pastikan terminal berada di folder praktikum_pl.

E. Tugas

20
1. Tuliskan versi Python di komputer Anda dan keluaran langkah 3 sampai 7. Lampirkan screenshot terminal.
20
2. Jelaskan dengan bahasa Anda sendiri mengapa harga_akhir_impure disebut tidak murni, sedangkan harga_akhir_pure disebut murni.
20
3. Tulis ulang kode imperatif berikut menjadi satu ekspresi bergaya fungsional:
hasil = [] / for x in range(1, 21): /   if x % 3 == 0: hasil.append(x * 10)
20
4. Tulis fungsi murni tambah_bonus(daftar_nilai, bonus) yang mengembalikan tuple baru berisi setiap nilai ditambah bonus, tanpa mengubah tuple asli. Tunjukkan bahwa tuple asli tidak berubah.
20
5. Sebutkan tiga contoh efek samping dalam program sehari-hari (misalnya pada aplikasi kasir) dan jelaskan mengapa efek samping membuat program lebih sulit diuji.

F. Indikator Ketercapaian

IndikatorSkor maks.
Lingkungan Python dan VS Code siap digunakan20
Mampu mengenali fungsi murni, efek samping, dan imutabilitas40
Mampu menulis ulang kode imperatif menjadi bergaya fungsional40

G. Refleksi

LKM Praktikum PL · Pertemuan 02

Paradigma Fungsional vs Prosedural vs Berorientasi Objek

Sub-CPMKSUB-CPMK 069.1 — Menerapkan perbedaan paradigma pemrograman fungsional, prosedural, dan berorientasi objek
CPMKCPMK 069
Bahan KajianParadigma pemrograman fungsional
MetodePraktik paradigma dasar, ceramah, dan diskusi · 100 menit
AsesmenTugas · 100% dari CPMK 069

A. Tujuan Praktikum

  1. Mahasiswa mampu menyelesaikan satu masalah dengan tiga paradigma: prosedural, OOP, dan fungsional.
  2. Mahasiswa mampu membandingkan kelebihan dan kekurangan setiap paradigma.
  3. Mahasiswa mampu memilih paradigma yang tepat untuk suatu kebutuhan.

B. Alat dan Bahan

C. Dasar Teori

AspekProseduralOOPFungsional
Unit utamaProsedur / langkahObjek (class)Fungsi
DataVariabel yang diubah-ubahAtribut objek, bisa berubahNilai tetap (immutable)
Perulanganfor/whilefor/while di dalam methodRekursi, map, filter, reduce, comprehension
Penggunaan ulangMemanggil prosedurPewarisan, polimorfismeKomposisi fungsi, fungsi tingkat tinggi
KekuatanSederhana, mudah diikutiMemodelkan entitas dunia nyataMudah diuji, aman untuk paralel, minim bug state

Masalah yang akan diselesaikan: hitung rata-rata nilai mahasiswa prodi TI dan daftar nama mahasiswa TI yang lulus (nilai ≥ 60).

D. Langkah Praktikum

  1. Versi prosedural. Buat p02.py:
    # p02.py
    from data_mhs import DATA_MHS
    
    def proses_prosedural(data):
        jumlah = 0
        banyak = 0
        lulus = []
        for m in data:
            if m["prodi"] == "TI":
                jumlah += m["nilai"]
                banyak += 1
                if m["nilai"] >= 60:
                    lulus.append(m["nama"])
        rata = jumlah / banyak if banyak else 0
        return rata, lulus
    
    print("Prosedural :", proses_prosedural(DATA_MHS))
    # hasil: (74.75, ['Andi Saputra', 'Dimas Pratama', 'Fajar Nugroho'])
  2. Versi OOP. Tambahkan:
    class Mahasiswa:
        def __init__(self, nim, nama, prodi, sks, nilai):
            self.nim, self.nama, self.prodi = nim, nama, prodi
            self.sks, self.nilai = sks, nilai
    
        def lulus(self):
            return self.nilai >= 60
    
    class Kelas:
        def __init__(self, daftar):
            self.daftar = daftar
    
        def filter_prodi(self, prodi):
            return Kelas([m for m in self.daftar if m.prodi == prodi])
    
        def rata_rata(self):
            return sum(m.nilai for m in self.daftar) / len(self.daftar) if self.daftar else 0
    
        def nama_lulus(self):
            return [m.nama for m in self.daftar if m.lulus()]
    
    kelas = Kelas([Mahasiswa(**m) for m in DATA_MHS])
    ti = kelas.filter_prodi("TI")
    print("OOP        :", (ti.rata_rata(), ti.nama_lulus()))
  3. Versi fungsional. Tambahkan:
    def dari_prodi(prodi):
        def cek(m):
            return m["prodi"] == prodi
        return cek                      # fungsi yang menghasilkan fungsi
    
    def ambil_nilai(m):
        return m["nilai"]
    
    def rata_rata(xs):
        xs = tuple(xs)
        return sum(xs) / len(xs) if xs else 0
    
    def proses_fungsional(data):
        ti = tuple(filter(dari_prodi("TI"), data))
        return (rata_rata(map(ambil_nilai, ti)),
                tuple(m["nama"] for m in ti if m["nilai"] >= 60))
    
    print("Fungsional :", proses_fungsional(DATA_MHS))
  4. Uji kemudahan pengujian. Fungsi murni mudah diuji dengan assert karena tidak bergantung pada state:
    contoh = ({"nama": "X", "prodi": "TI", "nilai": 50},
              {"nama": "Y", "prodi": "TI", "nilai": 70})
    assert proses_fungsional(contoh) == (60.0, ("Y",))
    assert rata_rata([]) == 0
    print("Semua uji lulus")
  5. Diskusikan dengan teman sebelah: versi mana yang paling mudah dibaca? Versi mana yang paling mudah ditambah fitur "rata-rata prodi SI"?

E. Tugas Dinilai

Sebuah toko alat tulis memiliki keranjang belanja berikut. Barang dengan harga satuan > Rp50.000 mendapat diskon 10%. Hitung total bayar.

KERANJANG = (
    ("Buku Python",    85_000,  2),   # (nama, harga satuan, jumlah)
    ("Pulpen",          5_000, 10),
    ("Flashdisk 32GB", 65_000,  1),
    ("Map Plastik",     3_000,  5),
)
# Jawaban benar untuk cek mandiri: total bayar = 276500
20
1. Selesaikan dengan paradigma prosedural (fungsi berisi perulangan dan variabel akumulator).
20
2. Selesaikan dengan paradigma OOP (class Barang dengan method subtotal() dan class Keranjang dengan method total()).
25
3. Selesaikan dengan paradigma fungsional: fungsi murni subtotal(barang) dan total(keranjang) tanpa perulangan for eksplisit dan tanpa variabel yang diubah. Sertakan minimal 2 assert untuk menguji fungsi Anda.
20
4. Buat tabel perbandingan ketiga solusi Anda: jumlah baris kode, ada/tidaknya variabel yang berubah, dan kemudahan pengujian.
15
5. Jika toko ingin menambah aturan "beli ≥ 10 barang yang sama diskon 5%", solusi mana yang paling mudah diubah? Jelaskan alasannya.

F. Indikator & Rubrik Penilaian

Indikator RPS: Mahasiswa mampu menerapkan konsep pemrograman fungsional dan membedakan paradigma pemrograman fungsional dengan prosedural dan object oriented programming.

KriteriaSangat baik (skor penuh)Skor maks.
Kebenaran programKetiga solusi menghasilkan total 27650040
Ketepatan paradigmaSolusi fungsional benar-benar murni, solusi OOP memakai class dengan tepat25
PengujianAda assert yang bermakna, termasuk kasus batas10
Analisis perbandinganPerbandingan objektif dan alasan logis25

G. Refleksi

LKM Praktikum PL · Pertemuan 03

Pemrograman Notasi Dasar Fungsional

Sub-CPMKSUB-CPMK 070.1 — Menerapkan pemrograman menggunakan notasi fungsional
CPMKCPMK 070
Bahan KajianPemrograman notasi dasar fungsional
MetodePraktikum notasi dasar fungsional, diskusi, dan ceramah · 100 menit
AsesmenNon-asesmen · 0%

A. Tujuan Praktikum

  1. Mahasiswa mampu menuliskan fungsi dalam notasi matematis (domain → kodomain) dan menerjemahkannya ke Python dengan type hint.
  2. Mahasiswa mampu menerapkan komposisi fungsi.
  3. Mahasiswa mampu menerapkan currying dan aplikasi parsial (partial application).

B. Alat dan Bahan

C. Dasar Teori

Dalam matematika, fungsi ditulis dengan signature (domain dan kodomain) dan definisi (aturan pemetaan). Python menuliskan signature dengan type hint. Type hint tidak dipaksakan saat program berjalan, tetapi memperjelas maksud fungsi dan dapat diperiksa dengan alat seperti mypy.

Notasi matematisNotasi PythonKeterangan
f : ℤ → ℤdef f(x: int) -> int:Signature
f(x) = x² + 1return x ** 2 + 1Definisi
g : ℤ × ℤ → ℤdef g(x: int, y: int) -> int:Fungsi dua argumen
(f ∘ g)(x) = f(g(x))compose(f, g)(x)Komposisi
h : ℤ → (ℤ → ℤ)def h(x: int) -> Callable[[int], int]:Currying: fungsi yang menghasilkan fungsi

Currying mengubah fungsi n-argumen menjadi rangkaian fungsi satu argumen: tambah(3, 4) menjadi tambah(3)(4). Aplikasi parsial mengisi sebagian argumen lebih dahulu sehingga terbentuk fungsi baru yang lebih khusus.

D. Langkah Praktikum

  1. Signature dan definisi. Buat p03.py:
    # p03.py
    from typing import Callable
    
    def f(x: int) -> int:
        """f : Z -> Z,  f(x) = x^2 + 1"""
        return x ** 2 + 1
    
    def g(x: int) -> int:
        """g : Z -> Z,  g(x) = 2x"""
        return 2 * x
    
    print(f(3), g(3))          # hasil: 10 6
    print(f(g(3)), g(f(3)))    # hasil: 37 20  -> urutan komposisi berpengaruh
    help(f)                    # menampilkan docstring
  2. Komposisi fungsi.
    def compose(f: Callable[[int], int],
                g: Callable[[int], int]) -> Callable[[int], int]:
        """(f o g)(x) = f(g(x))"""
        def h(x: int) -> int:
            return f(g(x))
        return h
    
    f_o_g = compose(f, g)
    g_o_f = compose(g, f)
    print(f_o_g(3), g_o_f(3))  # hasil: 37 20
  3. Currying.
    def tambah(x: int, y: int) -> int:                  # tambah   : Z x Z -> Z
        return x + y
    
    def tambah_c(x: int) -> Callable[[int], int]:       # tambah_c : Z -> (Z -> Z)
        def dalam(y: int) -> int:
            return x + y
        return dalam
    
    print(tambah(3, 4))        # hasil: 7
    print(tambah_c(3)(4))      # hasil: 7
    tambah5 = tambah_c(5)      # fungsi baru yang "mengingat" x = 5
    print(tambah5(10))         # hasil: 15
  4. Aplikasi parsial dengan functools.partial.
    from functools import partial
    
    def pangkat(basis: int, eksponen: int) -> int:
        return basis ** eksponen
    
    kuadrat = partial(pangkat, eksponen=2)
    kubik = partial(pangkat, eksponen=3)
    print(kuadrat(5), kubik(2))     # hasil: 25 8
  5. Fungsi dengan domain data studi kasus. Tulis signature dengan jelas:
    from data_mhs import DATA_MHS
    
    Mhs = dict                                  # alias tipe agar signature mudah dibaca
    
    def nilai_dari(m: Mhs) -> int:              # nilai_dari : Mhs -> Z
        return m["nilai"]
    
    def lulus(n: int) -> bool:                  # lulus : Z -> Bool
        return n >= 60
    
    status = compose(lulus, nilai_dari)         # status : Mhs -> Bool
    print([status(m) for m in DATA_MHS])
    # hasil: [True, True, False, True, False, True, True]
    Perhatikan bahwa compose di langkah 2 diberi type hint int, tetapi tetap berjalan untuk tipe lain karena Python tidak memaksakan type hint. Di sinilah peran mypy.
  6. (Opsional) Periksa type hint dengan mypy.
    pip install mypy
    mypy p03.py
    Tambahkan baris print(f("3")), jalankan mypy lagi, dan amati pesan kesalahannya.

E. Tugas

20
1. Tulis notasi matematis (signature + definisi) dan kode Python dengan type hint untuk: (a) luas lingkaran dari jari-jari, (b) konversi detik ke tuple (jam, menit, detik).
20
2. Diketahui p(x) = x + 3 dan q(x) = x * x. Hitung secara manual (p ∘ q)(4) dan (q ∘ p)(4), lalu buktikan dengan compose.
20
3. Buat versi curried dari fungsi nilai_akhir(tugas, uts, uas) = 0.3·tugas + 0.3·uts + 0.4·uas sehingga dapat dipanggil nilai_akhir_c(80)(70)(90). Tulis juga signature-nya.
20
4. Gunakan partial untuk membuat fungsi diskon10 dan diskon25 dari fungsi harga_diskon(persen, harga). Tampilkan hasil untuk harga 200.000.
20
5. Jelaskan perbedaan currying dan aplikasi parsial, serta manfaat keduanya dalam menyusun program.

F. Indikator Ketercapaian

Indikator RPS: Mahasiswa mampu menerapkan notasi dasar fungsional dalam konsep pemrograman berbasis fungsional.

IndikatorSkor maks.
Menuliskan signature dan definisi fungsi dengan benar40
Menerapkan komposisi fungsi20
Menerapkan currying dan aplikasi parsial40

G. Refleksi

LKM Praktikum PL · Pertemuan 04

Pemrograman Notasi Fungsional

Sub-CPMKSUB-CPMK 070.1 — Menerapkan pemrograman menggunakan notasi fungsional
CPMKCPMK 070
Bahan KajianPemrograman notasi fungsional
MetodePraktikum notasi fungsional, ceramah, dan diskusi · 100 menit
AsesmenTugas · 100% dari CPMK 070

A. Tujuan Praktikum

  1. Mahasiswa mampu menerjemahkan notasi pembentuk himpunan (set-builder) menjadi comprehension.
  2. Mahasiswa mampu menerapkan fungsi tingkat tinggi map, filter, dan reduce.
  3. Mahasiswa mampu menyusun pipeline pengolahan data dari komposisi fungsi.

B. Alat dan Bahan

C. Dasar Teori

Notasi fungsionalPythonHasil
{ x² | x ∈ S, x genap }[x**2 for x in S if x % 2 == 0]List hasil seleksi dan transformasi
map f [x₁, …, xₙ] = [f x₁, …, f xₙ]map(f, xs)Menerapkan f ke setiap elemen
filter p xs = [x | x ∈ xs, p x]filter(p, xs)Menyaring elemen yang memenuhi p
foldl ⊕ e [x₁, …, xₙ] = (…((e ⊕ x₁) ⊕ x₂) …) ⊕ xₙreduce(op, xs, e)Meringkas list menjadi satu nilai

map dan filter di Python 3 bersifat lazy: menghasilkan iterator yang baru dihitung ketika dibutuhkan. Bungkus dengan list() atau tuple() untuk melihat isinya. Iterator hanya dapat dibaca satu kali.

Pipeline adalah rangkaian fungsi yang dilalui data secara berurutan: data → saring → ubah → ringkas.

D. Langkah Praktikum

  1. Notasi himpunan ke comprehension. Buat p04.py:
    # p04.py
    S = range(1, 11)
    
    A = [x ** 2 for x in S if x % 2 == 0]            # { x^2 | x in S, x genap }
    print(A)                                          # hasil: [4, 16, 36, 64, 100]
    
    P = [(x, y) for x in (1, 2, 3) for y in "ab"]    # { (x, y) | x in {1,2,3}, y in {a,b} }
    print(P)   # hasil: [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), (3, 'b')]
    
    K = {x % 3 for x in S}                            # comprehension himpunan
    D = {x: x ** 3 for x in range(1, 4)}              # comprehension dictionary
    print(K, D)                                       # hasil: {0, 1, 2} {1: 1, 2: 8, 3: 27}
  2. map, filter, reduce.
    from functools import reduce
    import operator
    
    xs = (3, 8, 1, 6, 5)
    
    def kali_dua(x): return x * 2
    def ganjil(x): return x % 2 == 1
    
    print(list(map(kali_dua, xs)))         # hasil: [6, 16, 2, 12, 10]
    print(list(filter(ganjil, xs)))        # hasil: [3, 1, 5]
    print(reduce(operator.add, xs, 0))     # hasil: 23
    print(reduce(operator.mul, xs, 1))     # hasil: 720
    print(reduce(max, xs))                 # hasil: 8
    
    it = map(kali_dua, xs)
    print(list(it), list(it))              # hasil: [6, 16, 2, 12, 10] []  -> iterator habis
  3. Telusuri reduce secara manual. reduce(operator.add, (3, 8, 1, 6, 5), 0) dihitung sebagai ((((0+3)+8)+1)+6)+5 = 23.
  4. Pipeline dengan reduce.
    from functools import partial
    
    def pipe(nilai, *fungsi):
        """Alirkan nilai melewati fungsi-fungsi dari kiri ke kanan."""
        def langkah(acc, f):
            return f(acc)
        return reduce(langkah, fungsi, nilai)
    
    print(pipe(3, kali_dua, kali_dua, str))   # hasil: '12'
  5. Terapkan pada data mahasiswa.
    from data_mhs import DATA_MHS
    
    def ambil_nilai(m): return m["nilai"]
    def lulus(m): return m["nilai"] >= 60
    
    nilai_lulus = tuple(map(ambil_nilai, filter(lulus, DATA_MHS)))
    print(nilai_lulus)                         # hasil: (82, 91, 74, 88, 79)
    
    total_lulus = pipe(DATA_MHS,
                       partial(filter, lulus),
                       partial(map, ambil_nilai),
                       sum)
    print(total_lulus)                         # hasil: 414
    
    print(sum(nilai_lulus) / len(nilai_lulus)) # hasil: 82.8
  6. Bandingkan tiga notasi untuk hasil yang sama: comprehension, map/filter, dan pipeline. Tentukan mana yang paling mudah dibaca menurut Anda.

E. Tugas Dinilai

Gunakan DATA_MHS. Jangan memakai perulangan for sebagai pernyataan (comprehension diperbolehkan).

15
1. Tulis notasi himpunan dan comprehension untuk nama mahasiswa prodi TI yang mengambil SKS ≥ 20. (Cek: Andi, Dimas, Fajar)
15
2. Gunakan map untuk menghasilkan tuple berisi pasangan (nim, NAMA DALAM HURUF KAPITAL).
15
3. Gunakan reduce untuk menghitung total SKS seluruh mahasiswa. Tuliskan juga jejak (trace) perhitungannya. (Cek: 144)
25
4. Gunakan pipe untuk menghitung rata-rata nilai mahasiswa prodi SI. (Cek: 76.0)
20
5. Buat fungsi compose_all(*fs) yang menyusun banyak fungsi dari kanan ke kiri: compose_all(f, g, h)(x) = f(g(h(x))). Gunakan reduce dan sertakan contoh pemanggilannya.
10
6. Jelaskan apa yang dimaksud lazy evaluation pada map/filter dan tunjukkan contoh akibatnya.

F. Indikator & Rubrik Penilaian

Indikator RPS: Mahasiswa mampu menerapkan konsep pemrograman fungsional berbasis notasi fungsional.

KriteriaSangat baik (skor penuh)Skor maks.
Kebenaran hasilSemua jawaban sesuai nilai cek40
Ketepatan notasiNotasi himpunan, map/filter/reduce, dan pipeline dipakai sesuai fungsinya35
Gaya fungsionalTanpa perulangan eksplisit dan tanpa mutasi15
PenjelasanPenjelasan lazy evaluation tepat dan ada contoh10

G. Refleksi

LKM Praktikum PL · Pertemuan 05

Pemrograman Berbasis Ekspresi Dasar Fungsional

Sub-CPMKSUB-CPMK 071.1 — Menerapkan pemrograman fungsional berbasis ekspresi dasar dan ekspresi kondisional
CPMKCPMK 071
Bahan KajianPemrograman berbasis ekspresi dasar fungsional
MetodePraktikum ekspresi dasar fungsional · 100 menit
AsesmenNon-asesmen · 0% (materi diujikan pada UTS)

A. Tujuan Praktikum

  1. Mahasiswa mampu membedakan ekspresi (expression) dan pernyataan (statement).
  2. Mahasiswa mampu menggunakan ekspresi aritmetika, relasional, logika, dan string sesuai aturan presedensi.
  3. Mahasiswa mampu mengevaluasi fungsi dengan model substitusi.

B. Alat dan Bahan

C. Dasar Teori

Ekspresi adalah potongan kode yang dievaluasi menjadi nilai, misalnya 2 + 3, len("abc"), atau x > 0. Pernyataan adalah perintah yang melakukan aksi, misalnya x = 5, for, atau import. Program fungsional dibangun dari ekspresi: setiap fungsi idealnya cukup berupa satu ekspresi return.

Jenis ekspresiOperator / contoh
Aritmetika+ - * / // % **, divmod(17, 5)
Relasional== != < <= > >=, berantai: 0 <= x < 10
Logikaand or not (dievaluasi short-circuit)
String"ab" + "cd", "ab" * 3, f"{x:.2f}"
Pemanggilan fungsimax(3, 7), abs(-4)

Presedensi (dari tertinggi): ** → -x (unary) → * / // % → + - → relasional → not → and → or.

Model substitusi: karena fungsi murni tidak bergantung pada state, pemanggilan fungsi dapat dievaluasi dengan mengganti nama fungsi dengan definisinya. Sifat ini disebut referential transparency.

keliling(p, l) = 2 * (p + l)

keliling(3 + 1, 5)
→ keliling(4, 5)        ; argumen dievaluasi dulu
→ 2 * (4 + 5)           ; substitusi definisi
→ 2 * 9
→ 18

D. Langkah Praktikum

  1. Eksperimen di REPL. Ketik python di terminal, lalu coba satu per satu. Tebak dulu hasilnya sebelum menekan Enter:
    >>> 17 / 5, 17 // 5, 17 % 5, divmod(17, 5)
    >>> 2 + 3 * 4 ** 2
    >>> (2 + 3) * 4 ** 2
    >>> -2 ** 2
    >>> 2 ** 3 ** 2
    >>> x = 7
    >>> 0 < x < 10, x % 2 == 0 or x > 5
    >>> 0 or "bawaan", "" and 5, None or [] or "terakhir"
    >>> "UMRI" * 2 + "!", f"{22/7:.3f}"
  2. Fungsi satu ekspresi. Buat p05.py:
    # p05.py
    import math
    
    def nilai_akhir(tugas: float, uts: float, uas: float) -> float:
        return 0.3 * tugas + 0.3 * uts + 0.4 * uas
    
    def jarak(p: tuple, q: tuple) -> float:
        (x1, y1), (x2, y2) = p, q          # tuple unpacking
        return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
    
    def celcius_ke_fahrenheit(c: float) -> float:
        return c * 9 / 5 + 32
    
    def detik_ke_jam(d: int) -> tuple:
        return (d // 3600, d % 3600 // 60, d % 60)
    
    print(nilai_akhir(80, 70, 90))       # hasil: 81.0
    print(jarak((0, 0), (3, 4)))         # hasil: 5.0
    print(celcius_ke_fahrenheit(30))     # hasil: 86.0
    print(detik_ke_jam(3725))            # hasil: (1, 2, 5)
  3. Telusuri dengan model substitusi. Tuliskan di kertas langkah evaluasi detik_ke_jam(3725) seperti contoh keliling di atas.
  4. Operator sebagai fungsi. Modul operator menyediakan versi fungsi dari setiap operator, berguna untuk map dan reduce:
    import operator as op
    
    print(op.add(2, 3), op.mul(4, 5), op.pow(2, 10))   # hasil: 5 20 1024
    print(list(map(op.mul, [1, 2, 3], [4, 5, 6])))      # hasil: [4, 10, 18]
    ambil_nama = op.itemgetter("nama")
    print(ambil_nama({"nama": "Andi", "nilai": 82}))    # hasil: Andi
  5. Ekspresi pada data mahasiswa.
    from data_mhs import DATA_MHS
    
    def ringkas(m: dict) -> str:
        return f'{m["nim"]} - {m["nama"]:<16} | {m["nilai"]:>3} | lulus: {m["nilai"] >= 60}'
    
    print("\n".join(map(ringkas, DATA_MHS)))
    # baris pertama: 2304001 - Andi Saputra     |  82 | lulus: True
  6. Ekspresi penugasan (walrus) :=. Berguna untuk memberi nama hasil antara di dalam ekspresi:
    data = (12, 45, 7, 30)
    print([y for x in data if (y := x * 2) > 20])   # hasil: [24, 90, 60]
Jebakan bilangan pecahan: 0.1 + 0.2 == 0.3 bernilai False karena representasi biner. Untuk membandingkan pecahan gunakan math.isclose(a, b), dan untuk uang gunakan bilangan bulat (rupiah) atau modul decimal.

E. Tugas

20
1. Tuliskan hasil setiap baris pada langkah 1 dan jelaskan mengapa -2 ** 2 menghasilkan -4 dan 2 ** 3 ** 2 menghasilkan 512.
20
2. Tuliskan langkah evaluasi model substitusi untuk nilai_akhir(70 + 10, 70, 45 * 2).
20
3. Tulis fungsi satu ekspresi ipk(bobot_sks) yang menerima tuple pasangan (bobot, sks) dan menghasilkan IPK. Contoh: ipk(((4, 3), (3, 2), (2, 3))) = 3.0.
20
4. Tulis fungsi satu ekspresi cicilan(pokok, bunga_tahunan, bulan) dengan rumus anuitas M = P·r / (1 − (1 + r)−n), r = bunga bulanan. Hitung untuk P = 12.000.000, bunga 12%/tahun, 12 bulan.
20
5. Jelaskan perbedaan ekspresi dan pernyataan. Mengapa paradigma fungsional lebih menyukai ekspresi?

F. Indikator Ketercapaian

Indikator RPS: Mahasiswa mampu menerapkan dasar ekspresi dasar fungsional dalam konsep paradigma pemrograman fungsional.

IndikatorSkor maks.
Memahami presedensi dan jenis ekspresi30
Menelusuri evaluasi dengan model substitusi30
Menulis fungsi berbasis satu ekspresi dengan benar40

G. Refleksi

LKM Praktikum PL · Pertemuan 06

Pemrograman Berbasis Ekspresi Kondisional

Sub-CPMKSUB-CPMK 071.1 — Menerapkan pemrograman fungsional berbasis ekspresi dasar dan ekspresi kondisional
CPMKCPMK 071
Bahan KajianPemrograman berbasis ekspresi kondisional
MetodePraktikum ekspresi kondisional · 100 menit
AsesmenNon-asesmen · 0% (materi diujikan pada UTS)

A. Tujuan Praktikum

  1. Mahasiswa mampu menulis percabangan sebagai ekspresi (a if kondisi else b).
  2. Mahasiswa mampu menggunakan pencocokan pola (match-case) dengan guard.
  3. Mahasiswa mampu mengganti rantai if-elif dengan tabel fungsi (dictionary dispatch).

B. Alat dan Bahan

C. Dasar Teori

Dalam gaya imperatif, if adalah pernyataan yang memilih aksi. Dalam gaya fungsional, percabangan adalah ekspresi yang memilih nilai. Setiap cabang wajib menghasilkan nilai, sehingga tidak ada kasus yang terlupa.

BentukSintaksKapan dipakai
Ekspresi kondisionalA if kondisi else BDua pilihan, atau rantai pendek
Pencocokan polamatch nilai: case pola: ...Memeriksa bentuk/struktur data (tuple, dict, objek)
Guardcase pola if kondisi:Pola dengan syarat tambahan
Dictionary dispatch{"+": add, "-": sub}[op](a, b)Banyak pilihan berdasarkan kunci

Pola case _: adalah wildcard yang cocok dengan apa pun. Letakkan di akhir sebagai kasus bawaan.

D. Langkah Praktikum

  1. Ekspresi kondisional sederhana. Buat p06.py:
    # p06.py
    def mutlak(x: float) -> float:
        return x if x >= 0 else -x
    
    def maks3(a, b, c):
        return (a if a >= c else c) if a >= b else (b if b >= c else c)
    
    def status(nilai: float) -> str:
        return "Lulus" if nilai >= 60 else "Tidak Lulus"
    
    print(mutlak(-7), maks3(4, 9, 2), status(58))   # hasil: 7 9 Tidak Lulus
  2. Rantai ekspresi kondisional. Huruf mutu sesuai tabel studi kasus:
    def huruf_mutu(nilai: float) -> str:
        return ("A" if nilai >= 85 else
                "B" if nilai >= 75 else
                "C" if nilai >= 60 else
                "D" if nilai >= 50 else
                "E")
    
    from data_mhs import DATA_MHS
    print(tuple((m["nama"].split()[0], huruf_mutu(m["nilai"])) for m in DATA_MHS))
    # hasil: (('Andi', 'B'), ('Bunga', 'A'), ('Citra', 'D'), ('Dimas', 'C'),
    #         ('Eka', 'D'), ('Fajar', 'A'), ('Gilang', 'B'))
  3. match-case dengan nilai literal.
    def keterangan(huruf: str) -> str:
        match huruf:
            case "A" | "B":
                return "Sangat baik / baik"
            case "C":
                return "Cukup"
            case "D" | "E":
                return "Tidak lulus"
            case _:
                return "Huruf tidak dikenal"
    
    print(keterangan("B"), "|", keterangan("X"))   # hasil: Sangat baik / baik | Huruf tidak dikenal
  4. match-case dengan struktur dan guard.
    def kuadran(titik: tuple) -> str:
        match titik:
            case (0, 0):
                return "Titik asal"
            case (x, 0):
                return "Sumbu X"
            case (0, y):
                return "Sumbu Y"
            case (x, y) if x > 0 and y > 0:
                return "Kuadran I"
            case (x, y) if x < 0 and y > 0:
                return "Kuadran II"
            case (x, y) if x < 0:
                return "Kuadran III"
            case _:
                return "Kuadran IV"
    
    print([kuadran(t) for t in [(0, 0), (5, 0), (2, 3), (-1, 4), (-2, -2), (3, -1)]])
    # hasil: ['Titik asal', 'Sumbu X', 'Kuadran I', 'Kuadran II', 'Kuadran III', 'Kuadran IV']
  5. match-case pada dictionary.
    def predikat(m: dict) -> str:
        match m:
            case {"prodi": "TI", "nilai": n} if n >= 85:
                return "Kandidat asisten lab TI"
            case {"nilai": n} if n < 60:
                return "Wajib remedial"
            case _:
                return "-"
    
    print([predikat(m) for m in DATA_MHS])
  6. Dictionary dispatch sebagai pengganti rantai if-elif:
    import operator as op
    
    OPERASI = {"+": op.add, "-": op.sub, "*": op.mul, "/": op.truediv}
    
    def hitung(a: float, simbol: str, b: float):
        return OPERASI[simbol](a, b) if simbol in OPERASI else None
    
    print(hitung(8, "*", 5), hitung(8, "/", 0.5), hitung(8, "^", 2))   # hasil: 40 16.0 None
Hati-hati: pola case x: (nama tanpa titik) bukan pembanding dengan variabel x, melainkan menangkap nilai apa pun ke dalam x. Untuk membandingkan dengan konstanta, gunakan literal atau nama bertitik seperti case Warna.MERAH:.

E. Tugas

20
1. Tulis fungsi satu ekspresi kabisat(tahun): tahun kabisat jika habis dibagi 400, atau habis dibagi 4 tetapi tidak habis dibagi 100. Uji dengan 1900, 2000, 2024, 2026.
20
2. Tarif parkir motor: 2 jam pertama Rp2.000, setiap jam berikutnya Rp1.000, maksimal Rp10.000 per hari. Tulis tarif_parkir(jam) dengan ekspresi kondisional.
20
3. Tulis bobot(huruf) dengan match-case yang mengembalikan 4.0/3.0/2.0/1.0/0.0, lalu hitung bobot rata-rata seluruh mahasiswa di DATA_MHS dengan menggabungkan huruf_mutu dan bobot.
25
4. Tulis proses_perintah(perintah) dengan match-case untuk tuple: ("tambah", a, b), ("kali", a, b), ("negasi", a), dan perintah lain → "perintah tidak dikenal".
15
5. Bandingkan tiga cara percabangan (ekspresi kondisional, match-case, dictionary dispatch). Kapan masing-masing paling tepat?

F. Indikator Ketercapaian

Indikator RPS: Mahasiswa mampu menerapkan konsep ekspresi fungsional dalam pemrograman.

IndikatorSkor maks.
Menulis percabangan sebagai ekspresi yang menghasilkan nilai40
Menerapkan match-case dengan pola dan guard40
Memilih bentuk percabangan yang tepat20

G. Refleksi

LKM Praktikum PL · Pertemuan 07

Pemrograman Berbasis Ekspresi Rekursif Fungsional

Sub-CPMKSUB-CPMK 071.1 — Menerapkan pemrograman fungsional berbasis ekspresi dasar dan ekspresi kondisional (dilanjutkan ke ekspresi rekursif)
CPMKCPMK 071
Bahan KajianPemrograman berbasis ekspresi rekursif fungsional
MetodePraktikum ekspresi rekursif fungsional · 100 menit
AsesmenNon-asesmen · 0% (materi diujikan pada UTS dan UAS)

A. Tujuan Praktikum

  1. Mahasiswa mampu mengidentifikasi kasus dasar (base case) dan kasus rekursif.
  2. Mahasiswa mampu menulis fungsi rekursif sebagai pengganti perulangan.
  3. Mahasiswa mampu menelusuri pemanggilan rekursif dan memahami batas kedalaman rekursi serta memoisasi.

B. Alat dan Bahan

C. Dasar Teori

Fungsi rekursif adalah fungsi yang memanggil dirinya sendiri. Bahasa fungsional murni tidak memiliki for/while, sehingga semua pengulangan ditulis sebagai rekursi. Fungsi rekursif yang benar selalu memiliki:

  1. Kasus dasar: kondisi berhenti yang langsung menghasilkan nilai tanpa memanggil diri sendiri.
  2. Kasus rekursif: memanggil diri sendiri dengan masalah yang lebih kecil sehingga akhirnya mencapai kasus dasar.
n! = 1                 jika n = 0      (kasus dasar)
n! = n × (n − 1)!      jika n > 0      (kasus rekursif)

faktorial(4)
→ 4 * faktorial(3)
→ 4 * (3 * faktorial(2))
→ 4 * (3 * (2 * faktorial(1)))
→ 4 * (3 * (2 * (1 * faktorial(0))))
→ 4 * (3 * (2 * (1 * 1)))
→ 24

Setiap pemanggilan menempati satu frame di call stack. Python membatasi kedalaman rekursi (bawaan sekitar 1000) dan tidak melakukan optimasi tail call. Untuk rekursi yang menghitung ulang submasalah yang sama (misalnya Fibonacci), gunakan memoisasi dengan @lru_cache.

D. Langkah Praktikum

  1. Faktorial dan jejaknya. Buat p07.py:
    # p07.py
    def faktorial(n: int) -> int:
        return 1 if n == 0 else n * faktorial(n - 1)
    
    def faktorial_jejak(n: int, kedalaman: int = 0) -> int:
        print("  " * kedalaman + f"faktorial({n})")
        hasil = 1 if n == 0 else n * faktorial_jejak(n - 1, kedalaman + 1)
        print("  " * kedalaman + f"=> {hasil}")
        return hasil
    
    print(faktorial(5))        # hasil: 120
    faktorial_jejak(4)         # amati bentuk "turun lalu naik"
  2. Rekursi pada bilangan.
    def jumlah_digit(n: int) -> int:
        return n if n < 10 else n % 10 + jumlah_digit(n // 10)
    
    def fpb(a: int, b: int) -> int:            # algoritma Euclid
        return a if b == 0 else fpb(b, a % b)
    
    def pangkat(b: int, e: int) -> int:        # pangkat cepat: O(log e)
        if e == 0:
            return 1
        setengah = pangkat(b, e // 2)
        return setengah * setengah if e % 2 == 0 else b * setengah * setengah
    
    def biner(n: int) -> str:
        return str(n) if n < 2 else biner(n // 2) + str(n % 2)
    
    print(jumlah_digit(2304), fpb(48, 18), pangkat(2, 10), biner(13))
    # hasil: 9 6 1024 1101
  3. Fibonacci: rekursi bercabang dan memoisasi.
    import time
    from functools import lru_cache
    
    def fib(n: int) -> int:
        return n if n < 2 else fib(n - 1) + fib(n - 2)
    
    @lru_cache(maxsize=None)
    def fib_memo(n: int) -> int:
        return n if n < 2 else fib_memo(n - 1) + fib_memo(n - 2)
    
    for f in (fib, fib_memo):
        mulai = time.perf_counter()
        hasil = f(30)
        print(f.__name__, hasil, f"{time.perf_counter() - mulai:.4f} detik")
    # fib 832040 (sekitar 0,2–0,5 detik) ; fib_memo 832040 (hampir 0 detik)
  4. Menara Hanoi dengan hasil berupa tuple langkah (bukan print):
    def hanoi(n: int, asal="A", tujuan="C", bantu="B") -> tuple:
        if n == 0:
            return ()
        return (hanoi(n - 1, asal, bantu, tujuan)
                + ((asal, tujuan),)
                + hanoi(n - 1, bantu, tujuan, asal))
    
    langkah = hanoi(3)
    print(len(langkah), langkah)
    # hasil: 7 (('A', 'C'), ('A', 'B'), ('C', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('A', 'C'))
  5. Batas kedalaman rekursi.
    import sys
    print(sys.getrecursionlimit())     # umumnya 1000
    # faktorial(5000)                  # hapus tanda # : RecursionError: maximum recursion depth exceeded
  6. Rekursi tanpa kasus dasar. Tulis def salah(n): return n + salah(n - 1), panggil salah(5), lalu baca pesan errornya. Diskusikan mengapa kasus dasar wajib ada.

E. Tugas

20
1. Tulis definisi matematis (kasus dasar dan rekursif) dan fungsi Python rekursif jumlah_deret(n) = 1 + 2 + … + n. Tuliskan jejak evaluasi untuk n = 4.
20
2. Tulis fungsi rekursif balik_angka(n) yang membalik digit bilangan, contoh 2304 → 4032, tanpa mengubah ke string.
20
3. Tulis fungsi rekursif kombinasi(n, k) dengan rumus Pascal: C(n,k) = C(n−1,k−1) + C(n−1,k), C(n,0) = C(n,n) = 1. Hitung C(20,10) dengan dan tanpa @lru_cache lalu bandingkan waktunya.
20
4. Berapa jumlah langkah hanoi(n) untuk n = 1 sampai 6? Temukan rumus umumnya dan jelaskan secara rekursif.
20
5. Jelaskan apa yang terjadi di call stack ketika faktorial(4) dijalankan, dan mengapa fib tanpa memoisasi sangat lambat.

F. Indikator Ketercapaian

Indikator RPS: Mahasiswa mampu menerapkan konsep pemrograman fungsional rekursif atau fungsi yang dapat memanggil dirinya sendiri.

IndikatorSkor maks.
Menentukan kasus dasar dan kasus rekursif dengan benar40
Fungsi rekursif berjalan benar dan berhenti40
Memahami call stack dan memoisasi20

G. Refleksi

LKM Praktikum PL · Pertemuan 08

Pemrograman Berbasis Ekspresi Lambda & UTS

Sub-CPMKSUB-CPMK 071.2 — Menerapkan pemrograman fungsional berbasis ekspresi rekursif dan ekspresi lambda
CPMKCPMK 071
Bahan KajianPemrograman berbasis ekspresi lambda
MetodePraktikum pemrograman berbasis ekspresi lambda · 100 menit
AsesmenUTS · 100% dari CPMK 071

A. Tujuan Praktikum

  1. Mahasiswa mampu menulis fungsi anonim dengan ekspresi lambda.
  2. Mahasiswa mampu memakai lambda sebagai argumen sorted, max, min, map, filter, dan reduce.
  3. Mahasiswa mampu menggunakan closure dan menghindari jebakan late binding.
  4. Mahasiswa mampu menggabungkan ekspresi dasar, kondisional, dan lambda dalam soal UTS.

B. Alat dan Bahan

C. Dasar Teori

Ekspresi lambda berasal dari kalkulus lambda (Alonzo Church, 1930-an), fondasi teoretis semua bahasa fungsional. Di Python, lambda membuat fungsi tanpa nama yang isinya satu ekspresi:

lambda parameter1, parameter2: ekspresi

# setara dengan
def _(parameter1, parameter2):
    return ekspresi
BolehTidak boleh
Satu ekspresi, termasuk ekspresi kondisional: lambda n: "L" if n >= 60 else "TL"Pernyataan seperti return, for, if sebagai pernyataan, =
Parameter bawaan: lambda x, k=2: x * kDocstring dan type hint

Closure adalah fungsi yang "mengingat" variabel dari lingkup tempat ia dibuat. PEP 8 menyarankan: jika fungsi perlu diberi nama, pakai def. Lambda paling tepat sebagai argumen sekali pakai.

D. Langkah Praktikum

  1. Dasar lambda. Buat p08.py:
    # p08.py
    from functools import reduce
    from data_mhs import DATA_MHS
    
    print((lambda a, b: a + b)(3, 4))                           # hasil: 7
    status = lambda n: "Lulus" if n >= 60 else "Tidak Lulus"     # hanya untuk latihan
    print(status(75), status(40))                                # hasil: Lulus Tidak Lulus
  2. Lambda sebagai kunci pengurutan.
    urut = sorted(DATA_MHS, key=lambda m: m["nilai"], reverse=True)
    print([m["nama"] for m in urut][:3])
    # hasil: ['Bunga Lestari', 'Fajar Nugroho', 'Andi Saputra']
    
    print(max(DATA_MHS, key=lambda m: m["sks"])["nama"])    # hasil: Dimas Pratama
    print(min(DATA_MHS, key=lambda m: m["nilai"])["nama"])  # hasil: Citra Dewi
    
    # urut prodi (A-Z), lalu nilai (besar ke kecil)
    for m in sorted(DATA_MHS, key=lambda m: (m["prodi"], -m["nilai"])):
        print(m["prodi"], m["nilai"], m["nama"])
  3. Lambda bersama map, filter, reduce.
    nilai = tuple(map(lambda m: m["nilai"], DATA_MHS))
    tidak_lulus = tuple(filter(lambda m: m["nilai"] < 60, DATA_MHS))
    hasil_kali = reduce(lambda acc, x: acc * x, range(1, 6), 1)
    
    print(nilai)                                  # hasil: (82, 91, 55, 74, 58, 88, 79)
    print([m["nama"] for m in tidak_lulus])       # hasil: ['Citra Dewi', 'Eka Putri']
    print(hasil_kali)                             # hasil: 120
  4. Closure: fungsi yang menghasilkan lambda.
    def pengali(n):
        return lambda x: x * n
    
    kali3, kali10 = pengali(3), pengali(10)
    print(kali3(7), kali10(7))                    # hasil: 21 70
    
    def tambah_bonus(bonus):
        return lambda m: {**m, "nilai": min(100, m["nilai"] + bonus)}
    
    print(tuple(map(tambah_bonus(5), DATA_MHS))[2])   # Citra: nilai 55 -> 60, DATA_MHS tidak berubah
  5. Jebakan late binding.
    fs = [lambda x: x * i for i in range(3)]
    print([f(10) for f in fs])            # hasil: [20, 20, 20]  -> semua memakai i terakhir
    
    fs = [lambda x, i=i: x * i for i in range(3)]
    print([f(10) for f in fs])            # hasil: [0, 10, 20]   -> i "dibekukan" lewat parameter bawaan
  6. Lambda di dalam struktur data.
    KONVERSI = {
        "C-F": lambda c: c * 9 / 5 + 32,
        "F-C": lambda f: (f - 32) * 5 / 9,
        "C-K": lambda c: c + 273.15,
    }
    print(KONVERSI["C-F"](100), KONVERSI["F-C"](212), KONVERSI["C-K"](0))
    # hasil: 212.0 100.0 273.15

E. Soal UTS Dinilai · 100 menit

Ketentuan UTS: dikerjakan individu, boleh membuka modul ini dan dokumentasi Python, tidak boleh bertanya kepada teman atau memakai AI generatif. Semua fungsi harus murni. Tempel kode lengkap dan keluaran pada setiap kotak jawaban.

Gunakan data berikut (simpan sebagai uts.py):

PRODUK = (
    {"kode": "P01", "nama": "Kopi Bubuk", "kategori": "minuman", "harga": 25_000, "stok": 40},
    {"kode": "P02", "nama": "Teh Celup",  "kategori": "minuman", "harga": 12_000, "stok": 0},
    {"kode": "P03", "nama": "Gula Pasir", "kategori": "sembako", "harga": 16_500, "stok": 75},
    {"kode": "P04", "nama": "Beras 5kg",  "kategori": "sembako", "harga": 72_000, "stok": 12},
    {"kode": "P05", "nama": "Mi Instan",  "kategori": "makanan", "harga": 3_500,  "stok": 200},
    {"kode": "P06", "nama": "Biskuit",    "kategori": "makanan", "harga": 9_000,  "stok": 8},
)
15
1. Ekspresi dasar. Tulis fungsi satu ekspresi nilai_persediaan(p) = harga × stok, lalu hitung total nilai persediaan semua produk.
20
2. Ekspresi kondisional. (a) status_stok(stok): "Habis" jika 0, "Menipis" jika kurang dari 10, selain itu "Aman", ditulis sebagai ekspresi kondisional. (b) diskon_kategori(kategori) dengan match-case: minuman 5%, makanan 10%, lainnya 0%.
25
3. Lambda. Dengan lambda, tampilkan: (a) nama produk urut dari harga termahal, (b) produk yang stoknya tersedia, (c) produk dengan nilai persediaan terbesar menggunakan max(..., key=lambda ...), (d) jumlah stok seluruh produk dengan reduce.
25
4. Gabungan. Buat fungsi laporan(produk) yang menghasilkan tuple (nama, harga_setelah_diskon, status_stok) hanya untuk produk yang tidak habis, terurut berdasarkan nama. Gunakan fungsi dari soal 1–3.
15
5. Analisis. Jelaskan mengapa fungsi-fungsi Anda murni, dan berikan satu contoh situasi ketika lambda sebaiknya diganti dengan def.

F. Indikator & Rubrik Penilaian UTS

Indikator RPS: Mahasiswa diharapkan mampu menerapkan konsep ekspresi dasar, ekspresi kondisional, dan lambda dalam pemrograman fungsional.

KriteriaSangat baik (skor penuh)Skor maks.
Ekspresi dasarFungsi satu ekspresi benar, total persediaan benar15
Ekspresi kondisionalSemua cabang benar, match-case memiliki wildcard20
Ekspresi lambdaLambda dipakai tepat sebagai argumen fungsi tingkat tinggi25
IntegrasiFungsi laporan benar, murni, dan menggunakan ulang fungsi lain25
AnalisisPenjelasan kemurnian fungsi dan penggunaan lambda tepat15

G. Refleksi

LKM Praktikum PL · Pertemuan 09

Pemrograman Fungsi dengan Type Bentukan

Sub-CPMKSUB-CPMK 071.2 — Menerapkan pemrograman fungsional berbasis ekspresi rekursif dan ekspresi lambda
CPMKCPMK 071
Bahan KajianPemrograman fungsi dengan type bentukan fungsi
MetodePraktikum berbasis type data bentukan · 100 menit
AsesmenNon-asesmen · 0% (dipakai pada Pertemuan 13–16)

A. Tujuan Praktikum

  1. Mahasiswa mampu mendefinisikan type bentukan yang immutable dengan NamedTuple dan @dataclass(frozen=True).
  2. Mahasiswa mampu memakai Enum, alias tipe, Optional, dan Callable.
  3. Mahasiswa mampu membuat tipe gabungan (sum type) dan memprosesnya dengan match-case.

B. Alat dan Bahan

C. Dasar Teori

Type bentukan (composite / user-defined type) adalah tipe yang disusun dari tipe lain. Dalam pemrograman fungsional dikenal dua bentuk utama tipe data aljabar (algebraic data type):

BentukArtiPython
Product type (tipe "dan")Gabungan beberapa nilai sekaligus: Mahasiswa = NIM dan nama dan nilaiNamedTuple, @dataclass(frozen=True)
Sum type (tipe "atau")Salah satu dari beberapa bentuk: Bangun = Lingkaran atau Persegi atau SegitigaUnion[A, B, C] atau A | B | C
EnumerasiHimpunan nilai tetap yang diberi namaEnum
Tipe fungsiFungsi dari A ke BCallable[[A], B]
Tipe opsionalNilai A atau tidak adaOptional[A] = A | None

Dibandingkan dict, type bentukan lebih aman: nama field diperiksa, dapat dibuat immutable, dan dapat dipakai pada pola match-case.

D. Langkah Praktikum

  1. NamedTuple untuk Mahasiswa. Buat tipe.py (akan dipakai lagi nanti):
    # tipe.py
    from typing import NamedTuple, Optional, Callable
    from data_mhs import DATA_MHS
    
    class Mahasiswa(NamedTuple):
        nim: str
        nama: str
        prodi: str
        sks: int
        nilai: float
    
    MHS: tuple = tuple(Mahasiswa(**d) for d in DATA_MHS)   # konversi dict -> Mahasiswa
    
    if __name__ == "__main__":
        m = MHS[0]
        print(m)              # hasil: Mahasiswa(nim='2304001', nama='Andi Saputra', prodi='TI', sks=20, nilai=82)
        print(m.nama, m[4])   # hasil: Andi Saputra 82
        nim, nama, *_ = m     # NamedTuple tetap tuple: bisa di-unpack
        m2 = m._replace(nilai=90)
        print(m.nilai, m2.nilai)   # hasil: 82 90  -> m tidak berubah
        # m.nilai = 90             # hapus tanda # : AttributeError
  2. Dataclass beku. Buat p09.py:
    # p09.py
    import math
    from dataclasses import dataclass, replace
    from enum import Enum
    from typing import Union, Optional, Callable
    from tipe import Mahasiswa, MHS
    
    @dataclass(frozen=True)
    class MataKuliah:
        kode: str
        nama: str
        sks: int
    
    mk = MataKuliah("40401502", "Praktikum Pemrograman Lanjutan", 1)
    mk2 = replace(mk, sks=2)            # salinan baru dengan sks berbeda
    print(mk.sks, mk2.sks)              # hasil: 1 2
    # mk.sks = 3                        # hapus tanda # : FrozenInstanceError
  3. Enum untuk huruf mutu.
    class Huruf(Enum):
        A = 4.0
        B = 3.0
        C = 2.0
        D = 1.0
        E = 0.0
    
    def ke_huruf(nilai: float) -> Huruf:
        return (Huruf.A if nilai >= 85 else Huruf.B if nilai >= 75 else
                Huruf.C if nilai >= 60 else Huruf.D if nilai >= 50 else Huruf.E)
    
    h = ke_huruf(82)
    print(h, h.name, h.value)           # hasil: Huruf.B B 3.0
  4. Sum type + match-case.
    @dataclass(frozen=True)
    class Lingkaran:
        r: float
    
    @dataclass(frozen=True)
    class PersegiPanjang:
        p: float
        l: float
    
    @dataclass(frozen=True)
    class Segitiga:
        alas: float
        tinggi: float
    
    Bangun = Union[Lingkaran, PersegiPanjang, Segitiga]
    
    def luas(b: Bangun) -> float:
        match b:
            case Lingkaran(r=r):
                return math.pi * r ** 2
            case PersegiPanjang(p=p, l=l):
                return p * l
            case Segitiga(alas=a, tinggi=t):
                return 0.5 * a * t
            case _:
                raise TypeError(f"Bukan bangun datar: {b!r}")
    
    bangun = (Lingkaran(7), PersegiPanjang(4, 5), Segitiga(6, 3))
    print([round(luas(b), 2) for b in bangun])    # hasil: [153.94, 20, 9.0]
  5. Optional: hasil yang mungkin tidak ada.
    def cari_mhs(nim: str, data: tuple) -> Optional[Mahasiswa]:
        return next((m for m in data if m.nim == nim), None)
    
    print(cari_mhs("2304006", MHS).nama)     # hasil: Fajar Nugroho
    print(cari_mhs("9999999", MHS))          # hasil: None
  6. Callable: fungsi sebagai tipe.
    Transformasi = Callable[[Mahasiswa], Mahasiswa]
    
    def bonus(n: float) -> Transformasi:
        return lambda m: m._replace(nilai=min(100, m.nilai + n))
    
    def terapkan(f: Transformasi, data: tuple) -> tuple:
        return tuple(map(f, data))
    
    baru = terapkan(bonus(5), MHS)
    print([m.nilai for m in baru])     # hasil: [87, 96, 60, 79, 63, 93, 84]
    print([m.nilai for m in MHS])      # data asli tetap: [82, 91, 55, 74, 58, 88, 79]

E. Tugas

20
1. Buat NamedTuple Buku(isbn, judul, penulis, tahun, harga) dan tuple berisi minimal 4 buku. Tampilkan judul buku terbitan setelah 2015 yang diurutkan berdasarkan harga.
20
2. Tambahkan bangun Trapesium(a, b, t) pada sum type Bangun, lalu perbarui luas. Tulis juga fungsi keliling untuk Lingkaran dan PersegiPanjang.
20
3. Buat Enum Hari (SENIN … MINGGU) dan fungsi hari_kerja(h: Hari) -> bool dengan match-case.
20
4. Dengan MHS dan ke_huruf, buat fungsi yang menghasilkan dictionary {Huruf: tuple nama}, misalnya {Huruf.A: ('Bunga Lestari', 'Fajar Nugroho'), ...}.
20
5. Jelaskan perbedaan product type dan sum type, serta keuntungan NamedTuple/dataclass beku dibandingkan dict biasa.

F. Indikator Ketercapaian

Indikator RPS: Mahasiswa diharapkan mampu menerapkan konsep type fungsi bentukan.

IndikatorSkor maks.
Mendefinisikan product type yang immutable30
Mendefinisikan sum type dan memprosesnya dengan match-case40
Menggunakan Enum, Optional, dan Callable dengan tepat30

G. Refleksi

LKM Praktikum PL · Pertemuan 10

List dengan Elemen Sederhana & Karakter

Sub-CPMKSUB-CPMK 072.1 — Menerapkan pemrograman fungsional berbasis list
CPMKCPMK 072
Bahan KajianPemrograman fungsional berbasis list menggunakan elemen sederhana dan karakter
MetodePraktikum elemen sederhana dan karakter · 100 menit
AsesmenNon-asesmen · 0% (materi diujikan pada UAS)

A. Tujuan Praktikum

  1. Mahasiswa mampu memandang list sebagai pasangan head (elemen pertama) dan tail (sisanya).
  2. Mahasiswa mampu mengolah string sebagai list karakter dengan fungsi murni.
  3. Mahasiswa mampu memakai operasi list: indeks, slicing, zip, enumerate, join, ord/chr.

B. Alat dan Bahan

C. Dasar Teori

Dalam bahasa fungsional, list didefinisikan secara rekursif: list adalah kosong, atau sebuah elemen (head) diikuti list lain (tail). Contoh: [3, 5, 7] = 3 diikuti [5, 7] = 3 diikuti 5 diikuti [7] = … diikuti [].

String di Python adalah barisan karakter yang immutable, sehingga cocok untuk gaya fungsional: setiap operasi menghasilkan string baru.

OperasiContohHasil
Head / tailxs[0], xs[1:]Elemen pertama, sisa list
Unpackingkepala, *ekor = xsHead dan tail sekaligus
Slicings[::-1]Barisan terbalik
Kode karakterord("A"), chr(66)65, 'B'
Gabung"-".join(["a", "b"])'a-b'
Pasangkanzip("ab", [1, 2])('a', 1), ('b', 2)

D. Langkah Praktikum

  1. List elemen sederhana. Buat p10.py:
    # p10.py
    kata = "Informatika"
    huruf = list(kata)
    kepala, *ekor = huruf
    
    print(huruf)                       # hasil: ['I', 'n', 'f', 'o', 'r', 'm', 'a', 't', 'i', 'k', 'a']
    print(kepala, "".join(ekor))       # hasil: I nformatika
    print(kata[0], kata[-1], kata[1:4], kata[::-1])   # hasil: I a nfo akitamrofnI
    print(len(kata), "m" in kata, kata.count("a"))    # hasil: 11 True 2
    
    bools = [True, False, True, True]
    print(all(bools), any(bools), sum(bools))          # hasil: False True 3
  2. Fungsi murni untuk karakter.
    VOKAL = frozenset("aiueoAIUEO")
    
    def hitung_vokal(s: str) -> int:
        return sum(1 for c in s if c in VOKAL)
    
    def palindrom(s: str) -> bool:
        bersih = [c.lower() for c in s if c.isalnum()]
        return bersih == bersih[::-1]
    
    def inisial(nama: str) -> str:
        return "".join(k[0].upper() for k in nama.split())
    
    print(hitung_vokal("Informatika"))      # hasil: 5
    print(palindrom("Kasur ini rusak"))     # hasil: True
    print(inisial("doni winarso"))          # hasil: DW
  3. Sandi Caesar dengan ord/chr.
    def geser(c: str, k: int) -> str:
        if c.isupper():
            return chr((ord(c) - ord("A") + k) % 26 + ord("A"))
        if c.islower():
            return chr((ord(c) - ord("a") + k) % 26 + ord("a"))
        return c                              # spasi, angka, tanda baca tidak diubah
    
    def caesar(teks: str, k: int) -> str:
        return "".join(geser(c, k) for c in teks)
    
    sandi = caesar("Halo UMRI", 3)
    print(sandi, "|", caesar(sandi, -3))      # hasil: Kdor XPUL | Halo UMRI
  4. Frekuensi karakter.
    from collections import Counter
    
    def frekuensi(s: str) -> dict:
        return {c: s.lower().count(c) for c in sorted(set(s.lower())) if c.isalpha()}
    
    print(frekuensi("Teknik Informatika"))
    print(Counter("teknik informatika").most_common(3))   # hasil: [('k', 3), ('i', 3), ('t', 2)]
  5. zip dan enumerate.
    nama = ("Andi", "Bunga", "Citra")
    nilai = (82, 91, 55)
    print(list(zip(nama, nilai)))              # hasil: [('Andi', 82), ('Bunga', 91), ('Citra', 55)]
    print(dict(zip(nama, nilai))["Bunga"])     # hasil: 91
    print([f"{i}. {n}" for i, n in enumerate(nama, start=1)])   # hasil: ['1. Andi', '2. Bunga', '3. Citra']
  6. Terapkan pada data mahasiswa.
    from data_mhs import DATA_MHS
    
    print(tuple(inisial(m["nama"]) for m in DATA_MHS))
    # hasil: ('AS', 'BL', 'CD', 'DP', 'EP', 'FN', 'GR')
    print(tuple(m["nama"] for m in DATA_MHS if hitung_vokal(m["nama"]) < 5))
    # hasil: ('Citra Dewi', 'Eka Putri')
    print(sorted(DATA_MHS, key=lambda m: len(m["nama"]))[0]["nama"])   # hasil: Eka Putri

E. Tugas

20
1. Tulis fungsi anagram(a, b) yang memeriksa apakah dua kata adalah anagram (abaikan huruf besar/kecil dan spasi). Uji dengan "Listen"/"Silent".
20
2. Tulis fungsi judul(s) yang mengubah setiap kata menjadi diawali huruf kapital tanpa memakai str.title() atau str.capitalize().
20
3. Tulis fungsi kompres(s) (run-length encoding): "aaabccdddd" → "a3b1c2d4". Petunjuk: boleh memakai itertools.groupby.
20
4. Buat username(m) untuk setiap mahasiswa di DATA_MHS: huruf kecil nama depan + 3 digit terakhir NIM, contoh andi001. Hasilkan tuple semua username.
20
5. Jelaskan mengapa sifat immutable pada string sejalan dengan prinsip pemrograman fungsional. Apa akibatnya jika string dapat diubah?

F. Indikator Ketercapaian

Indikator RPS: Mahasiswa diharapkan mampu menerapkan konsep pemrograman menggunakan elemen sederhana dan karakter berbasis list.

IndikatorSkor maks.
Menggunakan konsep head/tail dan operasi list dasar30
Mengolah string sebagai list karakter dengan fungsi murni50
Penjelasan konsep imutabilitas20

G. Refleksi

LKM Praktikum PL · Pertemuan 11

List Integer & Himpunan (Set)

Sub-CPMKSUB-CPMK 072.1 — Menerapkan pemrograman fungsional berbasis list
CPMKCPMK 072
Bahan KajianPemrograman list dengan integer dan set himpunan
MetodePraktikum list berbasis integer dan set himpunan · 100 menit
AsesmenNon-asesmen · 0% (materi diujikan pada UAS)

A. Tujuan Praktikum

  1. Mahasiswa mampu mengolah list bilangan bulat dengan fungsi agregat dan comprehension.
  2. Mahasiswa mampu menulis fungsi statistik sederhana secara fungsional.
  3. Mahasiswa mampu menerapkan operasi himpunan: gabungan, irisan, selisih, selisih simetris, dan subset.

B. Alat dan Bahan

C. Dasar Teori

Himpunan adalah kumpulan elemen tanpa urutan dan tanpa duplikat. Python menyediakan set (bisa diubah) dan frozenset (immutable, cocok untuk gaya fungsional dan bisa menjadi kunci dictionary).

MatematikaPythonArti
A ∪ BA | BGabungan
A ∩ BA & BIrisan
A − BA - BSelisih
A ⊕ BA ^ BSelisih simetris
A ⊆ BA <= BSubset
x ∈ Ax in AKeanggotaan, rata-rata O(1)
|A|len(A)Kardinalitas

D. Langkah Praktikum

  1. List integer dan agregasi. Buat p11.py:
    # p11.py
    xs = (12, 7, 3, 18, 7, 25, 3, 10)
    
    print(sum(xs), max(xs), min(xs), len(xs))      # hasil: 85 25 3 8
    print(sorted(xs))                              # hasil: [3, 3, 7, 7, 10, 12, 18, 25]
    print(sorted(set(xs)))                         # hasil: [3, 7, 10, 12, 18, 25]
    print([x for x in xs if x % 2 == 0])           # hasil: [12, 18, 10]
    print(list(range(10, 0, -3)))                  # hasil: [10, 7, 4, 1]
  2. Fungsi statistik fungsional.
    def rata(xs) -> float:
        return sum(xs) / len(xs)
    
    def varians(xs) -> float:
        r = rata(xs)
        return sum((x - r) ** 2 for x in xs) / len(xs)
    
    def median(xs) -> float:
        s, n = sorted(xs), len(xs)
        return s[n // 2] if n % 2 else (s[n // 2 - 1] + s[n // 2]) / 2
    
    print(rata(xs), round(varians(xs), 3), median(xs))   # hasil: 10.625 50.734 8.5
    
    import statistics                                      # bandingkan dengan pustaka bawaan
    print(statistics.mean(xs), statistics.median(xs))      # hasil: 10.625 8.5
  3. Bilangan prima dan faktor.
    def prima(n: int) -> bool:
        return n > 1 and all(n % d for d in range(2, int(n ** 0.5) + 1))
    
    def faktor(n: int) -> tuple:
        return tuple(d for d in range(1, n + 1) if n % d == 0)
    
    print([n for n in range(2, 40) if prima(n)])
    # hasil: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
    print(faktor(36))     # hasil: (1, 2, 3, 4, 6, 9, 12, 18, 36)
  4. Operasi himpunan.
    A = frozenset({1, 2, 3, 4, 5})
    B = frozenset({4, 5, 6, 7})
    
    print(sorted(A | B))     # hasil: [1, 2, 3, 4, 5, 6, 7]
    print(sorted(A & B))     # hasil: [4, 5]
    print(sorted(A - B))     # hasil: [1, 2, 3]
    print(sorted(A ^ B))     # hasil: [1, 2, 3, 6, 7]
    print({1, 2} <= A, A.isdisjoint({8, 9}))      # hasil: True True
    print({x * x for x in A if x % 2})             # comprehension himpunan: {1, 9, 25}
  5. Studi kasus KRS. Mata kuliah yang diambil tiap mahasiswa:
    KRS = {
        "Andi":  frozenset({"PL", "SBD", "Jarkom", "AI"}),
        "Bunga": frozenset({"PL", "SBD", "RPL"}),
        "Citra": frozenset({"PL", "AI", "Grafika"}),
    }
    
    semua_mk = frozenset().union(*KRS.values())
    mk_bersama = frozenset.intersection(*KRS.values())
    hanya_andi = KRS["Andi"] - KRS["Bunga"] - KRS["Citra"]
    
    print(sorted(semua_mk))    # hasil: ['AI', 'Grafika', 'Jarkom', 'PL', 'RPL', 'SBD']
    print(sorted(mk_bersama))  # hasil: ['PL']
    print(sorted(hanya_andi))  # hasil: ['Jarkom']
    print({mhs for mhs, mk in KRS.items() if "AI" in mk})   # peserta AI: {'Andi', 'Citra'}
  6. Himpunan kuasa secara rekursif.
    def himpunan_kuasa(s) -> list:
        xs = tuple(s)
        if not xs:
            return [frozenset()]
        sisa = himpunan_kuasa(xs[1:])
        return sisa + [h | {xs[0]} for h in sisa]
    
    P = himpunan_kuasa({"a", "b", "c"})
    print(len(P), sorted(sorted(h) for h in P))
    # hasil: 8 [[], ['a'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'c'], ['b'], ['b', 'c'], ['c']]

E. Tugas

20
1. Tulis fungsi murni normalisasi(xs) yang mengubah setiap nilai menjadi skala 0–1 dengan rumus (x − min) / (max − min). Uji pada nilai DATA_MHS.
20
2. Tulis fungsi modus(xs) yang mengembalikan himpunan nilai yang paling sering muncul (bisa lebih dari satu). Uji dengan (12, 7, 3, 18, 7, 25, 3, 10).
20
3. Dengan data KRS, tampilkan: (a) pasangan mahasiswa yang memiliki mata kuliah sama (beserta mata kuliahnya), (b) mata kuliah yang hanya diambil satu orang.
20
4. Tulis fungsi kartesius(A, B) dengan comprehension yang menghasilkan himpunan pasangan (a, b). Bandingkan hasilnya dengan itertools.product.
20
5. Mengapa frozenset dapat menjadi anggota himpunan lain atau kunci dictionary, sedangkan set tidak? Hubungkan dengan konsep imutabilitas.

F. Indikator Ketercapaian

Indikator RPS: Mahasiswa diharapkan mampu menerapkan konsep list berbasis integer dan himpunan.

IndikatorSkor maks.
Mengolah list integer dengan fungsi agregat dan comprehension40
Menerapkan operasi himpunan pada studi kasus40
Memahami frozenset dan imutabilitas20

G. Refleksi

LKM Praktikum PL · Pertemuan 12

Resume Rekursi terhadap List

Sub-CPMKSUB-CPMK 072.1 — Menerapkan pemrograman fungsional berbasis list
CPMKCPMK 072
Bahan KajianPemrograman resume rekursi terhadap list
MetodePraktikum pemrograman resume dan rekursi berbasis list · 100 menit
AsesmenNon-asesmen · 0% (materi diujikan pada UAS)

A. Tujuan Praktikum

  1. Mahasiswa mampu menerapkan pola rekursi head/tail untuk mengolah list.
  2. Mahasiswa mampu menulis ulang fungsi bawaan (len, sum, max, map, filter, reduce) secara rekursif.
  3. Mahasiswa mampu menerapkan algoritma pengurutan rekursif: quicksort dan merge sort.

B. Alat dan Bahan

C. Dasar Teori

Hampir semua fungsi rekursif pada list mengikuti satu pola:

f([])          = nilai_dasar                       ; kasus dasar: list kosong
f([x] + sisa)  = gabungkan(x, f(sisa))             ; kasus rekursif: head + hasil tail
FungsiKasus dasarKasus rekursif
panjang01 + panjang(sisa)
jumlah0x + jumlah(sisa)
peta (map)[][f(x)] + peta(f, sisa)
saring (filter)[][x] + saring(sisa) jika p(x), selain itu saring(sisa)
lipat (reduce)awallipat(f, f(awal, x), sisa)
balik[]balik(sisa) + [x]

Karena setiap pemanggilan membuat potongan list baru (xs[1:]) dan Python membatasi kedalaman rekursi, versi rekursif ini dipakai untuk memahami pola. Untuk data besar, gunakan fungsi bawaan yang sudah dioptimasi.

D. Langkah Praktikum

  1. Fungsi dasar rekursif. Buat p12.py:
    # p12.py
    def panjang(xs) -> int:
        return 0 if not xs else 1 + panjang(xs[1:])
    
    def jumlah(xs):
        return 0 if not xs else xs[0] + jumlah(xs[1:])
    
    def maksimum(xs):
        if len(xs) == 1:
            return xs[0]
        m = maksimum(xs[1:])
        return xs[0] if xs[0] > m else m
    
    def balik(xs) -> list:
        return [] if not xs else balik(xs[1:]) + [xs[0]]
    
    def ada(x, xs) -> bool:
        return bool(xs) and (xs[0] == x or ada(x, xs[1:]))
    
    data = [4, 9, 2, 7, 5]
    print(panjang(data), jumlah(data), maksimum(data), balik(data), ada(7, data))
    # hasil: 5 27 9 [5, 7, 2, 9, 4] True
  2. map, filter, reduce versi rekursif.
    def peta(f, xs) -> list:
        return [] if not xs else [f(xs[0])] + peta(f, xs[1:])
    
    def saring(p, xs) -> list:
        if not xs:
            return []
        sisa = saring(p, xs[1:])
        return [xs[0]] + sisa if p(xs[0]) else sisa
    
    def lipat(f, awal, xs):
        return awal if not xs else lipat(f, f(awal, xs[0]), xs[1:])
    
    print(peta(lambda x: x * 10, data))           # hasil: [40, 90, 20, 70, 50]
    print(saring(lambda x: x > 4, data))          # hasil: [9, 7, 5]
    print(lipat(lambda a, x: a + x, 0, data))     # hasil: 27
  3. List bersarang (nested).
    def ratakan(xs) -> list:
        if not xs:
            return []
        kepala, ekor = xs[0], xs[1:]
        return (ratakan(kepala) if isinstance(kepala, list) else [kepala]) + ratakan(ekor)
    
    def kedalaman(xs) -> int:
        return 0 if not isinstance(xs, list) else 1 + max(map(kedalaman, xs), default=0)
    
    bersarang = [1, [2, [3, 4]], 5, [[6]]]
    print(ratakan(bersarang), kedalaman(bersarang))   # hasil: [1, 2, 3, 4, 5, 6] 3
  4. Quicksort dan merge sort.
    def quicksort(xs) -> list:
        if not xs:
            return []
        pivot, ekor = xs[0], xs[1:]
        return (quicksort([x for x in ekor if x < pivot])
                + [pivot]
                + quicksort([x for x in ekor if x >= pivot]))
    
    def gabung(a, b) -> list:
        if not a:
            return b
        if not b:
            return a
        return [a[0]] + gabung(a[1:], b) if a[0] <= b[0] else [b[0]] + gabung(a, b[1:])
    
    def merge_sort(xs) -> list:
        if len(xs) <= 1:
            return list(xs)
        t = len(xs) // 2
        return gabung(merge_sort(xs[:t]), merge_sort(xs[t:]))
    
    acak = [38, 27, 43, 3, 9, 82, 10]
    print(quicksort(acak))    # hasil: [3, 9, 10, 27, 38, 43, 82]
    print(merge_sort(acak))   # hasil: [3, 9, 10, 27, 38, 43, 82]
    print(acak)               # list asli tidak berubah
  5. Rekursi pada data mahasiswa.
    from tipe import MHS
    
    def total_sks(ms) -> int:
        return 0 if not ms else ms[0].sks + total_sks(ms[1:])
    
    def terbaik(ms):
        if len(ms) == 1:
            return ms[0]
        b = terbaik(ms[1:])
        return ms[0] if ms[0].nilai >= b.nilai else b
    
    print(total_sks(MHS), terbaik(MHS).nama)          # hasil: 144 Bunga Lestari

E. Tugas

20
1. Tulis fungsi rekursif hitung(x, xs) (banyaknya x di xs) dan hapus_semua(x, xs) (list tanpa x). Uji dengan [1, 3, 1, 2, 1].
20
2. Tulis fungsi rekursif zip_rek(xs, ys) dan ambil(n, xs) (n elemen pertama) tanpa memakai zip atau slicing xs[:n].
20
3. Tuliskan jejak (trace) pemanggilan quicksort([5, 2, 8, 1, 9]) sampai hasil akhir.
25
4. Ubah merge_sort menjadi merge_sort_by(kunci, xs) yang menerima fungsi kunci, lalu urutkan MHS berdasarkan nilai dan berdasarkan panjang nama.
15
5. Buat resume (ringkasan) pola rekursi list dalam bentuk tabel milik Anda sendiri: nama fungsi, kasus dasar, kasus rekursif, dan kompleksitas waktu kira-kira.

F. Indikator Ketercapaian

Indikator RPS: Mahasiswa diharapkan mampu menerapkan konsep rekursi dan resume terhadap list.

IndikatorSkor maks.
Menerapkan pola rekursi head/tail dengan benar40
Menerapkan algoritma pengurutan rekursif40
Menyusun resume pola rekursi list20

G. Refleksi

LKM Praktikum PL · Pertemuan 13

Pemrograman Fungsional Berbasis Struktur Data List

Sub-CPMKSUB-CPMK 072.1 — Menerapkan pemrograman fungsional berbasis list
CPMKCPMK 072
Bahan KajianPemrograman fungsional berbasis struktur data list
MetodePraktikum konsep list dalam struktur data · 100 menit
AsesmenNon-asesmen · 0% (materi diujikan pada UAS)

A. Tujuan Praktikum

  1. Mahasiswa mampu mengimplementasikan linked list immutable (cons list) dengan type bentukan.
  2. Mahasiswa mampu menulis operasi list (panjang, map, filter, fold, gabung, balik) pada struktur buatan sendiri.
  3. Mahasiswa mampu memahami structural sharing serta mengimplementasikan stack dan queue fungsional.

B. Alat dan Bahan

C. Dasar Teori

List di bahasa fungsional (Haskell, Lisp, Scala) adalah cons list: rantai simpul, masing-masing berisi kepala dan penunjuk ke ekor. List kosong ditandai nilai khusus (di sini None).

dari_list([1, 2, 3])  =  Cons(1, Cons(2, Cons(3, None)))

   ┌───┬───┐   ┌───┬───┐   ┌───┬───┐
   │ 1 │ ●─┼──►│ 2 │ ●─┼──►│ 3 │ ●─┼──► None
   └───┴───┘   └───┴───┘   └───┴───┘
   kepala ekor

Karena simpul tidak pernah diubah, menambah elemen di depan (cons) cukup membuat satu simpul baru yang menunjuk ke list lama. List lama tetap utuh dan dipakai bersama. Teknik ini disebut structural sharing dan menjadi dasar persistent data structure.

OperasiKompleksitas
cons (tambah di depan), kepala, ekorO(1)
panjang, ke-n, tambah di akhir, gabungO(n)

D. Langkah Praktikum

  1. Definisikan cons list. Buat daftar.py:
    # daftar.py
    from __future__ import annotations
    from dataclasses import dataclass
    from typing import Any, Callable, Optional
    
    @dataclass(frozen=True)
    class Cons:
        kepala: Any
        ekor: Optional[Cons]
    
    Daftar = Optional[Cons]          # Daftar = None (kosong) atau Cons
    KOSONG: Daftar = None
    
    def cons(x, xs: Daftar) -> Daftar:
        return Cons(x, xs)
    
    def dari_list(py) -> Daftar:
        return KOSONG if not py else cons(py[0], dari_list(py[1:]))
    
    def ke_list(xs: Daftar) -> list:
        return [] if xs is None else [xs.kepala] + ke_list(xs.ekor)
    
    def tampil(xs: Daftar) -> str:
        return "∅" if xs is None else f"{xs.kepala} → {tampil(xs.ekor)}"
  2. Operasi dasar. Tambahkan ke daftar.py:
    def panjang(xs: Daftar) -> int:
        return 0 if xs is None else 1 + panjang(xs.ekor)
    
    def ke_n(xs: Daftar, n: int):
        if xs is None:
            raise IndexError("indeks di luar jangkauan")
        return xs.kepala if n == 0 else ke_n(xs.ekor, n - 1)
    
    def peta(f: Callable, xs: Daftar) -> Daftar:
        return None if xs is None else Cons(f(xs.kepala), peta(f, xs.ekor))
    
    def saring(p: Callable, xs: Daftar) -> Daftar:
        if xs is None:
            return None
        sisa = saring(p, xs.ekor)
        return Cons(xs.kepala, sisa) if p(xs.kepala) else sisa
    
    def lipat_kanan(f: Callable, awal, xs: Daftar):
        """f(x1, f(x2, ... f(xn, awal)))"""
        return awal if xs is None else f(xs.kepala, lipat_kanan(f, awal, xs.ekor))
    
    def gabung(xs: Daftar, ys: Daftar) -> Daftar:
        return ys if xs is None else Cons(xs.kepala, gabung(xs.ekor, ys))
    
    def balik(xs: Daftar, acc: Daftar = None) -> Daftar:
        return acc if xs is None else balik(xs.ekor, Cons(xs.kepala, acc))
  3. Uji di p13.py.
    # p13.py
    from daftar import *
    
    xs = dari_list([1, 2, 3, 4, 5])
    print(tampil(xs))                                   # hasil: 1 → 2 → 3 → 4 → 5 → ∅
    print(panjang(xs), ke_n(xs, 2))                     # hasil: 5 3
    print(tampil(peta(lambda x: x * x, xs)))            # hasil: 1 → 4 → 9 → 16 → 25 → ∅
    print(tampil(saring(lambda x: x % 2, xs)))          # hasil: 1 → 3 → 5 → ∅
    print(lipat_kanan(lambda x, acc: x + acc, 0, xs))   # hasil: 15
    print(tampil(balik(xs)))                            # hasil: 5 → 4 → 3 → 2 → 1 → ∅
    print(tampil(gabung(xs, dari_list([6, 7]))))        # hasil: 1 → 2 → 3 → 4 → 5 → 6 → 7 → ∅
  4. Buktikan structural sharing.
    ys = cons(0, xs)
    print(tampil(ys))          # hasil: 0 → 1 → 2 → 3 → 4 → 5 → ∅
    print(tampil(xs))          # xs tetap: 1 → 2 → 3 → 4 → 5 → ∅
    print(ys.ekor is xs)       # hasil: True  -> tidak ada penyalinan
  5. Stack dan queue fungsional. Tambahkan ke p13.py:
    # Stack: push = cons, pop = (kepala, ekor)
    def push(s: Daftar, x) -> Daftar:
        return cons(x, s)
    
    def pop(s: Daftar) -> tuple:
        if s is None:
            raise IndexError("stack kosong")
        return s.kepala, s.ekor
    
    s1 = push(push(push(KOSONG, "a"), "b"), "c")
    atas, s2 = pop(s1)
    print(atas, tampil(s2), tampil(s1))   # hasil: c b → a → ∅ c → b → a → ∅
    
    # Queue dengan dua list: (depan, belakang). Belakang disimpan terbalik.
    def enqueue(q: tuple, x) -> tuple:
        depan, belakang = q
        return (depan, cons(x, belakang))
    
    def dequeue(q: tuple) -> tuple:
        depan, belakang = q
        if depan is None and belakang is None:
            raise IndexError("antrian kosong")
        if depan is None:
            return dequeue((balik(belakang), None))
        return depan.kepala, (depan.ekor, belakang)
    
    q = enqueue(enqueue(enqueue((None, None), "A1"), "A2"), "A3")
    x1, q = dequeue(q)
    x2, q = dequeue(q)
    print(x1, x2)     # hasil: A1 A2  (FIFO)
  6. Cons list berisi data mahasiswa.
    from tipe import MHS
    
    daftar_mhs = dari_list(MHS)
    ti = saring(lambda m: m.prodi == "TI", daftar_mhs)
    print(tampil(peta(lambda m: m.nama.split()[0], ti)))   # hasil: Andi → Citra → Dimas → Fajar → ∅
    print(lipat_kanan(lambda m, acc: m.sks + acc, 0, daftar_mhs))   # hasil: 144

E. Tugas

20
1. Tambahkan fungsi ambil(n, xs), buang(n, xs), dan tambah_akhir(xs, x) pada daftar.py. Uji ketiganya.
20
2. Tulis lipat_kiri(f, awal, xs). Tunjukkan perbedaan hasil lipat_kiri dan lipat_kanan untuk operasi pengurangan pada list 1 → 2 → 3.
20
3. Tulis sisip_terurut(xs, x) yang menyisipkan x ke cons list terurut, lalu gunakan untuk membuat insertion_sort(xs).
20
4. Gunakan stack fungsional untuk memeriksa keseimbangan kurung: seimbang("(a[b]{c})") = True, seimbang("(]") = False.
20
5. Gambarkan (dengan teks/ASCII) kondisi memori setelah ys = cons(0, xs) dan zs = cons(9, xs). Berapa simpul baru yang dibuat? Jelaskan keuntungan structural sharing.

F. Indikator Ketercapaian

Indikator RPS: Mahasiswa mampu menerapkan konsep pemrograman fungsional berbasis struktur data list.

IndikatorSkor maks.
Mengimplementasikan cons list dan operasinya40
Mengimplementasikan stack/queue fungsional30
Memahami structural sharing dan persistensi data30

G. Refleksi

LKM Praktikum PL · Pertemuan 14

Pemrograman Fungsional Berbasis Tree: Pohon N-ary & Biner

Sub-CPMKSUB-CPMK 072.2 — Menerapkan pemrograman fungsional berbasis tree (pohon)
CPMKCPMK 072
Bahan KajianPemrograman fungsional berbasis tree untuk pohon n-ary dan biner
MetodePraktikum pohon biner dan n-ary · 100 menit
AsesmenNon-asesmen · 0% (materi diujikan pada UAS)

A. Tujuan Praktikum

  1. Mahasiswa mampu mendefinisikan pohon biner dan pohon n-ary sebagai type bentukan rekursif.
  2. Mahasiswa mampu menulis fungsi rekursif pada pohon: ukuran, tinggi, daun, dan penelusuran (preorder, inorder, postorder).
  3. Mahasiswa mampu mentransformasi pohon secara immutable (map, mirror) dan mengevaluasi pohon ekspresi.

B. Alat dan Bahan

C. Dasar Teori

Pohon (tree) adalah struktur hierarkis yang didefinisikan secara rekursif: pohon adalah kosong, atau simpul berisi nilai dan sejumlah subpohon. Karena definisinya rekursif, fungsi pada pohon hampir selalu rekursif.

IstilahArti
Akar (root)Simpul paling atas
Daun (leaf)Simpul tanpa anak
Tinggi (height)Jumlah simpul pada jalur terpanjang dari akar ke daun (pohon kosong = 0)
Pohon binerSetiap simpul punya paling banyak 2 anak: kiri dan kanan
Pohon n-arySetiap simpul boleh punya banyak anak (contoh: struktur folder, organisasi)

Pohon biner yang dipakai dalam praktikum ini:

            8
          /   \
         3     10
        / \      \
       1   6      14
          / \    /
         4   7  13

preorder  (akar-kiri-kanan) : 8 3 1 6 4 7 10 14 13
inorder   (kiri-akar-kanan) : 1 3 4 6 7 8 10 13 14
postorder (kiri-kanan-akar) : 1 4 7 6 3 13 14 10 8

D. Langkah Praktikum

  1. Definisikan pohon biner. Buat pohon.py:
    # pohon.py
    from __future__ import annotations
    from dataclasses import dataclass
    from typing import Any, Callable, Optional
    
    @dataclass(frozen=True)
    class Simpul:
        nilai: Any
        kiri: Optional[Simpul] = None
        kanan: Optional[Simpul] = None
    
    Pohon = Optional[Simpul]         # Pohon = None (kosong) atau Simpul
    
    def daun(x) -> Simpul:
        return Simpul(x)
    
    def ukuran(t: Pohon) -> int:
        return 0 if t is None else 1 + ukuran(t.kiri) + ukuran(t.kanan)
    
    def tinggi(t: Pohon) -> int:
        return 0 if t is None else 1 + max(tinggi(t.kiri), tinggi(t.kanan))
    
    def jumlah_daun(t: Pohon) -> int:
        if t is None:
            return 0
        if t.kiri is None and t.kanan is None:
            return 1
        return jumlah_daun(t.kiri) + jumlah_daun(t.kanan)
    
    def preorder(t: Pohon) -> list:
        return [] if t is None else [t.nilai] + preorder(t.kiri) + preorder(t.kanan)
    
    def inorder(t: Pohon) -> list:
        return [] if t is None else inorder(t.kiri) + [t.nilai] + inorder(t.kanan)
    
    def postorder(t: Pohon) -> list:
        return [] if t is None else postorder(t.kiri) + postorder(t.kanan) + [t.nilai]
    
    def peta_pohon(f: Callable, t: Pohon) -> Pohon:
        return None if t is None else Simpul(f(t.nilai), peta_pohon(f, t.kiri), peta_pohon(f, t.kanan))
    
    def cermin(t: Pohon) -> Pohon:
        return None if t is None else Simpul(t.nilai, cermin(t.kanan), cermin(t.kiri))
    
    def gambar(t: Pohon, indent: str = "") -> str:
        """Gambar pohon menyamping: subpohon kanan di atas, kiri di bawah."""
        if t is None:
            return ""
        return (gambar(t.kanan, indent + "    ")
                + f"{indent}{t.nilai}\n"
                + gambar(t.kiri, indent + "    "))
  2. Bangun dan uji pohon contoh. Buat p14.py:
    # p14.py
    from pohon import *
    
    T = Simpul(8,
               Simpul(3, daun(1), Simpul(6, daun(4), daun(7))),
               Simpul(10, None, Simpul(14, daun(13), None)))
    
    print(ukuran(T), tinggi(T), jumlah_daun(T))   # hasil: 9 4 4
    print(preorder(T))     # hasil: [8, 3, 1, 6, 4, 7, 10, 14, 13]
    print(inorder(T))      # hasil: [1, 3, 4, 6, 7, 8, 10, 13, 14]
    print(postorder(T))    # hasil: [1, 4, 7, 6, 3, 13, 14, 10, 8]
    print(sum(inorder(peta_pohon(lambda x: x * 2, T))))   # hasil: 132
    print(inorder(cermin(T)))                             # hasil: [14, 13, 10, 8, 7, 6, 4, 3, 1]
    print(gambar(T))
  3. Pohon ekspresi aritmetika. Pohon biner juga dapat merepresentasikan ekspresi (3 + 4) * (5 - 2):
    import operator as op
    
    OPS = {"+": op.add, "-": op.sub, "*": op.mul, "/": op.truediv}
    
    def evaluasi(t: Simpul):
        match t:
            case Simpul(nilai=n, kiri=None, kanan=None):
                return n                                       # daun = bilangan
            case Simpul(nilai=o, kiri=a, kanan=b) if o in OPS:
                return OPS[o](evaluasi(a), evaluasi(b))       # simpul dalam = operator
            case _:
                raise ValueError(f"Ekspresi tidak valid: {t}")
    
    def ke_infix(t: Simpul) -> str:
        return (str(t.nilai) if t.kiri is None and t.kanan is None
                else f"({ke_infix(t.kiri)} {t.nilai} {ke_infix(t.kanan)})")
    
    E = Simpul("*", Simpul("+", daun(3), daun(4)), Simpul("-", daun(5), daun(2)))
    print(ke_infix(E), "=", evaluasi(E))   # hasil: ((3 + 4) * (5 - 2)) = 21
  4. Pohon n-ary. Tambahkan ke pohon.py:
    @dataclass(frozen=True)
    class SimpulN:
        nilai: Any
        anak: tuple = ()             # tuple berisi SimpulN
    
    def ukuran_n(t: SimpulN) -> int:
        return 1 + sum(map(ukuran_n, t.anak))
    
    def tinggi_n(t: SimpulN) -> int:
        return 1 + max(map(tinggi_n, t.anak), default=0)
    
    def daun_n(t: SimpulN) -> list:
        return [t.nilai] if not t.anak else [d for a in t.anak for d in daun_n(a)]
    
    def cetak_n(t: SimpulN, level: int = 0) -> str:
        return "  " * level + f"- {t.nilai}\n" + "".join(cetak_n(a, level + 1) for a in t.anak)
    
    def jalur(t: SimpulN, target) -> Optional[list]:
        if t.nilai == target:
            return [t.nilai]
        for a in t.anak:            # berhenti pada jalur pertama yang ditemukan
            j = jalur(a, target)
            if j:
                return [t.nilai] + j
        return None
  5. Struktur organisasi fakultas sebagai pohon n-ary. Tambahkan ke p14.py:
    FASILKOM = SimpulN("Fasilkom", (
        SimpulN("Teknik Informatika", (SimpulN("Lab Pemrograman"), SimpulN("Lab Jaringan"))),
        SimpulN("Sistem Informasi", (SimpulN("Lab Basis Data"), SimpulN("Lab BI"))),
        SimpulN("Sekretariat"),
    ))
    
    print(ukuran_n(FASILKOM), tinggi_n(FASILKOM))   # hasil: 8 3
    print(daun_n(FASILKOM))
    # hasil: ['Lab Pemrograman', 'Lab Jaringan', 'Lab Basis Data', 'Lab BI', 'Sekretariat']
    print(cetak_n(FASILKOM))
    print(jalur(FASILKOM, "Lab BI"))   # hasil: ['Fasilkom', 'Sistem Informasi', 'Lab BI']

E. Tugas

20
1. Gambarkan pohon biner dari pemanggilan berikut, lalu tuliskan hasil preorder, inorder, postorder, ukuran, dan tingginya:
Simpul("A", Simpul("B", daun("D"), daun("E")), Simpul("C", None, daun("F")))
20
2. Tulis fungsi jumlah_nilai(t), maks_pohon(t), dan level(t, k) (daftar nilai pada kedalaman k, akar = kedalaman 0) untuk pohon biner. Uji dengan pohon T.
20
3. Buat pohon ekspresi untuk (10 - 4) / (1 + 2) * 7, tampilkan bentuk infix-nya, lalu evaluasi.
20
4. Modelkan struktur folder proyek sebagai pohon n-ary dengan nilai berupa tuple (nama, ukuran_kb). Tulis fungsi total_ukuran(t) dan cari_file(t, nama).
20
5. Jelaskan mengapa pohon biner sebenarnya adalah kasus khusus pohon n-ary, dan bagaimana fungsi cermin tetap bersifat murni.

F. Indikator Ketercapaian

Indikator RPS: Mahasiswa diharapkan mampu menerapkan konsep pohon biner dan n-ary pada struktur data tree.

IndikatorSkor maks.
Mendefinisikan pohon biner dan n-ary dengan type bentukan25
Menulis fungsi rekursif dan penelusuran pohon50
Menerapkan pohon pada studi kasus (ekspresi, organisasi, folder)25

G. Refleksi

LKM Praktikum PL · Pertemuan 15

Pemrograman Fungsional Berbasis Binary Search & Balanced Search

Sub-CPMKSUB-CPMK 072.2 — Menerapkan pemrograman fungsional berbasis tree (pohon)
CPMKCPMK 072
Bahan KajianPemrograman fungsional berbasis binary search dan balance search
MetodePraktikum binary search dan balance search · 100 menit
AsesmenNon-asesmen · 0% (materi diujikan pada UAS)

A. Tujuan Praktikum

  1. Mahasiswa mampu menulis binary search rekursif pada barisan terurut.
  2. Mahasiswa mampu mengimplementasikan Binary Search Tree (BST) yang persisten (immutable).
  3. Mahasiswa mampu menjelaskan masalah pohon tidak seimbang dan mengimplementasikan pohon seimbang (AVL) dengan rotasi.

B. Alat dan Bahan

C. Dasar Teori

Binary search mencari pada barisan terurut dengan membandingkan elemen tengah, lalu membuang separuh barisan setiap langkah. Kompleksitasnya O(log n): 1.000.000 data cukup ±20 langkah.

Binary Search Tree (BST) adalah pohon biner dengan aturan: semua nilai di subpohon kiri < akar < semua nilai di subpohon kanan. Penelusuran inorder BST selalu menghasilkan data terurut. Pencarian, penyisipan, dan penghapusan memerlukan waktu sebanding dengan tinggi pohon.

Masalahnya, jika data disisipkan dalam urutan terurut (1, 2, 3, …), BST berubah menjadi "rantai" setinggi n sehingga pencarian menjadi O(n). Pohon seimbang (balanced tree) menjaga tinggi tetap O(log n). Salah satunya pohon AVL: selisih tinggi subpohon kiri dan kanan (balance factor) setiap simpul paling besar 1. Jika dilanggar, pohon diperbaiki dengan rotasi.

Sisip 1, 2, 3 ke BST biasa:       Setelah rotasi kiri (AVL):
   1                                   2
    \                                 / \
     2                               1   3
      \
       3          tinggi 3  →  tinggi 2
KasusKondisiPerbaikan
Kiri-Kirifaktor > 1, anak kiri condong kiriRotasi kanan
Kiri-Kananfaktor > 1, anak kiri condong kananRotasi kiri pada anak kiri, lalu rotasi kanan
Kanan-Kananfaktor < −1, anak kanan condong kananRotasi kiri
Kanan-Kirifaktor < −1, anak kanan condong kiriRotasi kanan pada anak kanan, lalu rotasi kiri

D. Langkah Praktikum

  1. Binary search rekursif. Buat p15.py:
    # p15.py
    from functools import reduce
    from dataclasses import dataclass, replace
    from typing import Any, Optional
    from pohon import Simpul, Pohon, inorder, tinggi
    
    def cari_biner(xs, target, lo=0, hi=None, langkah=1):
        """Mengembalikan (indeks atau -1, banyak langkah)."""
        hi = len(xs) - 1 if hi is None else hi
        if lo > hi:
            return -1, langkah
        t = (lo + hi) // 2
        if xs[t] == target:
            return t, langkah
        return (cari_biner(xs, target, t + 1, hi, langkah + 1) if xs[t] < target
                else cari_biner(xs, target, lo, t - 1, langkah + 1))
    
    data = tuple(range(0, 2_000_000, 2))         # 1 juta bilangan genap terurut
    print(cari_biner(data, 1_234_566))           # hasil: (617283, langkah ≤ 20)
    print(cari_biner(data, 7))                   # hasil: (-1, ...) karena 7 ganjil
  2. BST persisten.
    def sisip(t: Pohon, x) -> Pohon:
        if t is None:
            return Simpul(x)
        if x < t.nilai:
            return replace(t, kiri=sisip(t.kiri, x))     # salin jalur, sisanya dipakai bersama
        if x > t.nilai:
            return replace(t, kanan=sisip(t.kanan, x))
        return t                                          # duplikat diabaikan
    
    def cari(t: Pohon, x) -> bool:
        if t is None:
            return False
        if x == t.nilai:
            return True
        return cari(t.kiri, x) if x < t.nilai else cari(t.kanan, x)
    
    def minimum(t: Simpul):
        return t.nilai if t.kiri is None else minimum(t.kiri)
    
    def hapus(t: Pohon, x) -> Pohon:
        if t is None:
            return None
        if x < t.nilai:
            return replace(t, kiri=hapus(t.kiri, x))
        if x > t.nilai:
            return replace(t, kanan=hapus(t.kanan, x))
        if t.kiri is None:
            return t.kanan
        if t.kanan is None:
            return t.kiri
        pengganti = minimum(t.kanan)                      # successor inorder
        return Simpul(pengganti, t.kiri, hapus(t.kanan, pengganti))
    
    def bst_dari(xs) -> Pohon:
        return reduce(sisip, xs, None)
    
    B = bst_dari([8, 3, 10, 1, 6, 14, 4, 7, 13])
    print(inorder(B), tinggi(B))                  # hasil: [1, 3, 4, 6, 7, 8, 10, 13, 14] 4
    print(cari(B, 6), cari(B, 5))                 # hasil: True False
    B2 = hapus(B, 3)
    print(inorder(B2), inorder(B))                # B2 tanpa 3, B tetap utuh
  3. Amati masalah ketidakseimbangan.
    def seimbang_dari_terurut(xs) -> Pohon:
        if not xs:
            return None
        t = len(xs) // 2
        return Simpul(xs[t], seimbang_dari_terurut(xs[:t]), seimbang_dari_terurut(xs[t + 1:]))
    
    urut = list(range(1, 101))
    print(tinggi(bst_dari(urut)))                 # hasil: 100  -> seperti linked list
    print(tinggi(seimbang_dari_terurut(urut)))    # hasil: 7    -> ceil(log2(101))
  4. Pohon AVL fungsional.
    @dataclass(frozen=True)
    class AVL:
        nilai: Any
        kiri: Optional["AVL"] = None
        kanan: Optional["AVL"] = None
        tinggi: int = 1
    
    def h(t) -> int:
        return 0 if t is None else t.tinggi
    
    def buat(nilai, kiri, kanan) -> AVL:
        return AVL(nilai, kiri, kanan, 1 + max(h(kiri), h(kanan)))
    
    def faktor(t: AVL) -> int:
        return h(t.kiri) - h(t.kanan)
    
    def putar_kanan(y: AVL) -> AVL:
        x = y.kiri
        return buat(x.nilai, x.kiri, buat(y.nilai, x.kanan, y.kanan))
    
    def putar_kiri(x: AVL) -> AVL:
        y = x.kanan
        return buat(y.nilai, buat(x.nilai, x.kiri, y.kiri), y.kanan)
    
    def seimbangkan(t: AVL) -> AVL:
        f = faktor(t)
        if f > 1:                                          # berat kiri
            kiri = t.kiri if faktor(t.kiri) >= 0 else putar_kiri(t.kiri)
            return putar_kanan(buat(t.nilai, kiri, t.kanan))
        if f < -1:                                         # berat kanan
            kanan = t.kanan if faktor(t.kanan) <= 0 else putar_kanan(t.kanan)
            return putar_kiri(buat(t.nilai, t.kiri, kanan))
        return t
    
    def sisip_avl(t: Optional[AVL], x) -> AVL:
        if t is None:
            return AVL(x)
        if x < t.nilai:
            return seimbangkan(buat(t.nilai, sisip_avl(t.kiri, x), t.kanan))
        if x > t.nilai:
            return seimbangkan(buat(t.nilai, t.kiri, sisip_avl(t.kanan, x)))
        return t
    
    def inorder_avl(t) -> list:
        return [] if t is None else inorder_avl(t.kiri) + [t.nilai] + inorder_avl(t.kanan)
    
    for n in (7, 100, 500):
        bst = bst_dari(range(1, n + 1))
        avl = reduce(sisip_avl, range(1, n + 1), None)
        print(n, "data | tinggi BST:", tinggi(bst), "| tinggi AVL:", avl.tinggi)
    # hasil:
    # 7 data | tinggi BST: 7 | tinggi AVL: 3
    # 100 data | tinggi BST: 100 | tinggi AVL: 7
    # 500 data | tinggi BST: 500 | tinggi AVL: 9
  5. BST data mahasiswa berdasarkan NIM. Tuple dibandingkan elemen demi elemen, sehingga pasangan (nim, nama) terurut berdasarkan NIM:
    from tipe import MHS
    
    def cari_nim(t: Optional[AVL], nim: str):
        if t is None:
            return None
        k, nama = t.nilai
        return nama if nim == k else cari_nim(t.kiri if nim < k else t.kanan, nim)
    
    indeks = reduce(sisip_avl, ((m.nim, m.nama) for m in MHS), None)
    print(indeks.tinggi, cari_nim(indeks, "2304005"), cari_nim(indeks, "2309999"))
    # hasil: 3 Eka Putri None

E. Tugas

20
1. Tuliskan jejak cari_biner((3, 8, 15, 21, 30, 42, 57), 42): nilai lo, hi, dan tengah pada setiap langkah.
20
2. Sisipkan berturut-turut 50, 30, 70, 20, 40, 60, 80, 35 ke BST. Gambarkan pohonnya, lalu gambarkan hasil hapus(B, 30).
20
3. Sisipkan 10, 20, 30, 25, 28 ke pohon AVL. Gambarkan pohon setelah setiap penyisipan dan sebutkan rotasi yang terjadi.
20
4. Tulis fungsi rentang(t, a, b) yang mengembalikan semua nilai BST dalam rentang [a, b] tanpa menelusuri subpohon yang pasti di luar rentang.
20
5. Bandingkan pencarian pada list biasa, BST tidak seimbang, dan AVL dari sisi kompleksitas waktu. Kapan pohon seimbang benar-benar diperlukan?

F. Indikator Ketercapaian

Indikator RPS: Mahasiswa diharapkan mampu menerapkan konsep binary search dan balance search.

IndikatorSkor maks.
Menerapkan binary search rekursif20
Mengimplementasikan BST persisten (sisip, cari, hapus)40
Menjelaskan dan menerapkan penyeimbangan (AVL)40

G. Refleksi

LKM Praktikum PL · Pertemuan 16

UAS: Pemrograman Fungsional List & Tree

Sub-CPMKSUB-CPMK 072.2 — Menerapkan pemrograman fungsional berbasis tree (pohon)
CPMKCPMK 072
Bahan KajianPemrograman fungsional berbasis struktur data list dan tree
MetodeUjian pemrograman fungsional list dan tree · 100 menit
AsesmenUAS · 100% dari CPMK 072

A. Tujuan

  1. Mengukur kemampuan mahasiswa menerapkan list (elemen sederhana, integer, himpunan, dan struktur data list) secara fungsional.
  2. Mengukur kemampuan mahasiswa menerapkan tree (pohon biner, n-ary, BST, dan pohon seimbang) secara fungsional.

B. Ketentuan Ujian

C. Data Ujian

NILAI = (78, 92, 55, 61, 88, 43, 70, 95, 67, 82)

PEMINAT = {
    "AI":       frozenset({"Andi", "Citra", "Fajar", "Gilang"}),
    "Jaringan": frozenset({"Bunga", "Dimas", "Fajar"}),
    "Data":     frozenset({"Andi", "Bunga", "Eka", "Fajar"}),
}

NIM_MASUK = ("2304050", "2304020", "2304080", "2304010",
             "2304030", "2304070", "2304090", "2304060", "2304040")

D. Soal

15
1. List integer & rekursi. Tanpa memakai sum, len, atau max bawaan, tulis fungsi rekursif jumlah, banyak, dan terbesar untuk NILAI, lalu hitung rata-rata. Tampilkan juga nilai yang lulus (≥ 60) secara terurut menggunakan merge_sort rekursif.
15
2. Himpunan. Dari PEMINAT, tentukan: (a) mahasiswa yang berminat pada ketiga bidang, (b) mahasiswa yang hanya berminat pada satu bidang, (c) seluruh mahasiswa, (d) pasangan bidang yang tidak memiliki peminat bersama.
20
3. Struktur data list. Dengan cons list dari daftar.py, tulis sisip_terurut(xs, x) dan insertion_sort(xs), lalu urutkan NILAI. Tulis juga hapus_duplikat(xs) untuk cons list.
25
4. Binary Search Tree. Bangun BST dari NIM_MASUK sesuai urutan. (a) Gambarkan pohonnya. (b) Tampilkan hasil inorder dan tinggi pohon. (c) Cari NIM "2304070" dan "2304055". (d) Bangun pohon AVL dari NIM yang sama dalam urutan terurut, lalu bandingkan tingginya dengan BST biasa dari data terurut.
15
5. Pohon n-ary. Modelkan menu aplikasi berikut sebagai pohon n-ary: Beranda → (Profil → (Ubah Profil, Ganti Password), Akademik → (KRS, KHS, Transkrip), Keluar). Tulis fungsi untuk menghitung jumlah menu, kedalaman maksimum, daftar menu yang tidak memiliki submenu, dan jalur menuju "KHS".
10
6. Analisis. Jelaskan pilihan struktur data (tuple/list, set, cons list, BST/AVL) yang tepat untuk: (a) daftar riwayat transaksi yang terus bertambah, (b) pemeriksaan keanggotaan cepat, (c) pencarian data terurut berdasarkan kunci. Sertakan alasannya.

E. Indikator & Rubrik Penilaian UAS

Indikator RPS: Mahasiswa mampu menerapkan konsep list dan tree dalam pemrograman fungsional.

KriteriaSangat baik (skor penuh)Skor maks.
List integer dan rekursiFungsi rekursif benar, memiliki kasus dasar, hasil tepat15
HimpunanOperasi himpunan tepat dan ringkas15
Struktur data listOperasi cons list benar dan immutable20
BST dan pohon seimbangGambar pohon, penelusuran, pencarian, dan perbandingan tinggi benar25
Pohon n-aryPemodelan tepat dan semua fungsi benar15
AnalisisPemilihan struktur data disertai alasan kompleksitas10

F. Refleksi Akhir Semester