List #

List adalah struktur data paling sering digunakan di Python — urutan elemen yang terurut, bisa diubah (mutable), dan bisa menyimpan elemen dari tipe yang berbeda. Karena fleksibilitasnya, list sering digunakan bahkan saat struktur data lain sebenarnya lebih tepat. Memahami list secara mendalam — termasuk kompleksitas waktu setiap operasi, jebakan shallow copy, dan kapan beralih ke deque atau struktur lain — membuat kode kamu lebih efisien dan bebas bug tersembunyi.

Membuat List #

# Cara-cara membuat list
kosong = []
kosong2 = list()

angka = [1, 2, 3, 4, 5]
campuran = [42, "hello", 3.14, True, None]   # boleh tipe berbeda
bersarang = [[1, 2], [3, 4], [5, 6]]          # list di dalam list

# Dari iterable lain
dari_range = list(range(1, 6))       # → [1, 2, 3, 4, 5]
dari_string = list("Python")         # → ['P', 'y', 't', 'h', 'o', 'n']
dari_tuple = list((1, 2, 3))         # → [1, 2, 3]
dari_set = sorted(list({3, 1, 2}))   # → [1, 2, 3]

# List comprehension
kuadrat = [x**2 for x in range(1, 6)]          # → [1, 4, 9, 16, 25]
genap = [x for x in range(10) if x % 2 == 0]  # → [0, 2, 4, 6, 8]

# Inisialisasi list dengan nilai default
nol_sepuluh = [0] * 10           # → [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
matriks = [[0] * 3 for _ in range(3)]  # → [[0,0,0], [0,0,0], [0,0,0]]
# ANTI-PATTERN: inisialisasi matriks dengan perkalian list bersarang
# ANTI-PATTERN: matriks = [[0] * 3] * 3
# Semua baris menunjuk ke OBJEK YANG SAMA!
matriks_salah = [[0] * 3] * 3
matriks_salah[0][0] = 9
print(matriks_salah)   # → [[9,0,0], [9,0,0], [9,0,0]] ← semua baris berubah!

# BENAR: gunakan comprehension untuk baris independen
matriks_benar = [[0] * 3 for _ in range(3)]
matriks_benar[0][0] = 9
print(matriks_benar)   # → [[9,0,0], [0,0,0], [0,0,0]] ← hanya baris pertama

Indexing dan Slicing #

List Python mendukung indexing negatif dan slicing yang sangat ekspresif:

buah = ["apel", "jeruk", "mangga", "pisang", "anggur"]
#        0        1         2          3          4       ← indeks positif
#       -5       -4        -3         -2         -1       ← indeks negatif

# Indexing
print(buah[0])    # → apel    (pertama)
print(buah[-1])   # → anggur  (terakhir)
print(buah[-2])   # → pisang  (kedua dari belakang)

# Slicing: [start:stop:step]
print(buah[1:3])    # → ['jeruk', 'mangga']    (indeks 1 dan 2)
print(buah[:3])     # → ['apel', 'jeruk', 'mangga']  (dari awal)
print(buah[2:])     # → ['mangga', 'pisang', 'anggur']  (sampai akhir)
print(buah[::2])    # → ['apel', 'mangga', 'anggur']  (setiap 2)
print(buah[::-1])   # → ['anggur', 'pisang', 'mangga', 'jeruk', 'apel']  (terbalik)
print(buah[1:-1])   # → ['jeruk', 'mangga', 'pisang']  (kecuali pertama & terakhir)
# Slicing menghasilkan list BARU — bukan view seperti NumPy
a = [1, 2, 3, 4, 5]
b = a[1:4]   # salinan elemen indeks 1–3
b[0] = 99
print(a)     # → [1, 2, 3, 4, 5]  ← a tidak berubah

# Slice assignment — ubah bagian list tanpa loop
a = [1, 2, 3, 4, 5]
a[1:3] = [20, 30]        # ganti indeks 1–2
print(a)   # → [1, 20, 30, 4, 5]

a[1:3] = [10, 20, 30]    # ganti 2 elemen dengan 3
print(a)   # → [1, 10, 20, 30, 4, 5]

a[2:4] = []              # hapus elemen indeks 2–3
print(a)   # → [1, 10, 30, 4, 5]

Menambah Elemen #

lst = [1, 2, 3]

# append() — tambah satu elemen di akhir: O(1) amortized
lst.append(4)
print(lst)   # → [1, 2, 3, 4]

# extend() — tambah beberapa elemen dari iterable: O(k)
lst.extend([5, 6, 7])
print(lst)   # → [1, 2, 3, 4, 5, 6, 7]

# insert() — tambah di indeks tertentu: O(n) karena geser elemen
lst.insert(0, 0)    # tambah 0 di depan
print(lst)   # → [0, 1, 2, 3, 4, 5, 6, 7]
# ANTI-PATTERN: + untuk menambah elemen ke list yang ada
lst = [1, 2, 3]
lst = lst + [4]        # membuat list BARU setiap kali — O(n)

for i in range(1000):
    lst = lst + [i]    # sangat lambat untuk loop besar!

# BENAR: append() atau extend() untuk modifikasi in-place — O(1)
lst = [1, 2, 3]
lst.append(4)

hasil = []
for i in range(1000):
    hasil.append(i)    # O(1) per operasi

# Atau lebih baik lagi: list comprehension
hasil = list(range(1000))

Menghapus Elemen #

lst = [10, 20, 30, 20, 40, 20]

# remove() — hapus kemunculan PERTAMA nilai tertentu: O(n)
lst.remove(20)
print(lst)   # → [10, 30, 20, 40, 20]  ← hanya yang pertama dihapus

# pop() — hapus dan kembalikan elemen: O(1) untuk akhir, O(n) untuk tengah
terakhir = lst.pop()      # hapus elemen terakhir → 20
print(terakhir, lst)      # → 20 [10, 30, 20, 40]

elemen = lst.pop(1)       # hapus elemen di indeks 1 → 30
print(elemen, lst)        # → 30 [10, 20, 40]

# del — hapus elemen atau slice: O(n)
lst = [10, 20, 30, 40, 50]
del lst[1]                # hapus indeks 1
print(lst)   # → [10, 30, 40, 50]

del lst[1:3]              # hapus slice
print(lst)   # → [10, 50]

# clear() — hapus semua elemen: O(n)
lst.clear()
print(lst)   # → []
# ANTI-PATTERN: menghapus elemen saat iterasi — melewatkan elemen!
angka = [1, 2, 3, 4, 5, 6]
for n in angka:
    if n % 2 == 0:
        angka.remove(n)   # ← berbahaya! mengubah list saat diiterasi
print(angka)   # → [1, 3, 5]  TAPI bukan karena benar — ada bug tersembunyi
               # Coba dengan [1, 2, 3, 4] → hasilnya [1, 3] bukan [1, 3]

# BENAR: buat list baru (comprehension) atau iterasi salinan
angka = [1, 2, 3, 4, 5, 6]

# Cara 1: list comprehension (paling idiomatis)
angka = [n for n in angka if n % 2 != 0]
print(angka)   # → [1, 3, 5]

# Cara 2: filter()
angka = list(filter(lambda n: n % 2 != 0, [1, 2, 3, 4, 5, 6]))

# Cara 3: iterasi salinan (kurang efisien tapi kadang perlu)
angka = [1, 2, 3, 4, 5, 6]
for n in angka[:]:   # iterasi salinan
    if n % 2 == 0:
        angka.remove(n)
Jangan pernah menambah atau menghapus elemen dari list yang sedang kamu iterasi dengan for. Ini menyebabkan elemen terlewat atau diproses dua kali karena indeks internal bergeser. Selalu buat list baru menggunakan comprehension atau iterasi salinan lst[:].

Pencarian dan Informasi #

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

# Cek keberadaan: O(n)
print("mangga" in buah)      # → True
print("durian" not in buah)  # → True

# index() — indeks kemunculan PERTAMA: O(n), error jika tidak ada
print(buah.index("jeruk"))   # → 1
# buah.index("durian")       # → ValueError: 'durian' is not in list

# Cari dengan aman menggunakan try/except atau cek dulu
if "durian" in buah:
    idx = buah.index("durian")

# count() — hitung kemunculan: O(n)
print(buah.count("jeruk"))   # → 2
print(buah.count("durian"))  # → 0

# Panjang list
print(len(buah))   # → 5

Pengurutan #

angka = [3, 1, 4, 1, 5, 9, 2, 6]

# sort() — urutkan IN-PLACE, kembalikan None: O(n log n)
angka.sort()
print(angka)   # → [1, 1, 2, 3, 4, 5, 6, 9]

angka.sort(reverse=True)
print(angka)   # → [9, 6, 5, 4, 3, 2, 1, 1]

# sorted() — kembalikan list BARU, asli tidak berubah: O(n log n)
asli = [3, 1, 4, 1, 5]
terurut = sorted(asli)
print(asli)     # → [3, 1, 4, 1, 5]  ← tidak berubah
print(terurut)  # → [1, 1, 3, 4, 5]
# Pengurutan dengan key — sangat fleksibel
mahasiswa = [
    {"nama": "Citra", "nilai": 78},
    {"nama": "Ani",   "nilai": 92},
    {"nama": "Budi",  "nilai": 85},
]

# Urutkan berdasarkan nilai (descending)
urut_nilai = sorted(mahasiswa, key=lambda m: m["nilai"], reverse=True)
for m in urut_nilai:
    print(f"{m['nama']}: {m['nilai']}")
# → Ani: 92
# → Budi: 85
# → Citra: 78

# Urutkan berdasarkan beberapa kriteria (nama jika nilai sama)
data = [("Ani", 85), ("Budi", 92), ("Citra", 85), ("Dedi", 92)]
urut = sorted(data, key=lambda x: (-x[1], x[0]))   # nilai DESC, nama ASC
print(urut)
# → [('Budi', 92), ('Dedi', 92), ('Ani', 85), ('Citra', 85)]

# reverse() — balik urutan IN-PLACE: O(n)
lst = [1, 2, 3, 4, 5]
lst.reverse()
print(lst)   # → [5, 4, 3, 2, 1]

Salinan List: Shallow vs Deep Copy #

Ini adalah sumber bug tersembunyi yang paling umum saat bekerja dengan list bersarang:

# Shallow copy — elemen di dalamnya masih dishare
original = [[1, 2], [3, 4], [5, 6]]

salinan_shallow = original.copy()    # atau: original[:]  atau: list(original)
salinan_shallow[0][0] = 99           # ubah elemen dalam sub-list

print(original)       # → [[99, 2], [3, 4], [5, 6]]  ← IKUT BERUBAH!
print(salinan_shallow) # → [[99, 2], [3, 4], [5, 6]]

# Mengapa? Karena shallow copy hanya menyalin referensi ke sub-list,
# bukan objek sub-list itu sendiri.

# Deep copy — salin seluruh struktur secara rekursif
import copy

original = [[1, 2], [3, 4], [5, 6]]
salinan_deep = copy.deepcopy(original)
salinan_deep[0][0] = 99

print(original)      # → [[1, 2], [3, 4], [5, 6]]  ← tidak berubah!
print(salinan_deep)  # → [[99, 2], [3, 4], [5, 6]]
Ringkasan metode salinan:

Metode                │ Jenis   │ Salin sub-objek?
──────────────────────┼─────────┼─────────────────
lst.copy()            │ shallow │ Tidak
lst[:]                │ shallow │ Tidak
list(lst)             │ shallow │ Tidak
copy.copy(lst)        │ shallow │ Tidak
copy.deepcopy(lst)    │ deep    │ Ya (rekursif)

Kompleksitas Waktu Operasi List #

Mengetahui kompleksitas operasi penting untuk memilih pendekatan yang tepat saat data besar:

Operasi                   │ Kompleksitas │ Catatan
──────────────────────────┼──────────────┼──────────────────────────────
lst[i]                    │ O(1)         │ akses via indeks
lst[i] = x                │ O(1)         │ assignment via indeks
lst.append(x)             │ O(1)*        │ *amortized — kadang O(n) saat resize
lst.pop()                 │ O(1)         │ pop elemen terakhir
lst.pop(i)                │ O(n)         │ pop tengah — geser elemen
lst.insert(i, x)          │ O(n)         │ geser elemen setelah indeks i
lst.remove(x)             │ O(n)         │ cari + geser
x in lst                  │ O(n)         │ linear search
lst.index(x)              │ O(n)         │ linear search
lst.sort()                │ O(n log n)   │ Timsort — stabil
len(lst)                  │ O(1)         │ disimpan sebagai atribut
lst.reverse()             │ O(n)         │
lst + lst2                │ O(n+m)       │ buat list baru
lst.extend(lst2)          │ O(k)         │ k = panjang lst2
lst[a:b]                  │ O(b-a)       │ buat list baru (salinan)
# Implikasi praktis dari kompleksitas waktu

# ANTI-PATTERN: insert di depan list berulang — O(n) per operasi = O(n²) total
hasil = []
for i in range(10000):
    hasil.insert(0, i)   # setiap insert geser semua elemen — sangat lambat!

# BENAR: append di belakang lalu reverse satu kali
hasil = []
for i in range(10000):
    hasil.append(i)
hasil.reverse()          # O(n) satu kali — jauh lebih cepat

# Atau gunakan deque jika sering insert di kedua ujung
from collections import deque
d = deque()
for i in range(10000):
    d.appendleft(i)      # O(1) per operasi

collections.deque untuk Operasi di Kedua Ujung #

Jika kamu sering menambah atau menghapus elemen di kedua ujung list, deque (double-ended queue) jauh lebih efisien:

from collections import deque

# Deque mendukung semua operasi list, PLUS operasi O(1) di depan
antrian = deque(["B", "C", "D"])

antrian.appendleft("A")   # tambah di depan: O(1)
antrian.append("E")       # tambah di belakang: O(1)
print(antrian)   # → deque(['A', 'B', 'C', 'D', 'E'])

antrian.popleft()          # hapus dari depan: O(1)
antrian.pop()              # hapus dari belakang: O(1)
print(antrian)   # → deque(['B', 'C', 'D'])

# rotate() — putar elemen
antrian = deque([1, 2, 3, 4, 5])
antrian.rotate(2)    # putar 2 ke kanan
print(antrian)   # → deque([4, 5, 1, 2, 3])

antrian.rotate(-1)   # putar 1 ke kiri
print(antrian)   # → deque([5, 1, 2, 3, 4])

# maxlen — antrian dengan batas ukuran (sliding window)
log = deque(maxlen=3)   # simpan maksimal 3 elemen terakhir
for pesan in ["a", "b", "c", "d", "e"]:
    log.append(pesan)
print(log)   # → deque(['c', 'd', 'e'], maxlen=3)

bisect — List Terurut yang Efisien #

Untuk list yang selalu dalam kondisi terurut, modul bisect menyediakan pencarian dan penyisipan biner — O(log n) untuk pencarian:

import bisect

terurut = [1, 3, 5, 7, 9, 11]

# bisect_left / bisect_right — cari posisi penyisipan
pos = bisect.bisect_left(terurut, 6)   # → 3 (sebelum 7)
pos = bisect.bisect_right(terurut, 5)  # → 3 (setelah 5)

# insort — sisipkan sambil pertahankan urutan: O(log n) cari + O(n) geser
bisect.insort(terurut, 6)
print(terurut)   # → [1, 3, 5, 6, 7, 9, 11]

# Penggunaan praktis: cari rentang nilai
nilai = [60, 70, 75, 80, 85, 90, 95]

def grade(skor, batas=[60, 70, 80, 90], huruf=["D", "C", "B", "A"]):
    """Konversi skor ke huruf menggunakan binary search."""
    return huruf[bisect.bisect_left(batas, skor)]

print(grade(85))   # → B
print(grade(92))   # → A
print(grade(65))   # → C

Operasi Lanjutan dengan List #

# zip dua list jadi list of tuples
nama = ["Budi", "Ani", "Citra"]
nilai = [85, 92, 78]
pasangan = list(zip(nama, nilai))
print(pasangan)   # → [('Budi', 85), ('Ani', 92), ('Citra', 78)]

# Unzip kembali
nama_balik, nilai_balik = zip(*pasangan)
print(list(nama_balik))    # → ['Budi', 'Ani', 'Citra']

# Flatten list bersarang satu level
bersarang = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
datar = [item for sub in bersarang for item in sub]
print(datar)   # → [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Atau dengan itertools.chain
from itertools import chain
datar = list(chain.from_iterable(bersarang))

# Hilangkan duplikat sambil pertahankan urutan
data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
unik_terurut = list(dict.fromkeys(data))
print(unik_terurut)   # → [3, 1, 4, 5, 9, 2, 6]
# dict.fromkeys mempertahankan urutan kemunculan pertama (Python 3.7+)

Ringkasan #

  • Jangan inisialisasi matriks dengan [[x]*n]*m — semua baris menunjuk objek yang sama. Gunakan [[x]*n for _ in range(m)].
  • Jangan ubah list saat diiterasi — gunakan list comprehension atau iterasi salinan lst[:] untuk filter/transformasi.
  • append() bukan + untuk menambah elemen ke list yang ada — + membuat list baru O(n), append() O(1) amortized.
  • sort() vs sorted()sort() modifikasi in-place dan kembalikan None; sorted() kembalikan list baru dan tidak ubah aslinya.
  • Shallow copy vs deep copy.copy(), [:], dan list() hanya salin satu level. Gunakan copy.deepcopy() untuk list bersarang.
  • pop() dari akhir O(1), dari tengah O(n) — jika sering hapus dari depan, pertimbangkan deque.
  • x in list adalah O(n) — jika perlu cek keanggotaan berulang, konversi ke set terlebih dahulu.
  • deque untuk antrian (FIFO) atau stack di kedua ujung — appendleft() dan popleft() O(1) berbeda dengan insert(0, x) yang O(n).
  • bisect untuk pencarian dan penyisipan pada list terurut — O(log n) jauh lebih cepat dari linear search O(n).

← Sebelumnya: Eksepsi   Berikutnya: Dictionary →

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