Pola pada Chapter Pola Dasar Regex memilih karakter satu per satu: \d untuk satu digit, satu huruf literal untuk satu huruf. Dua kebutuhan nyata masih belum terjawab dengan cara itu: memilih satu karakter dari sekumpulan kandidat (misalnya “huruf vokal saja”), dan mengulang bagian pola (misalnya “tepat empat digit”). Character class menjawab kebutuhan pertama; quantifier menjawab yang kedua. Chapter ini membahas keduanya, ditutup perilaku greedy versus lazy yang sering menjebak pemula.

Character Classes: Memilih dari Kumpulan #

Tanda kurung siku [...] membentuk character class: satu posisi karakter dengan sejumlah kandidat. Pola [aeiou] cocok dengan satu huruf vokal; pola [bc]erita cocok dengan berita maupun cerita.

python
import re

kata = "universitas"
print(re.findall(r"[aeiou]", kata))
print(re.findall(r"[^aeiou]", kata))

Output yang Dihasilkan:

text
['u', 'i', 'e', 'i', 'a']
['n', 'v', 'r', 's', 't', 's']

Tanda ^ yang ditulis pertama di dalam kurung siku membalik makna kelas menjadi “bukan salah satu dari kandidat ini” — pada contoh di atas, seluruh konsonan tertangkap.

Kelas juga menerima rentang karakter: [a-z] berarti satu huruf kecil, [A-Za-z] berarti satu huruf Latin apa pun, [0-9] berarti satu digit — setara dengan shorthand \d.

python
import re

kode = "RinCIan: aB3f-9K"
print(re.findall(r"[a-z]", kode))
print(re.findall(r"[A-Za-z]", kode))
print(re.findall(r"[^0-9]", kode))

Output yang Dihasilkan:

text
['i', 'n', 'a', 'n', 'a', 'f']
['R', 'i', 'n', 'C', 'I', 'a', 'n', 'a', 'B', 'f', 'K']
['R', 'i', 'n', 'C', 'I', 'a', 'n', ':', ' ', 'a', 'B', 'f', '-', 'K']

Quantifier: Berapa Kali Pola Berulang #

Quantifier ditempelkan tepat di belakang bagian pola yang diulang — satu karakter, satu shorthand, atau satu character class — dengan makna berikut:

QuantifierMakna
*nol kali atau lebih
+satu kali atau lebih
?nol kali atau satu kali (opsional)
{m}tepat m kali
{m,n}antara m sampai n kali
{m,}m kali atau lebih

Perbedaan * dan + paling jelas dilihat langsung:

python
import re

for kata in ["mrah", "merah", "merrah"]:
    print(kata, "-> me*rah:", bool(re.search(r"^me*rah$", kata)),
          "| mer+ah:", bool(re.search(r"^mer+ah$", kata)))

Output yang Dihasilkan:

text
mrah -> me*rah: True | mer+ah: False
merah -> me*rah: True | mer+ah: True
merrah -> me*rah: False | mer+ah: True

Kata mrah diterima me*rah karena e* memperbolehkan huruf e absen, tetapi ditolak mer+ah yang mewajibkan e lalu minimal satu r. Sebaliknya, merrah ditolak me*rah karena pola tersebut hanya menyediakan tepat satu r, sementara mer+ah menerima deretan r berapa pun panjangnya.

Quantifier ? menandai bagian opsional — cocok sekali atau tidak sama sekali, misalnya pemisah tanggal yang kadang ditulis:

python
import re

for tanggal in ["2025-0615", "2025-06-15"]:
    print(tanggal, "->", bool(re.search(r"^2025-06-?15$", tanggal)))

Output yang Dihasilkan:

text
2025-0615 -> True
2025-06-15 -> True

Kurung kurawal memberi hitungan eksplisit. Pola nomor HP dari Chapter Pola Dasar Regex yang tadinya penuh \d berulang kini bisa ditulis ringkas — sekaligus lebih longgar karena pemisahnya boleh spasi, titik, tanda hubung, atau tidak sama sekali:

python
import re

pola_hp = r"^08\d{2}[ .-]?\d{4}[ .-]?\d{4}$"

for nomor in ["0812-3456-7890", "0812.3456.7890", "0812 3456 7890", "081234567890", "0812-345-678"]:
    print(nomor, "->", bool(re.search(pola_hp, nomor)))

Output yang Dihasilkan:

text
0812-3456-7890 -> True
0812.3456.7890 -> True
0812 3456 7890 -> True
081234567890 -> True
0812-345-678 -> False

Mari kita telaah apa yang terjadi pada baris-baris kode di atas:

  1. Baris 3 (pola_hp = r"^08\d{2}[ .-]?\d{4}[ .-]?\d{4}$"): \d{2} membaca tepat dua digit setelah awalan 08; character class [ .-] menerima spasi, titik, atau tanda hubung sebagai pemisah; ? membuat pemisah opsional sehingga format tanpa pemisah pun sah.
  2. Baris 5-6 (for nomor in): empat variasi penulisan diterima; kandidat terakhir ditolak karena segmen keduanya hanya berisi tiga digit, sedangkan \d{4} menuntut tepat empat.

Quantifier berlaku juga untuk character class. Pola [aeiou]+ mencocokkan deretan vokal yang berurutan sebagai satu kecocokan:

python
import re

kalimat = "Budi membaca buku di laut."
print(re.findall(r"[aeiou]+", kalimat))

Output yang Dihasilkan:

text
['u', 'i', 'e', 'a', 'a', 'u', 'u', 'i', 'au']

Deret au pada kata laut tertangkap sebagai satu kecocokan karena kedua huruf tersebut berurutan, sementara vokal yang terpisah konsonan dilaporkan terpisah.

Greedy vs Lazy #

Secara bawaan semua quantifier bersifat greedy: mesin regex mencoba memakan karakter sebanyak mungkin, lalu mundur (backtrack) seperlunya bila sisa pola gagal. Perilaku ini paling mudah tersalahpahami saat mengambil teks di antara dua penanda, misalnya kalimat di antara tanda kutip:

python
import re

kalimat = 'Budi berkata "halo" lalu Siti menjawab "hai"'
print(re.findall(r'".*"', kalimat))
print(re.findall(r'".*?"', kalimat))

Output yang Dihasilkan:

text
['"halo" lalu Siti menjawab "hai"']
['"halo"', '"hai"']

Mesin regex memproses ".*" begini: tanda kutip pembuka cocok pada kutip pertama, lalu .* yang greedy melahap semua karakter sampai akhir string — termasuk kutip kedua; mesin lalu mundur sampai menemukan kutip penutup, dan yang ditemukan adalah kutip paling kanan. Hasilnya satu kecocokan raksasa yang melintasi dua kalimat.

Menambahkan ? di belakang quantifier mengubahnya menjadi lazy: mesin mencoba memakan sesedikit mungkin lalu maju seperlunya. Pola ".*?" berhenti di kutip pertama yang ditemui, sehingga kedua kalimat terambil terpisah.