2Python untuk AI

Data Structures

8 jam18 min baca
Tujuan

Kuasai list, tuple, dict, set, dan comprehension. Ini **fondasi** semua data manipulation di Python — termasuk preprocessing data ML nanti.

04 — Data Structures

Estimasi: 8 jam Tujuan: Kuasai list, tuple, dict, set, dan comprehension. Ini fondasi semua data manipulation di Python — termasuk preprocessing data ML nanti.


Kenapa Materi Ini Penting?

Setiap dataset ML/AI yang akan kamu olah pada akhirnya direpresentasikan sebagai struktur data: list of records, dict of features, set of unique tokens. NumPy array, Pandas DataFrame, PyTorch tensor — semuanya dibangun di atas konsep yang dipelajari di file ini. Kalau kamu lancar pakai list comprehension dan dict, transisi ke vectorized operation di Fase 4 akan terasa natural.

Lebih jauh: pemilihan struktur data yang tepat = perbedaan antara kode yang jalan 1 detik vs 10 menit. Cek "kata haram" di list 100rb element vs set: O(n) vs O(1). Saat dataset jadi besar (jutaan baris), pilihan ini menentukan apakah training kamu jalan dalam menit atau jam.

Analogi besar: struktur data = wadah penyimpanan dengan karakteristik beda.

  • List = rak buku berurutan (boleh duplikat, ada urutan).
  • Tuple = album foto cetak (sekali jadi tidak bisa diubah).
  • Dict = laci dengan label (akses cepat by nama).
  • Set = kantong kelereng (unik, tidak peduli urutan).

Peta Konsep

flowchart TD
    A[📦 Data Structures] --> B[📋 List]
    A --> C[🔒 Tuple]
    A --> D[🗂️ Dict]
    A --> E[🎯 Set]
    A --> F[✨ Comprehensions]

    B --> B1[Mutable, ordered]
    C --> C1[Immutable, ordered]
    D --> D1[Key-value, fast lookup]
    E --> E1[Unique, unordered]

    F --> F1[List comp]
    F --> F2[Dict comp]
    F --> F3[Set comp]
    F --> F4[Generator expr]

Tabel Karakteristik Cepat

Cara Membaca Diagram

Decision tree dengan 2 pertanyaan kunci. Mulai dari root "Pilih Struktur Data", jawab pertanyaan, ikuti edge sesuai jawaban, sampai ke leaf node (tipe konkret). Setiap path ke leaf adalah kombinasi karakteristik unik.

Walkthrough Step-by-Step

  1. Tanya 1: Urutan penting?
    • Ya → lanjut ke tanya 2 (mutable?)
    • Tidak → lanjut ke tanya tentang key
  2. Tanya 2: Boleh diubah (mutable)?
    • Ya → list [1, 2, 3]
    • Tidak → tuple (1, 2, 3)
  3. Tanya: Akses by key?
    • Ya → dict {"key": "value"}
    • Tidak → set {1, 2, 3} (cuma unik values)
  4. Pilih sesuai use case: data dinamis = list, record fixed = tuple, lookup cepat = dict, dedup = set.

Analogi Sehari-hari

Pilih wadah penyimpanan di rumah. List = rak buku berurutan (boleh tambah/buang, ada urutan). Tuple = album foto cetak (sudah jadi, tidak bisa diubah). Dict = laci dengan label nama (akses cepat by nama). Set = kantong kelereng (tidak peduli urutan, tidak ada duplikat). Pilih berdasarkan kebutuhan akses, bukan kebiasaan.

Diagram statis Mermaid sebagai fallback:

flowchart LR
    subgraph Pilih
        Q1{Urutan penting?}
        Q2{Boleh diubah?}
        Q3{Akses by key?}
    end
    Q1 -->|Ya| Q2
    Q1 -->|Tidak| Q3
    Q2 -->|Ya| L[📋 list]
    Q2 -->|Tidak| T[🔒 tuple]
    Q3 -->|Ya| D[🗂️ dict]
    Q3 -->|Tidak| S[🎯 set]

Bagian 1 — List

List = collection terurut, mutable, boleh duplikat.

Bikin List

kosong = []
angka = [1, 2, 3, 4, 5]
campuran = [1, "dua", 3.0, True, None]
nested = [[1, 2], [3, 4], [5, 6]]

Visualisasi: List Indexing

Cara Membaca Diagram

Layer atas = root list dengan referensi ke 4 element (indexes 0-3). Layer tengah = index positif dengan emoji buah. Layer bawah = index negatif (-1 ke last, -4 ke first). Edge dashed dari index negatif → index positif menunjukkan ekuivalensi. Sisi kanan: methods dan slicing.

Walkthrough Step-by-Step

  1. List = ["apel", "pisang", "mangga", "jeruk"] — 4 element berurutan.
  2. Index positif mulai dari 0 (apel) sampai 3 (jeruk). Akses: buah[0] = "apel".
  3. Index negatif mulai dari -1 (jeruk = last) sampai -4 (apel = first). Akses: buah[-1] = "jeruk".
  4. Methods.append(), .pop(), .sort() untuk modifikasi in-place.
  5. Slicingbuah[1:3] = ["pisang", "mangga"]. Range [start:end] dengan end exclusive.

Analogi Sehari-hari

List = deretan rumah di gang. Rumah pertama = rumah ke-0 (Python style). Kalau bilang "rumah terakhir" = rumah ke-(-1), tidak perlu hitung total dulu. Slicing = "ambil rumah ke-1 sampai sebelum ke-3" (rumah 1 dan 2). Append = bangun rumah baru di ujung. Pop = pindahkan penghuni rumah terakhir.

Diagram statis Mermaid sebagai fallback:

flowchart LR
    subgraph List
        i0["[0]<br/>apel"]
        i1["[1]<br/>pisang"]
        i2["[2]<br/>mangga"]
        i3["[3]<br/>jeruk"]
    end
    subgraph "Index Negatif"
        n4["[-4]"] -.-> i0
        n3["[-3]"] -.-> i1
        n2["[-2]"] -.-> i2
        n1["[-1]"] -.-> i3
    end

Akses Element (Indexing)

buah = ["apel", "pisang", "mangga", "jeruk"]

buah[0]     # "apel"     (indexing dari 0)
buah[1]     # "pisang"
buah[-1]    # "jeruk"    (negatif = dari belakang)
buah[-2]    # "mangga"

Slicing

Sintaks: list[start:end:step]

buah = ["apel", "pisang", "mangga", "jeruk", "anggur"]

buah[1:4]      # ["pisang", "mangga", "jeruk"]      (index 1-3)
buah[:3]       # ["apel", "pisang", "mangga"]        (dari awal)
buah[2:]       # ["mangga", "jeruk", "anggur"]       (sampai akhir)
buah[:]        # full copy
buah[::2]      # ["apel", "mangga", "anggur"]        (step 2)
buah[::-1]     # ["anggur", ..., "apel"]             (reverse)

Penting: slicing end tidak inclusive. buah[1:4] = index 1, 2, 3 (bukan 4).

Method List Penting

buah = ["apel", "pisang"]

# Tambah
buah.append("mangga")           # ["apel", "pisang", "mangga"]
buah.insert(0, "anggur")        # ["anggur", "apel", "pisang", "mangga"]
buah.extend(["jeruk", "lemon"]) # tambah multiple

# Hapus
buah.remove("apel")             # hapus berdasarkan value
hilang = buah.pop()             # hapus & return last
hilang = buah.pop(0)            # hapus & return index 0
del buah[0]                     # hapus by index, no return

# Pencarian
buah.index("pisang")            # cari index
"pisang" in buah                # True/False
buah.count("pisang")            # berapa kali muncul

# Manipulasi
buah.sort()                     # in-place sort (modify list)
buah.sort(reverse=True)         # descending
sorted_buah = sorted(buah)      # return new list, original tidak berubah
buah.reverse()                  # in-place reverse

# Length
len(buah)                       # jumlah element

List Methods: In-place vs Return

Method Modifikasi list? Return?
.append() Ya None
.sort() Ya None
.reverse() Ya None
sorted() Tidak List baru
reversed() Tidak Iterator

Bug umum: lst = lst.sort() akan bikin lst = None. Pakai lst.sort() saja, atau lst = sorted(lst).

Iterasi List

buah = ["apel", "pisang", "mangga"]

# Cara biasa
for item in buah:
    print(item)

# Dengan index (enumerate)
for i, item in enumerate(buah):
    print(f"{i}: {item}")

# Reverse
for item in reversed(buah):
    print(item)

Copy List (Penting!)

a = [1, 2, 3]
b = a              # ❌ INI BUKAN COPY! b dan a referensi yang SAMA
b.append(4)
print(a)           # [1, 2, 3, 4] — modified!

# Cara copy yang benar
b = a.copy()
# atau
b = a[:]
# atau
b = list(a)

import copy
b = copy.deepcopy(a)   # untuk nested list

Bagian 2 — Tuple

Tuple = list immutable (tidak bisa diubah).

Bikin Tuple

kosong = ()
satu = (1,)              # WAJIB pakai koma untuk single-element tuple
beberapa = (1, 2, 3)
tanpa_kurung = 1, 2, 3   # juga tuple

# Mixed
data = ("Budi", 25, "Bandung")

Akses dan Slicing — Sama dengan List

data = ("Budi", 25, "Bandung")
data[0]          # "Budi"
data[1:]         # (25, "Bandung")

Tuple Tidak Bisa Diubah

data = (1, 2, 3)
data[0] = 99     # ❌ TypeError!
data.append(4)   # ❌ TypeError!

Tuple Unpacking

data = ("Budi", 25, "Bandung")
nama, umur, kota = data    # unpacking

# Swap variabel
a, b = 10, 20
a, b = b, a               # Pythonic swap

# Skip dengan _
_, umur, _ = data

Kapan Pakai Tuple vs List?

Pakai Tuple kalau... Pakai List kalau...
Data tidak akan diubah Akan tambah/hapus/edit
Heterogen (tipe campur) Homogen (semua tipe sama)
Sebagai key dict Iterate banyak
Return multiple values Collect data dinamis
# Tuple untuk "record"
user = ("Budi", 25, "Bandung")

# List untuk "collection"
buah_list = ["apel", "pisang", "mangga"]

Bagian 3 — Dictionary (Dict)

Visualisasi Dict

flowchart LR
    subgraph "🗂️ user dict"
        K1["'nama'"] --> V1["'Budi'"]
        K2["'umur'"] --> V2["25"]
        K3["'kota'"] --> V3["'Bandung'"]
    end

Analogi: dict = buku alamat. Kamu cari "Budi" → langsung dapat nomor HP. Tidak perlu scan satu per satu kayak list.

Dict = key-value pairs. Mirip JSON object atau hash map.

Bikin Dict

kosong = {}
user = {"nama": "Budi", "umur": 25, "kota": "Bandung"}

# Bikin dari list of tuple
data = dict([("a", 1), ("b", 2)])

# Bikin dengan keyword
data = dict(a=1, b=2)

Akses

user = {"nama": "Budi", "umur": 25}

user["nama"]              # "Budi"
user["email"]             # ❌ KeyError!

user.get("nama")          # "Budi"
user.get("email")         # None (tidak error)
user.get("email", "N/A")  # "N/A" (default value)

Modifikasi

user = {"nama": "Budi"}

# Tambah / update
user["umur"] = 25
user["nama"] = "Ani"      # update existing

# Hapus
del user["umur"]
hilang = user.pop("nama")     # hapus & return
hilang = user.pop("xyz", None) # default kalau tidak ada

# Update multiple
user.update({"a": 1, "b": 2})

Iterasi

data = {"a": 1, "b": 2, "c": 3}

# Loop key
for key in data:
    print(key)

# Loop value
for value in data.values():
    print(value)

# Loop key + value (paling sering)
for key, value in data.items():
    print(f"{key}: {value}")

Cek Existence

"a" in data           # True
"z" in data           # False
"z" not in data       # True

Method Penting

data.keys()           # dict_keys(['a', 'b', 'c'])
data.values()         # dict_values([1, 2, 3])
data.items()          # dict_items([('a', 1), ('b', 2), ('c', 3)])

list(data.keys())     # ['a', 'b', 'c']  ← convert ke list

Nested Dict

users = {
    "user1": {"nama": "Budi", "umur": 25},
    "user2": {"nama": "Ani", "umur": 30},
}

users["user1"]["nama"]    # "Budi"

Dict untuk Counting (Pattern Umum)

text = "apel pisang apel mangga apel pisang"
words = text.split()

count = {}
for word in words:
    count[word] = count.get(word, 0) + 1

print(count)
# {'apel': 3, 'pisang': 2, 'mangga': 1}

# Cara lebih Pythonic (collections.Counter)
from collections import Counter
count = Counter(words)
print(count)

Bagian 4 — Set

Visualisasi: Operasi Set

flowchart LR
    subgraph A["Set A"]
        a1[1]
        a2[2]
        a3[3]
        a4[4]
    end
    subgraph B["Set B"]
        b3[3]
        b4[4]
        b5[5]
        b6[6]
    end
    A --> U["A ∪ B<br/>{1,2,3,4,5,6}"]
    B --> U
    A --> I["A ∩ B<br/>{3,4}"]
    B --> I
    A --> D["A - B<br/>{1,2}"]

Analogi: set = kantong kelereng. Kalau kamu masukkan kelereng yang sama dua kali, di kantong cuma ada satu. Tidak ada urutan, tidak ada duplikat.

Set = collection unik, tidak terurut, mutable.

Bikin Set

kosong = set()           # WAJIB pakai set(), {} = dict
angka = {1, 2, 3, 4, 5}
duplikat = {1, 1, 2, 2, 3}   # {1, 2, 3} — duplikat dihapus

Operasi Set

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

a | b      # union: {1, 2, 3, 4, 5, 6}
a & b      # intersection: {3, 4}
a - b      # difference: {1, 2}
a ^ b      # symmetric diff: {1, 2, 5, 6}

# Method equivalent
a.union(b)
a.intersection(b)
a.difference(b)
a.symmetric_difference(b)

Method

s = {1, 2, 3}
s.add(4)              # {1, 2, 3, 4}
s.remove(2)           # {1, 3, 4} — error kalau tidak ada
s.discard(99)         # {1, 3, 4} — no error
s.pop()               # remove random element

5 in s                # False
len(s)                # 3

Use Case: Hapus Duplikat

data = [1, 2, 2, 3, 4, 4, 5]
unique = list(set(data))
print(unique)             # [1, 2, 3, 4, 5] (urutan tidak dijamin)

Bagian 5 — Comprehensions

Diagram: Anatomi List Comprehension

flowchart LR
    I[📋 iterable<br/>range/list] --> F{filter:<br/>if condition?}
    F -->|Lulus| T[🔧 transform:<br/>expression]
    F -->|Skip| X[❌]
    T --> O[📦 list output]

Analogi: comprehension = conveyor belt pabrik.

  1. Item lewat conveyor (iterable)
  2. Inspector cek (if condition)
  3. Lulus → masuk mesin transform
  4. Hasilnya jatuh ke kotak output

Cara Pythonic bikin list/dict/set dari sequence.

List Comprehension

Bentuk: [expression for item in iterable if condition]

# Tanpa comprehension
kuadrat = []
for i in range(10):
    kuadrat.append(i ** 2)

# Dengan comprehension (Pythonic)
kuadrat = [i ** 2 for i in range(10)]

Filter dengan Condition

nilai = [85, 60, 75, 90, 55]

# Cara biasa
lulus = []
for n in nilai:
    if n >= 70:
        lulus.append(n)

# Comprehension
lulus = [n for n in nilai if n >= 70]

If-Else dalam Comprehension

nilai = [85, 60, 75, 90, 55]
status = ["lulus" if n >= 70 else "gagal" for n in nilai]
print(status)   # ['lulus', 'gagal', 'lulus', 'lulus', 'gagal']

Catatan: if-else di awal untuk transformasi, if di akhir untuk filter. Bisa kombinasi keduanya.

Nested Comprehension

# Flatten 2D list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for row in matrix for x in row]
print(flat)   # [1, 2, 3, 4, 5, 6, 7, 8, 9]

Dict Comprehension

# Dict dari list
nama = ["Budi", "Ani", "Cici"]
panjang = {n: len(n) for n in nama}
print(panjang)   # {'Budi': 4, 'Ani': 3, 'Cici': 4}

# Filter
nilai = {"Budi": 85, "Ani": 60, "Cici": 75}
lulus = {k: v for k, v in nilai.items() if v >= 70}
print(lulus)     # {'Budi': 85, 'Cici': 75}

# Inversi
inversi = {v: k for k, v in nilai.items()}

Set Comprehension

# Set kuadrat unik
data = [1, 2, 2, 3, 3, 4]
kuadrat_unik = {x ** 2 for x in data}
print(kuadrat_unik)    # {1, 4, 9, 16}

Generator Expression (Bonus)

Mirip list comprehension tapi pakai () — return generator (lazy, hemat memori):

# Sum 1 sampai 1 juta
sum([i for i in range(1_000_000)])     # bikin list dulu (boros memori)
sum(i for i in range(1_000_000))       # generator (hemat)

Bagian 6 — Performance & Big-O

Penting di AI: dataset besar, performance matters.

Operasi List Dict Set
Akses by index O(1) - -
Akses by key - O(1) -
x in collection O(n) O(1) O(1)
Append/add O(1) O(1) O(1)
Remove O(n) O(1) O(1)
Search O(n) O(1) O(1)

Aturan praktis:

  • Membership check (x in lst) sering? → pakai set atau dict
  • Cari by key? → dict
  • Urutan penting? → list

Contoh Optimasi

# Cek kata haram (10000 kata)
kata_haram = ["spam", "scam", ...]   # list

text = "ini adalah spam jelas"

# JELEK — O(n×m), lambat untuk dataset besar
for kata in text.split():
    if kata in kata_haram:           # O(n) tiap iterasi
        print("spam!")

# BAIK — convert ke set sekali
kata_haram_set = set(kata_haram)
for kata in text.split():
    if kata in kata_haram_set:       # O(1) tiap iterasi
        print("spam!")

Common Mistakes & FAQ

❌ Mistake 1: b = a bukan copy

a = [1, 2, 3]
b = a              # SAMA referensi
b.append(4)
print(a)           # [1, 2, 3, 4] — ikut berubah!

# Fix:
b = a.copy()       # atau a[:] atau list(a)

❌ Mistake 2: {} itu dict, bukan set kosong

empty = {}              # ❌ ini dict
empty = set()           # ✅ set kosong

s = {1, 2, 3}           # ✅ set dengan element

❌ Mistake 3: Modify dict saat iterate

data = {"a": 1, "b": 2, "c": 3}
for k in data:
    if data[k] < 2:
        del data[k]    # RuntimeError: dict changed size during iteration

# Fix:
for k in list(data.keys()):    # iterate atas copy keys
    if data[k] < 2:
        del data[k]

❌ Mistake 4: Mutable as dict key

d = {[1, 2]: "value"}    # ❌ TypeError: unhashable type: 'list'

# Fix: pakai tuple
d = {(1, 2): "value"}    # ✅

Hanya immutable type bisa jadi dict key (str, int, float, tuple, frozenset).

❌ Mistake 5: Tuple satu element tanpa koma

t = (5)        # ini int 5, BUKAN tuple!
t = (5,)       # ini tuple
print(type(t)) # <class 'tuple'>

❌ Mistake 6: List comprehension overkill

# Susah dibaca, nested 3x
result = [x for sub in data for item in sub for x in item if x > 0]

# Pakai for loop biasa kalau lebih clear
result = []
for sub in data:
    for item in sub:
        for x in item:
            if x > 0:
                result.append(x)

FAQ

Q: Kapan pakai list vs tuple? A:

  • List = collection homogen yang akan diubah (data dinamis)
  • Tuple = record heterogen yang fixed (return multiple values, dict key)

Q: Kenapa dict.get() lebih baik dari dict[key]? A: .get() tidak error kalau key tidak ada — return None (atau default yang kamu kasih). Aman untuk handle key opsional.

Q: Set vs list untuk dedup? A: Set jauh lebih cepat untuk dedup (list(set(data))) dan untuk membership check (x in s). Tapi set tidak preserve urutan. Kalau urutan penting, pakai dict.fromkeys(data) (Python 3.7+).

Q: Generator expression vs list comprehension? A:

  • List comp [...] — buat list lengkap di memori. Cepat akses ulang.
  • Generator expr (...) — lazy, hemat memori. Cuma bisa iterate sekali.

Untuk data besar yang cuma di-iterate sekali (sum, max, loop), pakai generator.

Q: Dict di Python 3.7+ ordered? A: Ya, sejak Python 3.7 dict mempertahankan insertion order. Tapi semantically dict tetap "unordered collection of key-value pairs" — tidak bergantung urutan untuk operasi.


Cek Pemahaman

  • Tahu beda list, tuple, dict, set?
  • Bisa slice list dengan step?
  • Tahu kapan pakai .get() vs ["key"] di dict?
  • Bisa nested dict access?
  • Bisa list comprehension dengan filter?
  • Tahu kenapa cek membership di set lebih cepat dari list?
  • Tahu pitfall b = a (shallow reference)?

Challenge 2.4

Challenge 1 — Inventory System

inventory = [
    {"nama": "Buku", "harga": 50000, "stok": 10},
    {"nama": "Pulpen", "harga": 5000, "stok": 100},
    {"nama": "Pensil", "harga": 3000, "stok": 50},
]

Bikin function:

  1. total_value(inv) — total harga × stok semua barang
  2. cari_barang(inv, nama) — return dict barang atau None
  3. barang_habis(inv) — list barang dengan stok < 5
  4. update_stok(inv, nama, delta) — tambah/kurangi stok

Challenge 2 — Word Frequency

Input: paragraph text Output: dict {kata: frekuensi}

Bonus: top 5 kata terbanyak (pakai sorted + lambda).

Challenge 3 — Anagram Detector

Cek apakah dua string adalah anagram (huruf sama, urutan beda):

is_anagram("listen", "silent")   # True
is_anagram("hello", "world")     # False

Hint: convert ke set atau sorted list.

Challenge 4 — Matrix Operations

matrix_a = [[1, 2], [3, 4]]
matrix_b = [[5, 6], [7, 8]]

Bikin function:

  1. transpose(m) — transpose matrix (baris jadi kolom)
  2. add_matrix(a, b) — penjumlahan element-wise
  3. flatten(m) — convert 2D ke 1D list

Pakai list comprehension.

Challenge 5 — Student Grades

students = {
    "Budi": [85, 90, 78],
    "Ani": [60, 70, 65],
    "Cici": [95, 92, 98],
}

Bikin:

  1. Dict {nama: avg} — rata-rata tiap siswa
  2. Nama siswa dengan avg tertinggi
  3. Dict siswa lulus (avg >= 70)
  4. Subject report — list of (siswa, subject_idx, nilai) untuk semua nilai >= 80

Challenge 6 — Set Operations

kelas_a = {"Budi", "Ani", "Cici", "Doni", "Eka"}
kelas_b = {"Cici", "Doni", "Fani", "Gugun"}

Cari:

  1. Siswa di kelas A saja
  2. Siswa di kelas B saja
  3. Siswa di kedua kelas
  4. Siswa di salah satu (tapi tidak keduanya)
  5. Total siswa unik

Challenge 7 — JSON-like Data Munging

orders = [
    {"user": "Budi", "items": [{"name": "Buku", "qty": 2, "price": 50000}]},
    {"user": "Ani", "items": [
        {"name": "Pulpen", "qty": 5, "price": 5000},
        {"name": "Pensil", "qty": 3, "price": 3000},
    ]},
    {"user": "Budi", "items": [{"name": "Pulpen", "qty": 10, "price": 5000}]},
]

Hitung:

  1. Total transaksi tiap user
  2. Item terlaris (jumlah terbanyak)
  3. User dengan spending tertinggi

Challenge 8 — Comprehension Master

Pakai comprehension untuk:

  1. Bikin dict {x: x**2 for x in range(10) if x % 2 == 0}
  2. Bikin list pasangan (i, j) untuk semua kombinasi i in 1-3, j in 1-3, dengan i != j
  3. Hitung dict {huruf: jumlah} dari string, hanya untuk huruf yang muncul ≥ 2 kali
  4. Convert list of dicts jadi dict of lists:
    [{"a": 1, "b": 2}, {"a": 3, "b": 4}] → {"a": [1, 3], "b": [2, 4]}
    

Selanjutnya: 05-string-handling.md — string manipulation, formatting, dan regex dasar. Krusial untuk text processing di NLP/LLM.