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.
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))['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.
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}")'[email protected]' lolos
'[email protected]' lolos
'budi@' ditolak
'@contoh.example' ditolak
'tanpa alamat' ditolakResep 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.
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))['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.
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')}")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 ditolakDua 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.
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)[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# jalankan seluruh uji resep dari folder tempat berkas disimpan
python3 uji_resep.py[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 benarKeempat 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.parserbawaan 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
jsondancsv, 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.fullmatchmenguji string utuh untuk validasi, sedangkansearchdanfindallmenemukan 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#
- Regular expression operations — Python documentation
— Referensi resmi fungsi
fullmatch,findall,sub, dan sintaks pola modulre. - Speech and Language Processing, draf edisi ke-3 — Jurafsky & Martin, Stanford — Bab tokenisasi menunjukkan peran regex pada tahap awal pemrosesan teks.