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 pola if 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+.
  • defaultdict menghilangkan pola if key not in d: d[key] = [] — cukup langsung gunakan kunci tersebut.
  • Counter untuk menghitung frekuensi, mendukung operasi aritmatika antar counter dan .most_common(n).
  • TypedDict untuk dict dengan struktur tetap yang perlu divalidasi oleh type checker.
  • key in dict adalah O(1) — jauh lebih cepat dari key in list yang 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 fungsi deep_get helper.

← Sebelumnya: List   Berikutnya: Date & Time →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact