Tips untuk Regex Golf

43

Mirip dengan utas kami untuk kiat golf khusus bahasa: apa saja trik umum untuk mempersingkat ekspresi reguler?

Saya dapat melihat tiga penggunaan regex ketika datang ke golf: golf regex klasik ("di sini adalah daftar yang harus cocok, dan di sini adalah daftar yang harus gagal"), menggunakan regex untuk memecahkan masalah komputasi dan ekspresi reguler yang digunakan sebagai bagian dari kode golf yang lebih besar. Jangan ragu untuk mengirim kiat mengatasi salah satu atau semua ini. Jika tip Anda terbatas pada satu atau lebih rasa, sebutkan rasa ini di atas.

Seperti biasa, harap tetap berpegang pada satu tip (atau keluarga tips yang sangat erat terkait) per jawaban, sehingga tips yang paling berguna dapat naik ke puncak melalui voting.

Martin Ender
sumber
Promosi mandiri yang mencolok: kategori penggunaan regex apa yang termasuk dalam kategori ini? codegolf.stackexchange.com/a/37685/8048
Kyle Strand
@KyleStrand "ekspresi reguler digunakan sebagai bagian dari kode golf yang lebih besar."
Martin Ender

Jawaban:

24

Kapan tidak melarikan diri

Aturan-aturan ini berlaku untuk sebagian besar rasa, jika tidak semua:

  • ] tidak perlu melarikan diri saat tidak cocok.

  • {dan }tidak perlu melarikan diri ketika mereka bukan bagian dari pengulangan, misalnya {a}pertandingan {a}secara harfiah. Bahkan jika Anda ingin mencocokkan sesuatu seperti {2}, Anda hanya perlu melarikan diri salah satunya, misalnya {2\}.

Di kelas karakter:

  • ]tidak perlu keluar ketika itu karakter pertama dalam set karakter, misalnya []abc]cocok dengan salah satu ]abc, atau ketika itu karakter kedua setelah a ^, misalnya [^]]cocok dengan apa pun kecuali ]. (Pengecualian penting: rasa ECMAScript!)

  • [tidak perlu melarikan diri sama sekali. Bersama dengan tip di atas, ini berarti Anda bisa mencocokkan kedua tanda kurung dengan kelas karakter kontra-intuitif yang mengerikan [][].

  • ^tidak perlu melarikan diri ketika itu bukan karakter pertama dalam rangkaian karakter, mis [ab^c].

  • -tidak perlu keluar ketika itu karakter pertama (kedua setelah a ^) atau terakhir dalam rangkaian karakter, misalnya [-abc], [^-abc]atau [abc-].

  • Tidak ada karakter lain yang perlu melarikan diri di dalam kelas karakter, bahkan jika mereka adalah karakter meta di luar kelas karakter (kecuali untuk backslash \itu sendiri).

Juga, dalam beberapa rasa ^dan $dicocokkan secara harfiah ketika masing-masing tidak pada awal atau akhir regex.

(Terima kasih kepada @ MartinBüttner karena telah mengisi beberapa detail)

Sp3000
sumber
Beberapa lebih suka melarikan diri dari titik yang sebenarnya dengan melampirkannya dalam kelas karakter di mana ia tidak perlu melarikan diri (mis. [.]). Melarikannya secara normal akan menghemat 1 byte dalam kasus ini\.
CSᵠ
Catatan yang [harus diloloskan di Jawa. Namun, tidak yakin tentang ICU (digunakan di Android dan iOS) atau .NET.
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳
18

Ekspresi reguler sederhana untuk mencocokkan semua karakter yang dapat dicetak dalam tabel ASCII .

[ -~]
hwnd
sumber
1
kemegahan murni, semua karakter dari keyboard AS standar! catatan: tabel ascii standar (tidak termasuk rentang diperpanjang 127-255
CSᵠ
Saya sering menggunakannya, tetapi tidak memiliki karakter "biasa" yang umum: TAB. Dan itu mengasumsikan Anda menggunakan LC_ALL = "C" (atau serupa) karena beberapa lokal lain akan gagal.
Olivier Dulac
Dapatkah tanda hubung digunakan seperti itu untuk menentukan rentang karakter dalam tabel ASCII? Apakah itu bekerja untuk semua rasa regex?
Josh Withee
14

Ketahui rasa regex Anda

Ada sejumlah orang yang berpikir bahwa ekspresi reguler pada dasarnya adalah agnostik bahasa. Namun, sebenarnya ada perbedaan yang cukup besar antara rasa, dan terutama untuk golf kode, ada baiknya untuk mengetahui beberapa di antaranya, dan fitur-fiturnya yang menarik, sehingga Anda dapat memilih yang terbaik untuk setiap tugas. Berikut ini adalah ikhtisar tentang beberapa rasa penting dan apa yang membedakan mereka dari yang lain. (Daftar ini tidak dapat benar-benar lengkap, tetapi beri tahu saya jika saya melewatkan sesuatu yang sangat mencolok.)

Perl dan PCRE

Saya melempar ini ke dalam panci tunggal, karena saya tidak terlalu terbiasa dengan rasa Perl dan kebanyakan setara (PCRE adalah untuk Ekspresi Reguler Kompatibel-Kompatibel setelah semua). Keuntungan utama dari rasa Perl adalah bahwa Anda benar-benar dapat memanggil kode Perl dari dalam regex dan substitusi.

  • Rekursi / subrutin . Mungkin fitur yang paling penting untuk bermain golf (yang hanya ada dalam beberapa rasa).
  • Pola bersyarat (?(group)yes|no).
  • Mendukung mengubah kasus dalam string pengganti dengan \l, \u, \Ldan \U.
  • PCRE memungkinkan pergantian tampilan, di mana setiap alternatif dapat memiliki panjang yang berbeda (tetapi tetap). (Sebagian besar rasa, termasuk Perl membutuhkan tampilan tetap untuk memiliki panjang tetap keseluruhan.)
  • \G untuk menambatkan pertandingan ke akhir pertandingan sebelumnya.
  • \K untuk mengatur ulang awal pertandingan
  • PCRE mendukung properti karakter dan skrip Unicode .
  • \Q...\Euntuk menghindari lari karakter yang lebih lama. Berguna saat Anda mencoba mencocokkan string yang berisi banyak meta-karakter.

.BERSIH

Ini mungkin rasa yang paling kuat, dengan hanya sedikit kekurangan.

Salah satu kekurangan penting dalam hal bermain golf adalah tidak mendukung quantifiers posesif seperti beberapa rasa lainnya. Alih-alih .?+Anda harus menulis (?>.?).

Jawa

  • Karena bug (lihat Lampiran) Java mendukung jenis tampilan variabel-panjang yang terbatas di belakang: Anda dapat melihat di belakang sampai ke awal string dengan .*dari mana Anda sekarang dapat memulai lookahead, seperti (?<=(?=lookahead).*).
  • Mendukung penyatuan dan persimpangan kelas karakter.
  • Memiliki dukungan paling luas untuk Unicode, dengan kelas karakter untuk "skrip Unicode, blok, kategori, dan properti biner" .
  • \Q...\E seperti pada Perl / PCRE.

Rubi

Dalam versi terbaru, rasa ini sama kuatnya dengan PCRE, termasuk dukungan untuk panggilan subrutin. Seperti Java, ini juga mendukung penyatuan dan persimpangan kelas karakter. Satu fitur khusus adalah kelas karakter bawaan untuk digit hex: \h(dan yang dinegasikan \H).

Fitur yang paling berguna untuk bermain golf adalah bagaimana Ruby menangani bilangan bulat. Terutama, dimungkinkan untuk mengukur sarang tanpa tanda kurung. .{5,7}+bekerja dan begitu juga .{3}?. Juga, sebagai lawan dari kebanyakan rasa lainnya, jika batas bawah pada kuantifier 0dapat dihilangkan, misalnya .{,5}setara dengan .{0,5}.

Adapun subrutin, perbedaan utama antara subrutin PCRE dan subrutin Ruby, adalah bahwa sintaks Ruby adalah satu byte lebih panjang (?n)vs \g<n>, tetapi subrutin Ruby dapat digunakan untuk menangkap, sedangkan PCRE me-reset menangkap setelah subrutin selesai.

Terakhir, Ruby memiliki semantik berbeda untuk pengubah yang berhubungan dengan jalur dibandingkan kebanyakan rasa lainnya. Pengubah yang biasa disebut mdengan citarasa lain selalu menyala di Ruby. Jadi ^dan $selalu cocok dengan awal dan akhir garis, tidak hanya awal dan akhir string. Ini dapat menghemat satu byte jika Anda memerlukan perilaku ini, tetapi akan dikenakan biaya tambahan byte jika tidak, karena Anda harus mengganti ^dan $dengan \Adan \z, masing-masing. Selain itu, pengubah yang biasanya disebut s(yang membuat .umpan baris cocok) disebut mdi Ruby sebagai gantinya. Ini tidak mempengaruhi jumlah byte, tetapi harus diingat untuk menghindari kebingungan.

Python

Python memiliki rasa yang kuat, tapi saya tidak mengetahui adanya fitur yang sangat berguna yang tidak akan Anda temukan di tempat lain.

Namun , ada rasa alternatif yang dimaksudkan untuk menggantikan remodul di beberapa titik, dan yang mengandung banyak fitur menarik. Selain menambahkan dukungan untuk rekursi, tampilan panjang variabel dan operator kombinasi kelas karakter, ini juga memiliki fitur unik pencocokan fuzzy . Intinya, Anda dapat menentukan sejumlah kesalahan (penyisipan, penghapusan, pergantian) yang diizinkan, dan mesin juga akan memberi Anda perkiraan kecocokan.

Naskah ECMAS

Rasa ECMAScript sangat terbatas, dan karenanya jarang sangat berguna untuk bermain golf. Satu-satunya hal yang terjadi adalah kelas karakter kosong yang dinegasikan [^] untuk mencocokkan karakter apa pun serta kelas karakter kosong yang gagal tanpa syarat [](sebagai lawan dari yang biasa (?!)). Sayangnya, rasanya tidak memiliki fitur yang membuat yang terakhir berguna untuk masalah normal.

Lua

Lua memiliki citarasa yang cukup unik, yang cukup terbatas (mis. Anda bahkan tidak dapat mengukur kelompok) tetapi memang hadir dengan sejumlah fitur yang berguna dan menarik.

  • Ada sejumlah besar singkatan untuk kelas karakter bawaan , termasuk tanda baca, karakter huruf besar / kecil dan digit hex.
  • Dengan %bitu mendukung sintaks yang sangat kompak untuk mencocokkan string seimbang. Misalnya %b()cocok dengan (dan kemudian semuanya hingga cocok )(melewatkan dengan benar pasangan yang cocok dalam). (dan )dapat berupa dua karakter apa pun di sini.

Dorongan

Rasa regex Boost pada dasarnya adalah Perl. Namun, ia memiliki beberapa fitur baru yang bagus untuk penggantian regex, termasuk perubahan kasus dan persyaratan . Yang terakhir ini unik untuk Meningkatkan sejauh yang saya ketahui.

Martin Ender
sumber
Perhatikan bahwa melihat-depan dalam melihat-belakang akan menembus batas terikat dalam melihat-belakang. Diuji di Jawa dan PCRE.
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳
Tidak .?+setara dengan .*?
CalculatorFeline
@CalculatorFeline Yang pertama adalah quantifier 0-atau-1 posesif (dalam rasa yang mendukung quantifiers posesif), yang terakhir adalah quantifier 0 atau lebih.
Martin Ender
@ CalculatorFeline ah saya mengerti kebingungan. Ada kesalahan ketik.
Martin Ender
13

Ketahui kelas karakter Anda

Sebagian besar rasa regex memiliki kelas karakter yang telah ditentukan. Misalnya, \dcocok dengan angka desimal, yang tiga byte lebih pendek dari [0-9]. Ya, mereka mungkin sedikit berbeda karena \dmungkin juga cocok dengan angka Unicode dalam beberapa rasa, tetapi untuk sebagian besar tantangan ini tidak akan membuat perbedaan.

Berikut adalah beberapa kelas karakter yang ditemukan di sebagian besar rasa regex:

\d      Match a decimal digit character
\s      Match a whitespace character
\w      Match a word character (typically [a-zA-Z0-9_])

Selain itu, kami juga memiliki:

\D \S \W

yang merupakan versi negasi di atas.

Pastikan untuk memeriksa rasa Anda untuk kelas karakter tambahan apa pun yang mungkin dimilikinya. Sebagai contoh, PCRE memiliki \Runtuk baris baru dan Lua bahkan memiliki kelas-kelas seperti huruf kecil dan huruf besar.

(Terima kasih kepada @HamZa dan @ MartinBüttner karena menunjukkan ini)

Sp3000
sumber
3
\Runtuk baris baru di PCRE.
HamZa
12

Jangan repot-repot dengan kelompok yang tidak menangkap (kecuali ...)

Kiat ini berlaku untuk (setidaknya) semua rasa yang diilhami Perl.

Ini mungkin jelas, tetapi (ketika tidak bermain golf) adalah praktik yang baik untuk menggunakan kelompok yang tidak menangkap (?:...)jika memungkinkan. Kedua karakter tambahan ?:ini boros ketika bermain golf, jadi gunakan saja grup menangkap, bahkan jika Anda tidak akan mereferensi mereka.

Ada satu pengecualian (jarang): jika Anda kebetulan pada grup referensi 10-ulang setidaknya 3 kali, Anda sebenarnya dapat menyimpan byte dengan mengubah grup sebelumnya menjadi grup yang tidak menangkap, sehingga semua \10itu menjadi \9s. (Trik serupa berlaku, jika Anda menggunakan grup 11setidaknya 5 kali dan seterusnya.)

Martin Ender
sumber
Mengapa 11 perlu 5 kali menjadi layak ketika 10 membutuhkan 3?
Nic Hartley
1
@QPaysTaxes dapat menggunakan $9alih-alih $10atau $11sekali menghemat satu byte. Mengubah $10menjadi $9membutuhkan satu ?:, yaitu dua byte, jadi Anda akan membutuhkan tiga $10untuk menyimpan sesuatu. Mengubah $11menjadi $9membutuhkan dua ?:s yang merupakan empat byte, jadi Anda akan membutuhkan lima $11s untuk menyimpan sesuatu (atau lima $10dan $11digabungkan).
Martin Ender
10

Rekursi untuk penggunaan kembali pola

Sejumlah rekursi rasa mendukung ( setahu saya , Perl, PCRE dan Ruby). Bahkan ketika Anda tidak mencoba untuk memecahkan masalah rekursif, fitur ini dapat menyimpan banyak byte dalam pola yang lebih rumit. Tidak perlu melakukan panggilan ke grup lain (bernama atau bernomor) di dalam grup itu sendiri. Jika Anda memiliki pola tertentu yang muncul beberapa kali di regex Anda, cukup kelompokkan dan lihat di luar grup itu. Ini tidak berbeda dari panggilan subrutin dalam bahasa pemrograman normal. Jadi, bukannya

...someComplexPatternHere...someComplexPatternHere...someComplexPatternHere... 

di Perl / PCRE yang dapat Anda lakukan:

...(someComplexPatternHere)...(?1)...(?1)...

atau di Ruby:

...(someComplexPatternHere)...\g<1>...\g<1>...

asalkan itu adalah grup pertama (tentu saja, Anda dapat menggunakan nomor apa pun dalam panggilan rekursif).

Perhatikan bahwa ini tidak sama dengan backreference ( \1). Referensi ulang cocok dengan string yang sama persis dengan kelompok yang cocok terakhir kali. Panggilan subrutin ini benar-benar mengevaluasi pola lagi. Sebagai contoh untuk someComplexPatternHeremengambil kelas karakter yang panjang:

a[0_B!$]b[0_B!$]c[0_B!$]d

Ini akan cocok dengan sesuatu seperti

aBb0c!d

Perhatikan bahwa Anda tidak dapat menggunakan referensi-ulang di sini sambil mempertahankan perilaku. Referensi balik akan gagal pada string di atas, karena Bdan 0dan !tidak sama. Namun, dengan panggilan subrutin, polanya sebenarnya dievaluasi kembali. Pola di atas sama dengan

a([0_B!$])b(?1)c(?1)d

Menangkap panggilan subrutin

Satu catatan hati-hati untuk Perl dan PCRE: jika grup 1dalam contoh di atas berisi grup lebih lanjut, maka panggilan subrutin tidak akan mengingat hasil tangkapan mereka. Pertimbangkan contoh ini:

(\w(\d):)\2 (?1)\2 (?1)\2

Ini tidak akan cocok

x1:1 y2:2 z3:3

karena setelah panggilan subrutin kembali, penangkapan baru grup 2akan dibuang. Alih-alih, pola ini akan cocok dengan string ini:

x1:1 y2:1 z3:1

Ini berbeda dari Ruby, di mana panggilan subrutin memang mempertahankan tangkapan mereka, sehingga regex Ruby yang setara (\w(\d):)\2 \g<1>\2 \g<1>\2akan cocok dengan yang pertama dari contoh di atas.

Martin Ender
sumber
Anda dapat menggunakan \1untuk Javascript. Dan PHP juga (kurasa).
Ismael Miguel
5
@IsmaelMiguel Ini bukan referensi balik. Ini sebenarnya mengevaluasi pola lagi. Misalnya (..)\1akan cocok ababtetapi gagal abbapadahal (..)(?1)akan cocok dengan yang terakhir. Ini sebenarnya adalah panggilan subrutin dalam arti bahwa ekspresi diterapkan lagi, alih-alih secara harfiah cocok dengan yang cocok terakhir kali.
Martin Ender
Wow, saya tidak tahu! Belajar sesuatu yang baru setiap hari
Ismael Miguel
Dalam. NET (atau rasa lain tanpa fitur ini):(?=a.b.c)(.[0_B!$]){3}d
jimmy23013
@ user23013 yang tampaknya sangat spesifik untuk contoh khusus ini. Saya tidak yakin itu berlaku jika saya menggunakan kembali subpattern tertentu dalam berbagai pencarian.
Martin Ender
9

Menyebabkan pertandingan gagal

Saat menggunakan regex untuk memecahkan masalah komputasi atau mencocokkan bahasa yang sangat tidak biasa, kadang-kadang perlu untuk membuat cabang dari pola gagal terlepas di mana pun Anda berada di string. Pendekatan naif adalah dengan menggunakan lookahead negatif yang kosong:

(?!)

Isi (pola kosong) selalu cocok, sehingga tampilan negatif selalu gagal. Tetapi lebih sering daripada tidak, ada opsi yang lebih sederhana: cukup gunakan karakter yang Anda tahu tidak akan pernah muncul di input. Misalnya, jika Anda tahu input Anda akan selalu hanya terdiri dari digit, Anda bisa menggunakan saja

!

atau karakter non-digit, non-meta lainnya yang menyebabkan kegagalan.

Bahkan jika input Anda berpotensi mengandung substring apa pun, ada cara yang lebih pendek daripada (?!). Rasa apa pun yang memungkinkan jangkar muncul dalam suatu pola yang bertentangan dengan akhir, dapat menggunakan salah satu dari solusi 2 karakter berikut:

a^
$a

Namun perlu dicatat bahwa beberapa rasa akan memperlakukan ^dan $sebagai karakter literal di posisi ini, karena mereka jelas tidak masuk akal sebagai jangkar.

Dalam rasa ECMAScript ada juga solusi 2 karakter yang agak elegan

[]

Ini adalah kelas karakter kosong, yang mencoba memastikan bahwa karakter berikutnya adalah salah satu dari yang ada di kelas - tetapi tidak ada karakter di kelas, jadi ini selalu gagal. Perhatikan bahwa ini tidak akan berhasil dalam citarasa lain, karena kelas karakter biasanya tidak dapat kosong.

Martin Ender
sumber
8

Optimalkan Anda ATAU

Setiap kali Anda memiliki 3 atau lebih alternatif di RegEx Anda:

/aliceblue|antiquewhite|aquamarine|azure/

Periksa untuk melihat apakah ada awal yang umum:

/a(liceblue|ntiquewhite|quamarine|zure)/

Dan mungkin bahkan akhir yang umum?

/a(liceblu|ntiquewhit|quamarin|zur)e/

Catatan: 3 hanyalah awal dan akan menjelaskan panjang yang sama, 4+ akan membuat perbedaan


Tetapi bagaimana jika tidak semuanya memiliki awalan yang sama? (spasi hanya ditambahkan untuk kejelasan)

/aliceblue|antiquewhite|aqua|aquamarine|azure
|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood
|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan/

Kelompokkan mereka, selama aturan 3+ masuk akal:

/a(liceblue|ntiquewhite|qua|quamarine|zure)
|b(eige|isque|lack|lanchedalmond|lue|lueviolet|rown|urlywood)
|c(adetblue|hartreuse|hocolate|oral|ornflowerblue|ornsilk|rimson|yan)/

Atau bahkan menggeneralisasi jika entropi memuaskan pengguna kami:

/\w(liceblue|ntiquewhite|qua|quamarine|zure
|eige|isque|lack|lanchedalmond|lue|lueviolet|rown|urlywood
|adetblue|hartreuse|hocolate|oral|ornflowerblue|ornsilk|rimson|yan)/

^ dalam hal ini kami yakin kami tidak mendapatkan clueataucrown slack Ryan

Ini "menurut beberapa tes" juga meningkatkan kinerja, karena menyediakan jangkar untuk memulai.

CSᵠ
sumber
1
Jika awal atau akhir yang sama lebih dari satu karakter, bahkan pengelompokan dua dapat membuat perbedaan. Suka aqua|aquamarineaqua(|marine)atau aqua(marine)?.
Paŭlo Ebermann
6

Yang ini cukup sederhana, tetapi layak disebutkan:

Jika Anda mendapati diri Anda mengulang kelas karakter, [a-zA-Z]Anda mungkin bisa menggunakan [a-z]dan menambahkan i( case- i nsensitive modifier) ​​ke regex Anda.

Misalnya, di Ruby, dua regex berikut ini setara:

/[a-zA-Z]+\d{3}[a-zA-Z]+/
/[a-z]+\d{3}[a-z]/i - 7 byte lebih pendek

Dalam hal ini, pengubah lainnya dapat mempersingkat panjang total Anda juga. Alih-alih melakukan ini:

/(.|\n)/

yang sesuai dengan karakter APAPUN (karena dot tidak sesuai newline), menggunakan s perapian di tungku-line modifier s, yang membuat baris baru dot pertandingan.

/./s - 3 byte lebih pendek


Di Ruby, ada satu ton Kelas Karakter bawaan untuk regex. Lihat halaman ini dan cari "Karakter Properties".
Contoh yang bagus adalah "Simbol Mata Uang". Menurut Wikipedia ada satu ton simbol mata uang yang mungkin, dan untuk menempatkannya dalam kelas karakter akan sangat mahal ( [$฿¢₡Ð₫€.....]) sedangkan Anda bisa mencocokkannya dengan 6 byte:\p{Sc}

Devon Parsons
sumber
1
Kecuali JavaScript, di mana spengubah tidak didukung. :( Tapi di sana Anda dapat menggunakan /[^]/trik milik JavaScript .
manatwork
Catatan yang (.|\n)bahkan tidak bekerja dalam beberapa rasa, karena .sering juga tidak cocok dengan jenis pemisah garis lainnya. Namun, cara biasa untuk melakukan ini (tanpa s) adalah [\s\S]byte yang sama dengan (.|\n).
Martin Ender
@ MartinBüttner, ide saya adalah untuk tetap bersama dengan tips terkait akhir baris lainnya. Tetapi jika Anda merasa jawaban ini lebih tentang pengubah, saya tidak keberatan jika Anda memposting ulang.
manatwork
@manatwork selesai (dan menambahkan trik khusus non-ES terkait juga)
Martin Ender
6

Pengurai bahasa yang sederhana

Anda bisa membuat parser yang sangat sederhana dengan seperti RE \d+|\w+|".*?"|\n|\S. Token yang harus Anda cocokkan dipisahkan dengan karakter RE 'atau'.

Setiap kali mesin RE mencoba untuk mencocokkan pada posisi saat ini dalam teks, itu akan mencoba pola pertama, kemudian yang kedua, dll. Jika gagal (pada karakter spasi di sini misalnya), itu bergerak dan mencoba pertandingan lagi . Ketertiban itu penting. Jika kami menempatkan \Sistilah sebelum \d+istilah, maka \Sakan cocok terlebih dahulu pada karakter non-spasi yang akan merusak pengurai kami.

Itu ".*?" String matcher menggunakan pengubah non-serakah sehingga kita hanya mencocokkan satu string pada suatu waktu. Jika RE Anda tidak memiliki fungsi non-serakah, Anda dapat menggunakan "[^"]*"yang setara.

Contoh Python:

text = 'd="dogfinder"\nx=sum(ord(c)*872 for c in "fish"+d[3:])'
pat = r'\d+|\w+|".*?"|\n|\S'
print re.findall(pat, text)

['d', '=', '"dogfinder"', '\n', 'x', '=', 'sum', '(', 'ord', '(', 'c', ')',
    '*', '872', 'for', 'c', 'in', '"fish"', '+', 'd', '[', '3', ':', ']', ')']

Contoh Python Golf:

# assume we have language text in A, and a token processing function P
map(P,findall(r'\d+|\w+|".*?"|\n|\S',A))

Anda dapat menyesuaikan pola dan urutannya untuk bahasa yang Anda harus cocok. Teknik ini berfungsi baik untuk ekspresi JSON, HTML dasar, dan numerik. Ini telah berhasil digunakan berkali-kali dengan Python 2, tetapi harus cukup umum untuk bekerja di lingkungan lain.

Ksatria Logika
sumber
6

\K bukannya tampilan positif di belakang

PCRE dan Perl mendukung urutan pelarian \K, yang mengatur ulang awal pertandingan. Itu ab\Kcdakan membutuhkan string input Anda berisi abcdtetapi kecocokan yang dilaporkan hanya akan cd.

Jika Anda menggunakan tampilan positif di belakang pada awal pola Anda (yang mungkin merupakan tempat yang paling mungkin), maka dalam kebanyakan kasus, Anda dapat menggunakan \Ksebagai gantinya dan menyimpan 3 byte:

(?<=abc)def
abc\Kdef

Ini setara untuk sebagian besar tujuan, tetapi tidak sepenuhnya. Perbedaan membawa keuntungan dan kerugian:

  • Terbalik: PCRE dan Perl tidak mendukung tampilan panjang sewenang-wenang (hanya .NET yang mendukung). Artinya, Anda tidak dapat melakukan sesuatu seperti (?<=ab*). Tetapi dengan \KAnda dapat menempatkan segala macam pola di depannya! Begitu ab*\Kberhasil. Ini sebenarnya membuat teknik ini jauh lebih kuat dalam kasus-kasus di mana itu berlaku.
  • Terbalik: Lookarounds tidak mundur. Ini relevan jika Anda ingin menangkap sesuatu dalam tampilan di belakang untuk merujuk kembali nanti, tetapi ada beberapa kemungkinan tangkapan yang semuanya mengarah ke pertandingan yang valid. Dalam hal ini, mesin regex hanya akan mencoba salah satu dari kemungkinan itu. Ketika menggunakan\K bagian regex sedang ditelusuri kembali seperti yang lainnya.
  • Kekurangan: Seperti yang mungkin Anda ketahui, beberapa korek api pada regex tidak bisa tumpang tindih. Seringkali, lookaround digunakan untuk mengatasi keterbatasan ini sebagian, karena lookahead dapat memvalidasi bagian dari string yang sudah dikonsumsi oleh pertandingan sebelumnya. Jadi, jika Anda ingin mencocokkan semua karakter yang mengikuti ab Anda mungkin menggunakan (?<=ab).. Diberikan input

    ababc
    

    ini akan cocok dengan yang kedua adan yang c. Ini tidak dapat direproduksi dengan \K. Jika Anda menggunakan ab\K., Anda hanya akan mendapatkan pertandingan pertama, karena sekarang abtidak ada dalam pencarian.

Martin Ender
sumber
Jika suatu pola menggunakan \Kurutan lolos dalam pernyataan positif, awal pertandingan yang dilaporkan dapat lebih besar dari akhir pertandingan.
hwnd
@hwnd Maksud saya adalah bahwa diberikan ababc, tidak ada cara untuk mencocokkan kedua adan cdengan \K. Anda hanya akan mendapatkan satu pertandingan.
Martin Ender
Anda benar, bukan dengan fitur itu sendiri. Anda harus berlabuh dengan\G
hwnd
@hwnd Ah saya mengerti maksud Anda sekarang. Tapi saya kira pada titik itu (dari perspektif golf) Anda lebih baik dengan tampilan negatif di belakang, karena Anda sebenarnya mungkin bahkan memerlukannya karena Anda tidak dapat memastikan bahwa .dari pertandingan terakhir sebenarnya adalah a.
Martin Ender
1
Penggunaan \ K =) yang menarik
hwnd
5

Mencocokkan karakter apa pun

Rasa ECMAScript kurang memiliki spengubah yang .cocok dengan karakter apa pun (termasuk baris baru). Ini berarti tidak ada solusi satu karakter untuk mencocokkan karakter yang sepenuhnya arbitrer. Solusi standar dalam rasa lain (ketika seseorang tidak ingin menggunakan skarena suatu alasan) adalah [\s\S]. Namun, ECMAScript adalah satu-satunya rasa (untuk pengetahuan saya) yang mendukung kelas karakter kosong, dan karenanya memiliki alternatif yang jauh lebih pendek: [^]. Ini adalah kelas karakter kosong yang dinegasikan - yaitu, cocok dengan karakter apa pun.

Bahkan untuk citarasa lain, kita dapat belajar dari teknik ini: jika kita tidak ingin menggunakan s(misalnya karena kita masih perlu makna biasa .di tempat lain), masih bisa ada cara yang lebih pendek untuk mencocokkan kedua baris baru dan karakter yang dapat dicetak, asalkan ada beberapa karakter yang kita tahu tidak muncul di input. Katakanlah, kami sedang memproses angka yang dibatasi oleh baris baru. Kemudian kita dapat mencocokkan karakter apa pun dengan [^!], karena kita tahu itu !tidak akan pernah menjadi bagian dari string. Ini menghemat dua byte di atas naif [\s\S]atau [\d\n].

Martin Ender
sumber
4
Dalam Perl, \Nberarti persis apa yang .berarti di luar /smode, kecuali itu tidak terpengaruh oleh mode.
Konrad Borowski
4

Gunakan grup atom dan quantifiers posesif

Saya menemukan kelompok atom ( (?>...)) dan bilangan posesif ( ?+, *+, ++, {m,n}+) kadang-kadang sangat berguna untuk golf. Ini cocok dengan string dan tidak memungkinkan untuk mengulangi kembali nanti. Jadi itu hanya akan cocok dengan string pertama yang cocok yang ditemukan oleh mesin regex.

Misalnya: Untuk mencocokkan string dengan angka ganjil adi awal, yang tidak diikuti oleh lebih banyak a, Anda dapat menggunakan:

^(aa)*+a
^(?>(aa)*)a

Ini memungkinkan Anda untuk menggunakan hal-hal seperti .* bebas, dan jika ada kecocokan yang jelas, tidak akan ada kemungkinan lain yang cocok dengan terlalu banyak atau terlalu sedikit karakter, yang dapat merusak pola Anda.

Di .NET regex (yang tidak memiliki quantifiers posesif), Anda dapat menggunakan ini untuk melompati grup 1 kelipatan terbesar dari 3 (dengan maksimum 30) kali (tidak bermain golf dengan sangat baik):

(?>((?<-1>){3}|){10})
jimmy23013
sumber
1
Script ECMA juga hilang kuantifiers posesif atau kelompok atom :(
CSᵠ
4

Lupakan grup yang ditangkap setelah subekspresi (PCRE)

Untuk regex ini:

^((a)(?=\2))(?!\2)

Jika Anda ingin menghapus \ 2 setelah grup 1, Anda dapat menggunakan rekursi:

^((a)(?=\2)){0}(?1)(?!\2)

Ini akan cocok aadengan yang sebelumnya tidak. Terkadang Anda juga bisa menggunakan ??atau bahkan ?menggantikannya {0}.

Ini mungkin berguna jika Anda sering menggunakan rekursi, dan beberapa referensi balik atau kelompok bersyarat muncul di tempat yang berbeda di regex Anda.

Juga perhatikan bahwa kelompok atom diasumsikan untuk rekursi dalam PCRE. Jadi ini tidak akan cocok dengan satu huruf a:

^(a?){0}(?1)a

Saya belum mencobanya dalam rasa lain.

Untuk lookaheads, Anda juga dapat menggunakan negatif ganda untuk tujuan ini:

^(?!(?!(a)(?=\1))).(?!\1)
jimmy23013
sumber
4

Ekspresi opsional

Terkadang berguna untuk mengingatnya

(abc)?

adalah sebagian besar sama dengan

(abc|)

Namun ada perbedaan kecil: dalam kasus pertama, grup tersebut menangkap abcatau tidak menangkap sama sekali. Kasus terakhir akan membuat referensi balik gagal tanpa syarat. Dalam ekspresi kedua, grup akan menangkap abcatau string kosong, di mana case terakhir akan membuat pertandingan referensi-ulang tanpa syarat. Untuk meniru perilaku yang terakhir dengan ?Anda harus mengelilingi segala sesuatu di grup lain yang akan menelan biaya dua byte:

((abc)?)

Versi yang digunakan |juga berguna ketika Anda ingin membungkus ekspresi dalam beberapa bentuk grup lain dan tidak peduli tentang penangkapan:

(?=(abc)?)
(?=abc|)

(?>(abc)?)
(?>abc|)

Akhirnya, trik ini juga dapat diterapkan pada ungreedy di ?mana ia menyimpan satu byte bahkan dalam bentuk mentahnya (dan akibatnya 3 byte bila dikombinasikan dengan bentuk grup lain):

(abc)??
(|abc)
Martin Ender
sumber
1

Beberapa lookaheads yang selalu cocok (.NET)

Jika Anda memiliki 3 atau lebih konstruksi lookahead yang selalu cocok (untuk menangkap subekspresi), atau ada penjumlahan pada lookahead diikuti oleh sesuatu yang lain, jadi mereka harus berada dalam kelompok yang tidak perlu ditangkap:

(?=a)(?=b)(?=c)
((?=a)b){...}

Ini lebih pendek:

(?(?(?(a)b)c))
(?(a)b){...}

di mana aseharusnya tidak menjadi nama grup yang ditangkap. Anda tidak dapat menggunakan |untuk maksud hal biasa di bdan ctanpa menambahkan sepasang tanda kurung.

Sayangnya, menyeimbangkan kelompok-kelompok di kondisional tampak buggy, membuatnya tidak berguna dalam banyak kasus.

jimmy23013
sumber