Dictionary #
Dictionary adalah struktur data inti Python untuk menyimpan pasangan kunci-nilai (key-value pairs) dengan akses O(1) berdasarkan kunci. Sejak Python 3.7, dictionary menjamin urutan penyisipan — artinya saat kamu iterasi, elemen keluar dalam urutan yang sama seperti saat dimasukkan. Dictionary sangat serbaguna: digunakan untuk konfigurasi, caching, pengelompokan data, counting, dan sebagai alternatif ringan dari kelas untuk data terstruktur. Memahami idiom dan varian dictionary yang tepat — defaultdict, Counter, TypedDict — membuat kode kamu jauh lebih ekspresif dan bebas boilerplate.
Membuat Dictionary #
# Cara-cara membuat dictionary
kosong = {}
kosong2 = dict()
# Literal — cara paling umum
pengguna = {
"nama": "Budi Santoso",
"umur": 28,
"email": "[email protected]",
"aktif": True,
}
# Dari keyword arguments — hanya untuk kunci yang valid sebagai identifier Python
konfigurasi = dict(host="localhost", port=5432, ssl=False)
# Dari list of tuples
dari_tuple = dict([("a", 1), ("b", 2), ("c", 3)])
# Dict comprehension
kuadrat = {x: x**2 for x in range(1, 6)}
print(kuadrat) # → {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Dict dari dua list menggunakan zip
kunci = ["nama", "umur", "kota"]
nilai = ["Budi", 28, "Jakarta"]
profil = dict(zip(kunci, nilai))
print(profil) # → {'nama': 'Budi', 'umur': 28, 'kota': 'Jakarta'}
# Inisialisasi dengan nilai default untuk semua kunci
skor_awal = dict.fromkeys(["matematika", "fisika", "kimia"], 0)
print(skor_awal) # → {'matematika': 0, 'fisika': 0, 'kimia': 0}
Mengakses Nilai #
Cara mengakses nilai adalah perbedaan paling penting yang perlu dipahami — ada dua cara, dan masing-masing tepat untuk situasi yang berbeda:
data = {"nama": "Budi", "umur": 28, "kota": "Jakarta"}
# Akses via [] — melempar KeyError jika kunci tidak ada
print(data["nama"]) # → Budi
# print(data["email"]) # → KeyError: 'email'
# Akses via get() — kembalikan None (atau default) jika kunci tidak ada
print(data.get("email")) # → None (tidak error)
print(data.get("email", "kosong")) # → kosong (nilai default eksplisit)
print(data.get("nama", "anonim")) # → Budi (kunci ada, default diabaikan)
# ANTI-PATTERN: akses langsung untuk kunci yang mungkin tidak ada
def tampilkan_profil(pengguna: dict) -> None:
print(pengguna["nama"]) # crash jika "nama" tidak ada
print(pengguna["telepon"]) # hampir pasti crash
# BENAR: gunakan get() dengan default yang masuk akal
def tampilkan_profil(pengguna: dict) -> None:
print(pengguna.get("nama", "Pengguna"))
print(pengguna.get("telepon", "Tidak tersedia"))
# ANTI-PATTERN: pola cek-then-access yang verbose
if "email" in pengguna:
email = pengguna["email"]
else:
email = "[email protected]"
# BENAR: get() lebih ringkas
email = pengguna.get("email", "[email protected]")
Modifikasi Dictionary #
data = {"a": 1, "b": 2}
# Tambah atau update kunci
data["c"] = 3 # tambah kunci baru
data["a"] = 10 # update nilai kunci yang sudah ada
print(data) # → {'a': 10, 'b': 2, 'c': 3}
# setdefault() — tambah kunci HANYA jika belum ada
data.setdefault("d", 0) # tambah 'd': 0 karena belum ada
data.setdefault("a", 99) # 'a' sudah ada, tidak diubah
print(data) # → {'a': 10, 'b': 2, 'c': 3, 'd': 0}
# update() — perbarui dari dict lain atau keyword args
data.update({"e": 5, "f": 6})
data.update(g=7, h=8)
print(data) # → {'a': 10, 'b': 2, 'c': 3, 'd': 0, 'e': 5, 'f': 6, 'g': 7, 'h': 8}
# Menghapus elemen
d = {"a": 1, "b": 2, "c": 3, "d": 4}
nilai_b = d.pop("b") # hapus dan kembalikan nilai: O(1)
print(nilai_b, d) # → 2 {'a': 1, 'c': 3, 'd': 4}
nilai_x = d.pop("x", None) # pop dengan default — tidak error jika tidak ada
print(nilai_x) # → None
item_terakhir = d.popitem() # hapus dan kembalikan pasangan terakhir (LIFO)
print(item_terakhir) # → ('d', 4)
del d["a"] # hapus kunci tertentu tanpa kembalikan nilai
d.clear() # hapus semua elemen
Iterasi Dictionary #
menu = {"nasi goreng": 25000, "mie ayam": 20000, "es teh": 5000}
# Iterasi kunci (default)
for kunci in menu:
print(kunci)
# Iterasi kunci secara eksplisit — sama hasilnya
for kunci in menu.keys():
print(kunci)
# Iterasi nilai
for harga in menu.values():
print(f"Rp{harga:,}")
# Iterasi pasangan kunci-nilai — paling umum digunakan
for nama_menu, harga in menu.items():
print(f"{nama_menu}: Rp{harga:,}")
# → nasi goreng: Rp25,000
# → mie ayam: Rp20,000
# → es teh: Rp5,000
# ANTI-PATTERN: akses nilai via kunci saat iterasi
for kunci in menu:
print(menu[kunci]) # tidak perlu — ada cara lebih baik
# BENAR: gunakan .values() atau .items()
for harga in menu.values():
print(harga)
for nama, harga in menu.items():
print(nama, harga)
Dict Comprehension #
Dict comprehension memungkinkan pembuatan atau transformasi dictionary secara ringkas:
# Transformasi nilai
harga = {"apel": 5000, "jeruk": 8000, "mangga": 12000}
# Terapkan diskon 10%
harga_diskon = {nama: int(h * 0.9) for nama, h in harga.items()}
print(harga_diskon)
# → {'apel': 4500, 'jeruk': 7200, 'mangga': 10800}
# Filter berdasarkan kondisi
mahal = {nama: h for nama, h in harga.items() if h >= 8000}
print(mahal) # → {'jeruk': 8000, 'mangga': 12000}
# Balik kunci dan nilai (invert dict)
kode_negara = {"Indonesia": "ID", "Malaysia": "MY", "Singapura": "SG"}
negara_dari_kode = {v: k for k, v in kode_negara.items()}
print(negara_dari_kode)
# → {'ID': 'Indonesia', 'MY': 'Malaysia', 'SG': 'Singapura'}
# Buat lookup dari list objek
produk_list = [
{"id": 1, "nama": "Laptop"},
{"id": 2, "nama": "Mouse"},
{"id": 3, "nama": "Keyboard"},
]
# Buat dict untuk lookup O(1) berdasarkan id
produk_lookup = {p["id"]: p for p in produk_list}
print(produk_lookup[2]) # → {'id': 2, 'nama': 'Mouse'}
Penggabungan Dictionary #
d1 = {"a": 1, "b": 2}
d2 = {"b": 20, "c": 3} # 'b' ada di keduanya — d2 menang
# update() — modifikasi d1 in-place (Python semua versi)
d1_copy = d1.copy()
d1_copy.update(d2)
print(d1_copy) # → {'a': 1, 'b': 20, 'c': 3}
# {**d1, **d2} — buat dict baru (Python 3.5+)
gabung = {**d1, **d2}
print(gabung) # → {'a': 1, 'b': 20, 'c': 3}
# Operator | — buat dict baru (Python 3.9+) — paling ringkas
gabung = d1 | d2
print(gabung) # → {'a': 1, 'b': 20, 'c': 3}
# Operator |= — update in-place (Python 3.9+)
d1 |= d2
print(d1) # → {'a': 1, 'b': 20, 'c': 3}
# Gabungkan banyak dict dengan urutan prioritas
default = {"debug": False, "timeout": 30, "max_retry": 3}
env = {"timeout": 60}
user = {"debug": True}
# user menang atas env, env menang atas default
config = default | env | user
print(config)
# → {'debug': True, 'timeout': 60, 'max_retry': 3}
Varian Dictionary dari collections
#
defaultdict — Nilai Default Otomatis
#
defaultdict menghilangkan kebutuhan cek “apakah kunci sudah ada” sebelum menggunakannya:
from collections import defaultdict
# ANTI-PATTERN: pola manual yang verbose
kelompok = {}
data = [("A", 1), ("B", 2), ("A", 3), ("C", 4), ("B", 5)]
for kunci, nilai in data:
if kunci not in kelompok:
kelompok[kunci] = [] # inisialisasi manual
kelompok[kunci].append(nilai)
# BENAR: defaultdict menginisialisasi otomatis
kelompok = defaultdict(list) # default factory: list()
for kunci, nilai in data:
kelompok[kunci].append(nilai) # langsung append — tidak perlu cek
print(dict(kelompok)) # → {'A': [1, 3], 'B': [2, 5], 'C': [4]}
# defaultdict dengan berbagai factory
hitung = defaultdict(int) # default: 0
total = defaultdict(float) # default: 0.0
nested = defaultdict(dict) # default: {}
set_unik = defaultdict(set) # default: set()
# Contoh: hitung frekuensi karakter
teks = "mississippi"
freq = defaultdict(int)
for huruf in teks:
freq[huruf] += 1
print(dict(freq))
# → {'m': 1, 'i': 4, 's': 4, 'p': 2}
# Contoh: kelompokkan siswa per nilai
siswa = [("Budi", "A"), ("Ani", "B"), ("Citra", "A"), ("Dedi", "B"), ("Eko", "C")]
per_nilai = defaultdict(list)
for nama, nilai in siswa:
per_nilai[nilai].append(nama)
print(dict(per_nilai))
# → {'A': ['Budi', 'Citra'], 'B': ['Ani', 'Dedi'], 'C': ['Eko']}
Counter — Hitung Frekuensi
#
Counter adalah subkelas dict yang dioptimalkan untuk menghitung kemunculan elemen:
from collections import Counter
# Hitung dari iterable
kata = ["apel", "jeruk", "apel", "mangga", "jeruk", "apel"]
hitungan = Counter(kata)
print(hitungan)
# → Counter({'apel': 3, 'jeruk': 2, 'mangga': 1})
# Hitung karakter dalam string
huruf = Counter("abracadabra")
print(huruf)
# → Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
# N elemen paling sering muncul
print(huruf.most_common(3))
# → [('a', 5), ('b', 2), ('r', 2)]
# Operasi aritmatika antar Counter
c1 = Counter({"apel": 3, "jeruk": 2})
c2 = Counter({"apel": 1, "mangga": 4})
print(c1 + c2) # → Counter({'mangga': 4, 'apel': 4, 'jeruk': 2})
print(c1 - c2) # → Counter({'jeruk': 2, 'apel': 2})
print(c1 & c2) # → Counter({'apel': 1}) (minimum)
print(c1 | c2) # → Counter({'mangga': 4, 'apel': 3, 'jeruk': 2}) (maksimum)
# Update counter
hitungan.update(["apel", "mangga"])
hitungan["jeruk"] += 5
ChainMap — Gabungkan Beberapa Dict sebagai Satu View
#
ChainMap menggabungkan beberapa dictionary menjadi satu tampilan tanpa menyalin data:
from collections import ChainMap
default_config = {"debug": False, "timeout": 30, "theme": "light"}
user_config = {"debug": True, "theme": "dark"}
env_config = {"timeout": 60}
# Pencarian: env_config → user_config → default_config
config = ChainMap(env_config, user_config, default_config)
print(config["debug"]) # → True (dari user_config)
print(config["timeout"]) # → 60 (dari env_config)
print(config["theme"]) # → dark (dari user_config)
# Modifikasi hanya mempengaruhi map pertama
config["baru"] = "nilai"
print(env_config) # → {'timeout': 60, 'baru': 'nilai'}
TypedDict — Dictionary dengan Type Safety
#
TypedDict (Python 3.8+) memungkinkan mendefinisikan tipe eksak untuk setiap kunci, sehingga type checker bisa memvalidasi penggunaan:
from typing import TypedDict, Optional
class Pengguna(TypedDict):
nama: str
email: str
umur: int
telepon: Optional[str] # boleh None
class PenggunaParsial(TypedDict, total=False):
# total=False: semua kunci menjadi opsional
nama: str
email: str
# Type checker memvalidasi ini
pengguna: Pengguna = {
"nama": "Budi",
"email": "[email protected]",
"umur": 28,
"telepon": None,
}
# mypy akan menandai ini sebagai error:
# pengguna["gaji"] = 5000 # Extra key 'gaji' not allowed
# pengguna["umur"] = "28" # 'str' incompatible with 'int'
Pola-pola Idiomatis #
Grouping Data #
# Kelompokkan transaksi berdasarkan kategori
transaksi = [
{"kategori": "makan", "jumlah": 50000},
{"kategori": "transport", "jumlah": 30000},
{"kategori": "makan", "jumlah": 75000},
{"kategori": "hiburan", "jumlah": 100000},
{"kategori": "transport", "jumlah": 25000},
]
from collections import defaultdict
per_kategori = defaultdict(list)
for t in transaksi:
per_kategori[t["kategori"]].append(t["jumlah"])
total_per_kategori = {k: sum(v) for k, v in per_kategori.items()}
print(total_per_kategori)
# → {'makan': 125000, 'transport': 55000, 'hiburan': 100000}
Caching Hasil Komputasi (Memoization Manual) #
# Simpan hasil kalkulasi mahal agar tidak dihitung ulang
_cache_fibonacci: dict[int, int] = {}
def fibonacci(n: int) -> int:
if n in _cache_fibonacci:
return _cache_fibonacci[n]
if n <= 1:
return n
hasil = fibonacci(n - 1) + fibonacci(n - 2)
_cache_fibonacci[n] = hasil
return hasil
# Atau gunakan functools.lru_cache (lebih direkomendasikan)
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci_lru(n: int) -> int:
if n <= 1:
return n
return fibonacci_lru(n - 1) + fibonacci_lru(n - 2)
Nested Dictionary #
# Akses nested dict yang aman
config = {
"database": {
"host": "localhost",
"port": 5432,
}
}
# ANTI-PATTERN: akses langsung — crash jika kunci tidak ada di manapun
host = config["database"]["host"] # aman hanya jika tahu struktur pasti
port = config["server"]["port"] # KeyError: 'server'
# BENAR: gunakan get() berantai
host = config.get("database", {}).get("host", "localhost")
port = config.get("server", {}).get("port", 8080)
# Untuk nested yang lebih dalam, pertimbangkan fungsi helper
def deep_get(d: dict, *kunci, default=None):
"""Akses nested dict secara aman."""
for k in kunci:
if not isinstance(d, dict):
return default
d = d.get(k, {})
return d if d != {} else default
print(deep_get(config, "database", "host")) # → localhost
print(deep_get(config, "server", "port", default=8080)) # → 8080
Kompleksitas Waktu Operasi Dictionary #
Operasi │ Kompleksitas │ Catatan
───────────────────────┼──────────────┼─────────────────────────────
d[key] │ O(1) avg │ worst case O(n) — hash collision
d[key] = val │ O(1) avg │
del d[key] │ O(1) avg │
key in d │ O(1) avg │ jauh lebih cepat dari list!
d.get(key) │ O(1) avg │
d.keys() │ O(1) │ kembalikan view, bukan list
d.values() │ O(1) │ kembalikan view, bukan list
d.items() │ O(1) │ kembalikan view, bukan list
for k in d │ O(n) │ iterasi semua kunci
d.copy() │ O(n) │ shallow copy
d | d2 │ O(n+m) │ buat dict baru
Operasi O(1) untuk kunci adalah keunggulan utama dict dibanding list. Gunakan dict jika kamu perlu lookup cepat berdasarkan identifier — misalnya cache, index, atau konfigurasi.
Ringkasan #
- Gunakan
.get(key, default)untuk kunci yang mungkin tidak ada — lebih aman dan ringkas dari polaif key in d: ... else: ....dict.fromkeys(keys, default)untuk inisialisasi dict dengan nilai default seragam untuk semua kunci.setdefault(key, default)menambah kunci hanya jika belum ada — berguna untuk inisialisasi lazy.- Operator
|(Python 3.9+) adalah cara paling ringkas menggabungkan dua dict. Gunakan{**d1, **d2}untuk kompatibilitas Python 3.5+.defaultdictmenghilangkan polaif key not in d: d[key] = []— cukup langsung gunakan kunci tersebut.Counteruntuk menghitung frekuensi, mendukung operasi aritmatika antar counter dan.most_common(n).TypedDictuntuk dict dengan struktur tetap yang perlu divalidasi oleh type checker.key in dictadalah O(1) — jauh lebih cepat darikey in listyang O(n). Gunakan dict/set untuk lookup berulang.- Dict comprehension untuk transformasi dan filter dict — lebih ekspresif dari loop manual.
- Jangan akses nested dict dengan
[]berantai tanpa jaminan struktur — gunakan.get()berantai atau fungsideep_gethelper.