Resep pola praktis

Pola untuk tanggal, alamat surel, harga, dan pengenal, beserta kondisi ketika regex adalah alat yang salah.

Daftar isi

Resep pola menyatukan literal, kelas karakter, kuantifier, anchor, dan grup menjadi pola yang siap dipakai pada pekerjaan nyata. Topik ini menghimpun empat resep yang paling sering dibutuhkan, yaitu tanggal, alamat surel, harga, dan pengenal dokumen, lalu mengujinya terhadap daftar contoh valid dan tidak valid. Bagian penutup menegaskan batasannya, karena tidak semua masalah teks adalah masalah regex dan sebagian justru menjadi ranah parser.

Resep tanggal dd/mm/yyyy#

Tanggal berformat dd/mm/yyyy paling mudah ditangkap dengan pola dua digit, garis miring, dua digit, garis miring, lalu empat digit. Pola dasar \d{2}/\d{2}/\d{4} menemukan setiap rentetan itu, tetapi juga menerima tanggal mustahil seperti 99/99/9999 karena bentuknya saja yang diuji. Kelas karakter dan alternasi menahan rentangnya, yaitu bagian hari 0[1-9]|[12]\d|3[01] menerima 01 sampai 31, sedangkan bagian bulan 0[1-9]|1[0-2] menerima 01 sampai 12.

python Contoh: resep tanggal dari longgar ke terbatas
import re

teks = "Rapat dijadwalkan 05/03/2026, ditunda menjadi 12/03/2026, lalu 99/99/9999."

# resep dasar: dua digit, garis miring, dua digit, garis miring, empat digit
print(re.findall(r"\b\d{2}/\d{2}/\d{4}\b", teks))

# resep terbatas dengan grup penangkap: findall mengembalikan isi grup
pola_grup = r"\b(0[1-9]|[12]\d|3[01])/(0[1-9]|1[0-2])/\d{4}\b"
print(re.findall(pola_grup, teks))

# grup non-penangkap agar findall mengembalikan kecocokan utuh
pola_utuh = r"\b((?:0[1-9]|[12]\d|3[01])/(?:0[1-9]|1[0-2])/\d{4})\b"
print(re.findall(pola_utuh, teks))
text Keluaran
['05/03/2026', '12/03/2026', '99/99/9999']
[('05', '03'), ('12', '03')]
['05/03/2026', '12/03/2026']

Baris pertama memperlihatkan false positive pada pola dasar, yaitu 99/99/9999 ikut terbaca karena bentuknya memang cocok. Baris kedua menunjukkan efek samping grup penangkap pada findall, yaitu keluaran berisi pasangan hari dan bulan alih-alih tanggal utuh. Baris ketiga menutup masalah itu dengan grup non-penangkap di sekeliling setiap alternasi, sehingga tanggal utuh kembali terbaca. Pola terbatas pun masih menerima 31/02/2026, sebab kalender sungguhan bukan pekerjaan regex. Setelah kandidat tertangkap, validasi kalender diserahkan kepada modul tanggal.

Resep alamat surel#

Alamat surel (alamat email) pada data teks umumnya mengikuti bentuk sederhana, yaitu rangkaian karakter, tanda @, domain, titik, dan akhiran domain. Pola [\w.+-]+@[\w-]+\.[\w.-]+ menampung nama lokal dengan titik dan tanda tambah, serta domain bertingkat seperti contoh.ac.id. Pola ini sengaja dibuat pragmatis untuk menyaring data teks, bukan untuk meniru spesifikasi lengkapnya.

Uji pada potongan memakai re.fullmatch, sehingga string yang diuji harus cocok dari awal hingga akhir. Untuk mencari alamat di dalam teks panjang, pola yang sama dipanggil dengan findall. Dua contoh terakhir pada keluaran ditolak karena bagian lokalnya kosong atau tanda @ tidak diikuti domain.

python Contoh: uji pola alamat surel
import re

pola_surel = r"[\w.+-]+@[\w-]+\.[\w.-]+"

uji = [
    "[email protected]",
    "[email protected]",
    "budi@",
    "@contoh.example",
    "tanpa alamat",
]

for alamat in uji:
    if re.fullmatch(pola_surel, alamat):
        status = "lolos"
    else:
        status = "ditolak"
    print(f"{alamat!r:34} {status}")
text Keluaran
'[email protected]'                lolos
'[email protected]'    lolos
'budi@'                            ditolak
'@contoh.example'                  ditolak
'tanpa alamat'                     ditolak

Resep harga dengan pemisah ribuan#

Harga dalam format Indonesia menyusun ribuan dengan titik dan desimal dengan koma. Pola Rp\s?\d{1,3}(?:\.\d{3})*(?:,\d{1,2})? menampung tiga variasi sekaligus, yaitu tanpa pemisah, dengan pemisah ribuan berulang, dan dengan dua digit desimal. Kuantifier * pada grup (?:\.\d{3}) mengizinkan pemisah ribuan yang berulang, sedangkan ? pada grup desimal membuat bagian itu opsional.

python Contoh: resep harga dengan pemisah ribuan
import re

teks = "Total belanja Rp1.250.000, ongkos kirim Rp 37.500, dan pajak Rp3.750,50."

print(re.findall(r"Rp\s?\d{1,3}(?:\.\d{3})*(?:,\d{1,2})?", teks))
text Keluaran
['Rp1.250.000', 'Rp 37.500', 'Rp3.750,50']

Keluaran mengembalikan tiga harga utuh beserta spasinya. Pola ini sengaja longgar pada tempat yang memang longgar di data nyata, misalnya spasi setelah Rp yang kadang ada dan kadang tidak. Angka tanpa awalan Rp tidak ikut tertangkap, sehingga angka lain seperti jarak 12.500 meter aman dari kekeliruan. Apabila angka tanpa awalan juga perlu dicari, tambahkan pola kedua tanpa Rp dengan kesadaran bahwa angka non-harga ikut tertangkap.

Resep pengenal dokumen#

Banyak lembaga menomori dokumen dengan bentuk kategori-tahun/urutan, contohnya SK-2026/001. Pola [A-Z]{2,}-\d{4}/\d{3,} menuntut kategori berisi dua huruf atau lebih, tahun empat digit, dan nomor urut tiga digit atau lebih. Kuantifier rentang {m,n} bekerja pada dua posisi sekaligus di pola ini.

Pengujian memakai re.fullmatch agar bentuk yang menyimpang di ujung mana pun ditolak. Dengan search, pola yang sama dapat menangkap pengenal di dalam isi surat, dan grup bernama memilah kategori, tahun, serta nomor urutnya sekali jalan.

python Contoh: resep pengenal dokumen
import re

pola_id = re.compile(r"(?P<kategori>[A-Z]{2,})-(?P<tahun>\d{4})/(?P<urut>\d{3,})")

uji = ["SK-2026/001", "NOTA-2026/042", "SK-2026/1", "sk-2026/001", "SK-26/001"]

for dokumen in uji:
    m = pola_id.fullmatch(dokumen)
    if m is None:
        print(f"{dokumen:14} ditolak")
    else:
        print(f"{dokumen:14} kategori={m.group('kategori')} tahun={m.group('tahun')} urut={m.group('urut')}")
text Keluaran
SK-2026/001    kategori=SK tahun=2026 urut=001
NOTA-2026/042  kategori=NOTA tahun=2026 urut=042
SK-2026/1      ditolak
sk-2026/001    ditolak
SK-26/001      ditolak

Dua dokumen pertama lolos beserta ketiga potongannya, sedangkan tiga bentuk sisanya ditolak karena huruf kecil, tahun dua digit, atau nomor urut yang terlalu pendek. Penolakan tegas seperti ini berguna ketika pengenal menjadi kunci pencarian, karena bentuk yang menyimpang biasanya memang salah ketik. Apabila format lembaganya berbeda, sesuaikan rentang kuantifiernya terlebih dahulu sebelum menambah pilihan baru.

Fungsi uji kecil untuk resep#

Resep yang tidak diuji hanyalah asumsi. Berkas uji_resep.py menyimpan keempat pola beserta daftar contoh valid dan tidak valid, lalu memeriksa setiap contoh dengan re.fullmatch. Keluarannya mencantumkan status OK atau GAGAL untuk setiap baris uji beserta rekapitulasinya, sehingga penyuntingan pola yang merusak resep lain langsung terlihat.

Contoh tidak valid pada daftar bukan pilihan acak, karena setiap contoh menyasar satu aturan, misalnya tahun dua digit pada pengenal dan bagian lokal kosong pada surel. Perintah berikut menjalankan seluruh uji dari terminal.

python uji_resep.py
import re

# Empat resep pola beserta daftar uji kecilnya.
RESEP = [
    ("tanggal", r"\b(?:0[1-9]|[12]\d|3[01])/(?:0[1-9]|1[0-2])/\d{4}\b",
     ["05/03/2026", "31/12/2026"], ["99/99/9999", "5/3/2026"]),
    ("surel", r"[\w.+-]+@[\w-]+\.[\w.-]+",
     ["[email protected]", "[email protected]"],
     ["budi@", "@contoh.example"]),
    ("harga", r"Rp\s?\d{1,3}(?:\.\d{3})*(?:,\d{1,2})?",
     ["Rp1.250.000", "Rp 37.500,50"], ["12.500", "Rp12.5"]),
    ("pengenal", r"[A-Z]{2,}-\d{4}/\d{3,}",
     ["SK-2026/001", "NOTA-2026/042"], ["SK-2026/1", "sk-2026/001"]),
]

def uji_resep(nama, pola, valid, tidak_valid):
    hasil = []
    for contoh in valid:
        lolos = re.fullmatch(pola, contoh) is not None
        hasil.append((contoh, "valid", "OK" if lolos else "GAGAL"))
    for contoh in tidak_valid:
        lolos = re.fullmatch(pola, contoh) is not None
        hasil.append((contoh, "tidak valid", "OK" if not lolos else "GAGAL"))
    print(f"[{nama}]")
    for contoh, harapan, status in hasil:
        print(f"  {contoh:30} {harapan:10} {status}")
    gagal = sum(1 for _, _, status in hasil if status == "GAGAL")
    print(f"  -> {len(hasil) - gagal} dari {len(hasil)} uji benar")
    print()

for nama, pola, valid, tidak_valid in RESEP:
    uji_resep(nama, pola, valid, tidak_valid)
text Keluaran
[tanggal]
  05/03/2026                     valid      OK
  31/12/2026                     valid      OK
  99/99/9999                     tidak valid OK
  5/3/2026                       tidak valid OK
  -> 4 dari 4 uji benar

[surel]
  [email protected]              valid      OK
  [email protected]  valid      OK
  budi@                          tidak valid OK
  @contoh.example                tidak valid OK
  -> 4 dari 4 uji benar

[harga]
  Rp1.250.000                    valid      OK
  Rp 37.500,50                   valid      OK
  12.500                         tidak valid OK
  Rp12.5                         tidak valid OK
  -> 4 dari 4 uji benar

[pengenal]
  SK-2026/001                    valid      OK
  NOTA-2026/042                  valid      OK
  SK-2026/1                      tidak valid OK
  sk-2026/001                    tidak valid OK
  -> 4 dari 4 uji benar
bash
# jalankan seluruh uji resep dari folder tempat berkas disimpan
python3 uji_resep.py
text Keluaran
[tanggal]
  05/03/2026                     valid      OK
  31/12/2026                     valid      OK
  99/99/9999                     tidak valid OK
  5/3/2026                       tidak valid OK
  -> 4 dari 4 uji benar

[surel]
  [email protected]              valid      OK
  [email protected]  valid      OK
  budi@                          tidak valid OK
  @contoh.example                tidak valid OK
  -> 4 dari 4 uji benar

[harga]
  Rp1.250.000                    valid      OK
  Rp 37.500,50                   valid      OK
  12.500                         tidak valid OK
  Rp12.5                         tidak valid OK
  -> 4 dari 4 uji benar

[pengenal]
  SK-2026/001                    valid      OK
  NOTA-2026/042                  valid      OK
  SK-2026/1                      tidak valid OK
  sk-2026/001                    tidak valid OK
  -> 4 dari 4 uji benar

Keempat resep meloloskan empat uji masing-masing, sehingga enam belas contoh dinyatakan benar. Kumpulan uji semacam ini berukuran puluhan baris dan berjalan di bawah satu detik, sehingga tidak ada alasan menunda pemakaiannya pada pola yang dipakai secara serius.

Ketika regex bukan alat yang tepat#

Keempat resep di atas bekerja karena sasarannya datar, yaitu satu baris, satu rentang angka, atau satu bentuk token. Kondisi berikut menandai saatnya regex ditinggalkan.

  • Dokumen HTML yang tag-nya bersarang lebih baik diuraikan dengan parser, misalnya modul html.parser bawaan Python, karena parser memahami hierarki tag sedangkan kuantifier hanya memahami pengulangan yang datar.
  • Validasi alamat surel sekuat spesifikasi menuntut parser penuh, sebab RFC 5322 mengizinkan bagian lokal berkutip dan komentar yang sulit dinyatakan dengan pola yang tetap dapat dibaca manusia.
  • Dokumen JSON dan CSV yang memuat kutip berpasangan dan koma bersarang lebih aman diuraikan dengan modul json dan csv, karena strukturnya tidak dapat dinyatakan sebagai pola berulang yang datar.
  • Pola dengan kuantifier bersarang yang menerima masukan tak terkendali membawa risiko backtracking katastropik, sehingga pola itu diganti versi pipih, diberi penahan mundur, atau digantikan pengurai yang ditulis langsung.

Pengambilan keputusannya singkat. Regex dipakai untuk pola datar yang bentuknya konsisten, sedangkan parser dipakai untuk format yang bersarang atau baku. Apabila sebuah pola mulai menumpuk alternasi bersarang dan catatan kaki untuk dijelaskan, itu tanda parser perlu ditulis. Keempat resep pada topik ini tetap dalam batas yang sehat, dan daftar ujinya menjaga batas itu tetap terukur.

Ringkasan#

  • Empat resep praktis, yaitu tanggal terbatas, surel pragmatis, harga berpemisah ribuan, dan pengenal dokumen, tersusun dari literal, kelas, kuantifier, dan grup yang sama.
  • re.fullmatch menguji string utuh untuk validasi, sedangkan search dan findall menemukan pola di dalam teks yang lebih panjang.
  • Daftar uji valid dan tidak valid menjaga resep tetap benar ketika polanya disunting.
  • Format bersarang seperti HTML, JSON, dan surel sekuat RFC adalah ranah parser, bukan regex.

Referensi#