Javascript: ekuivalen dengan tampilan negatif?

144

Apakah ada cara untuk mencapai persamaan tampilan negatif di ekspresi reguler javascript? Saya perlu mencocokkan string yang tidak dimulai dengan kumpulan karakter tertentu.

Sepertinya saya tidak dapat menemukan regex yang melakukan ini tanpa gagal jika bagian yang cocok ditemukan di awal string. Penampilan negatif tampaknya menjadi satu-satunya jawaban, tetapi javascript tidak memilikinya.

EDIT: Ini adalah regex yang ingin saya kerjakan, tetapi tidak:

(?<!([abcdefg]))m

Jadi itu akan cocok dengan 'm' di 'jim' atau 'm', tapi bukan 'jam'

Andrew Ensley
sumber
Pertimbangkan untuk memposting regex karena akan terlihat dengan tampilan negatif di belakang; sehingga lebih mudah untuk merespons.
Daniel LeCheminant
1
Mereka yang ingin melacak adopsi lookbehind etc. silakan merujuk ke tabel kompatibilitas ECMAScript 2016+
Wiktor Stribiżew
@ WiktorStribew: Lihat-belakang ditambahkan pada spesifikasi 2018. Chrome mendukungnya, tetapi Firefox masih belum menerapkan spesifikasi tersebut .
Lonnie Best
Apakah ini perlu melihat ke belakang? Tentang apa (?:[^abcdefg]|^)(m)? Seperti di"mango".match(/(?:[^abcdefg]|^)(m)/)[1]
slebetman

Jawaban:

65

Lookbehind Pernyataan mendapat diterima ke dalam spesifikasi ECMAScript pada 2018.

Penggunaan lookbehind positif:

console.log(
  "$9.99  €8.47".match(/(?<=\$)\d+(\.\d*)?/) // Matches "9.99"
);

Penggunaan tampilan negatif di belakang:

console.log(
  "$9.99  €8.47".match(/(?<!\$)\d+(?:\.\d*)/) // Matches "8.47"
);

Dukungan platform:

Okku
sumber
2
apakah ada polyfill?
Killy
1
@Killy tidak sejauh yang saya tahu, dan saya ragu akan ada, karena membuat satu akan berpotensi sangat tidak praktis (
Yaitu
Bagaimana dengan menggunakan plugin babel, apakah mungkin untuk dikompilasi ke ES5 atau sudah mendukung ES6?
Stefan J
1
@IlpoOksanen Saya pikir yang Anda maksud adalah memperluas implementasi RegEx .. itulah yang dilakukan polyfill .... dan tidak ada yang salah dengan menulis logika dalam JavaScript
neaumusic
1
Apa yang kau bicarakan? Hampir semua proposal terinspirasi oleh bahasa lain dan mereka akan selalu memilih untuk mencocokkan sintaks dan semantik bahasa lain yang masuk akal dalam konteks JS idiomatik dan kompatibilitas mundur. Saya pikir saya cukup jelas menyatakan bahwa baik lookbehinds positif dan negatif diterima ke dalam spesifikasi 2018 pada tahun 2017 dan saya memberikan tautan ke sumber. Selanjutnya, saya menjelaskan secara rinci platform mana yang menerapkan spesifikasi tersebut dan apa status platform lain - dan bahkan telah memperbaruinya sejak saat itu. Tentu itu bukan fitur Regexp terakhir yang akan kita lihat
Okku
87

Sejak 2018, Lookbehind Assertions adalah bagian dari spesifikasi bahasa ECMAScript .

// positive lookbehind
(?<=...)
// negative lookbehind
(?<!...)

Jawab sebelum 2018

Karena Javascript mendukung lookahead negatif , salah satu cara untuk melakukannya adalah:

  1. membalikkan string masukan

  2. cocokkan dengan ekspresi reguler terbalik

  3. membalikkan dan memformat ulang korek api


const reverse = s => s.split('').reverse().join('');

const test = (stringToTests, reversedRegexp) => stringToTests
  .map(reverse)
  .forEach((s,i) => {
    const match = reversedRegexp.test(s);
    console.log(stringToTests[i], match, 'token:', match ? reverse(reversedRegexp.exec(s)[0]) : 'Ø');
  });

Contoh 1:

Mengikuti pertanyaan @ andrew-ensley:

test(['jim', 'm', 'jam'], /m(?!([abcdefg]))/)

Keluaran:

jim true token: m
m true token: m
jam false token: Ø

Contoh 2:

Mengikuti komentar @neaumusic (cocok max-heighttapi tidak line-height, tokennya height):

test(['max-height', 'line-height'], /thgieh(?!(-enil))/)

Keluaran:

max-height true token: height
line-height false token: Ø
JBE
sumber
36
Masalah dengan pendekatan ini adalah bahwa pendekatan ini tidak berfungsi jika Anda memiliki tampilan yang
menghadap ke belakang
3
bisakah Anda menunjukkan contoh yang berfungsi, katakan saya ingin cocok max-heighttetapi tidak line-heightdan saya hanya ingin pertandingan menjadiheight
neaumusic
Tidak membantu jika tugasnya adalah mengganti dua simbol identik yang berurutan (dan tidak lebih dari 2) yang tidak diawali dengan beberapa simbol. ''(?!\()akan menggantikan tanda kutip ''(''test'''''''testdari ujung yang lain, sehingga meninggalkan (''test'NNNtestdaripada (''testNNN'test.
Wiktor Stribiżew
63

Misalkan Anda ingin menemukan semua yang inttidak didahului oleh unsigned:

Dengan dukungan untuk melihat ke belakang negatif:

(?<!unsigned )int

Tanpa dukungan untuk melihat ke belakang negatif:

((?!unsigned ).{9}|^.{0,8})int

Pada dasarnya idenya adalah untuk mengambil n karakter sebelumnya dan mengecualikan kecocokan dengan pandangan ke depan negatif, tetapi juga mencocokkan kasus di mana tidak ada karakter n sebelumnya. (di mana n adalah panjang tampilan ke belakang).

Jadi regex yang dimaksud:

(?<!([abcdefg]))m

akan diterjemahkan ke:

((?!([abcdefg])).|^)m

Anda mungkin perlu bermain dengan kelompok penangkap untuk menemukan tempat yang tepat dari string yang menarik minat Anda atau Anda ingin mengganti bagian tertentu dengan bagian lain.

Kamil Szot
sumber
2
Ini harus menjadi jawaban yang benar. Lihat: "So it would match the 'm' in 'jim' or 'm', but not 'jam'".replace(/(j(?!([abcdefg])).|^)m/g, "$1[MATCH]") pengembalian "So it would match the 'm' in 'ji[MATCH]' or 'm', but not 'jam'" Ini sangat sederhana dan berhasil!
Asrail
Cemerlang! Gunakan pandangan negatif ke depan sebagai solusi untuk JavaScript lama!
Peter Thoeny
41

Strategi Mijoja berfungsi untuk kasus spesifik Anda tetapi tidak secara umum:

js>newString = "Fall ball bill balll llama".replace(/(ba)?ll/g,
   function($0,$1){ return $1?$0:"[match]";});
Fa[match] ball bi[match] balll [match]ama

Berikut adalah contoh di mana tujuannya adalah untuk mencocokkan double-l tetapi tidak jika diawali dengan "ba". Perhatikan kata "balll" - true lookbehind seharusnya menyembunyikan 2 l pertama tetapi cocok dengan pasangan kedua. Tapi dengan mencocokkan 2 l pertama dan kemudian mengabaikan kecocokan itu sebagai positif palsu, mesin ekspresi reguler melanjutkan dari akhir pertandingan itu, dan mengabaikan karakter apa pun dalam positif palsu.

Jason S
sumber
5
Ah, kamu benar. Namun, ini jauh lebih dekat dari sebelumnya. Saya dapat menerima ini sampai sesuatu yang lebih baik datang (seperti javascript benar-benar menerapkan lookbehinds).
Andrew Ensley
33

Menggunakan

newString = string.replace(/([abcdefg])?m/, function($0,$1){ return $1?$0:'m';});
Mijoja
sumber
10
Ini tidak melakukan apa-apa: newStringakan selalu sama string. Mengapa begitu banyak suara positif?
MikeM
@MikeM: karena intinya adalah untuk mendemonstrasikan teknik pencocokan.
bug
57
@bug. Demonstrasi yang tidak melakukan apa-apa adalah jenis demonstrasi yang aneh. Jawabannya muncul seolah-olah hanya disalin dan ditempel tanpa pemahaman tentang cara kerjanya. Dengan demikian kurangnya penjelasan yang menyertai dan kegagalan untuk menunjukkan bahwa semuanya telah cocok.
MikeM
2
@MikeM: aturan SO adalah, jika menjawab pertanyaan seperti yang tertulis , itu benar. OP tidak menentukan kasus penggunaan
bug
7
Konsepnya benar, tapi ya itu tidak didemokan dengan baik. Coba jalankan ini di konsol JS ... "Jim Jam Momm m".replace(/([abcdefg])?m/g, function($0, $1){ return $1 ? $0 : '[match]'; });. Ini harus kembali Ji[match] Jam Mo[match][match] [match]. Tetapi perhatikan juga bahwa seperti yang disebutkan Jason di bawah ini, ini dapat gagal pada kasus edge tertentu.
Simon East
11

Anda dapat menentukan grup non-penangkap dengan meniadakan kumpulan karakter Anda:

(?:[^a-g])m

... yang akan cocok dengan setiap m TIDAK yang didahului oleh salah satu huruf itu.

Klemen Slavič
sumber
2
Saya pikir pertandingan itu sebenarnya juga mencakup karakter sebelumnya.
Sam
4
^ ini benar. Kelas karakter mewakili ... karakter! Semua yang dilakukan grup non-penangkap Anda tidak membuat nilai itu tersedia dalam konteks pengganti. Ekspresi Anda tidak mengatakan "setiap m TIDAK didahului oleh salah satu dari huruf-huruf itu" itu mengatakan "setiap m didahului oleh karakter yang BUKAN salah satu dari huruf-huruf itu"
theflowersoftime
5
Untuk jawaban yang juga menyelesaikan masalah asli (awal string), itu juga harus menyertakan opsi, sehingga regex yang dihasilkan adalah (?:[^a-g]|^)m. Lihat regex101.com/r/jL1iW6/2 untuk menjalankan contoh.
Johny Skovdal
Menggunakan logika kosong tidak selalu menghasilkan efek yang diinginkan.
GoldBishop
2

Ini adalah bagaimana saya mencapai str.split(/(?<!^)@/)Node.js 8 (yang tidak mendukung lookbehind):

str.split('').reverse().join('').split(/@(?!$)/).map(s => s.split('').reverse().join('')).reverse()

Bekerja? Ya (unicode belum teruji). Tidak menyenangkan? Iya.

Fishrock123
sumber
1

mengikuti ide Mijoja, dan menggambar dari masalah yang diungkapkan oleh JasonS, saya punya ide ini; saya memeriksa sedikit tetapi tidak yakin pada diri saya sendiri, jadi verifikasi oleh seseorang yang lebih ahli daripada saya di js regex akan sangat bagus :)

var re = /(?=(..|^.?)(ll))/g
         // matches empty string position
         // whenever this position is followed by
         // a string of length equal or inferior (in case of "^")
         // to "lookbehind" value
         // + actual value we would want to match

,   str = "Fall ball bill balll llama"

,   str_done = str
,   len_difference = 0
,   doer = function (where_in_str, to_replace)
    {
        str_done = str_done.slice(0, where_in_str + len_difference)
        +   "[match]"
        +   str_done.slice(where_in_str + len_difference + to_replace.length)

        len_difference = str_done.length - str.length
            /*  if str smaller:
                    len_difference will be positive
                else will be negative
            */

    }   /*  the actual function that would do whatever we want to do
            with the matches;
            this above is only an example from Jason's */



        /*  function input of .replace(),
            only there to test the value of $behind
            and if negative, call doer() with interesting parameters */
,   checker = function ($match, $behind, $after, $where, $str)
    {
        if ($behind !== "ba")
            doer
            (
                $where + $behind.length
            ,   $after
                /*  one will choose the interesting arguments
                    to give to the doer, it's only an example */
            )
        return $match // empty string anyhow, but well
    }
str.replace(re, checker)
console.log(str_done)

keluaran pribadi saya:

Fa[match] ball bi[match] bal[match] [match]ama

prinsipnya adalah memanggil checkersetiap titik dalam string di antara dua karakter mana pun, kapan pun posisi itu merupakan titik awal dari:

--- setiap substring dari ukuran apa yang tidak diinginkan (di sini 'ba', dengan demikian ..) (jika ukuran itu diketahui; jika tidak, mungkin akan lebih sulit untuk dilakukan)

--- --- atau lebih kecil dari itu jika itu adalah awal dari string: ^.?

dan, setelah ini,

--- apa yang sebenarnya dicari (di sini 'll').

Pada setiap panggilan checker, akan ada tes untuk memeriksa apakah nilai sebelumnya llbukan yang tidak kita inginkan ( !== 'ba'); jika itu masalahnya, kita memanggil fungsi lain, dan itu harus yang ini ( doer) yang akan membuat perubahan pada str, jika tujuannya adalah yang ini, atau lebih umum, yang akan memasukkan data yang diperlukan untuk diproses secara manual hasil pemindaian str.

di sini kita mengubah string jadi kita perlu menyimpan jejak perbedaan panjang untuk mengimbangi lokasi yang diberikan replace, semua dihitung str, yang dengan sendirinya tidak pernah berubah.

karena string primitif tidak dapat diubah, kita dapat menggunakan variabel struntuk menyimpan hasil dari seluruh operasi, tetapi saya pikir contoh, yang sudah diperumit oleh penggantian, akan lebih jelas dengan variabel lain ( str_done).

Saya rasa dalam hal kinerja itu pasti cukup keras: semua penggantian tidak berguna dari '' ke '', this str.length-1kali, ditambah di sini penggantian manual oleh pelaku, yang berarti banyak pemotongan ... mungkin dalam kasus khusus di atas yang bisa dikelompokkan, dengan memotong string hanya sekali-potong sekitar di mana kita ingin memasukkan [match]dan .join()ing dengan [match]sendirinya.

hal lain adalah bahwa saya tidak tahu bagaimana cara menangani kasus yang lebih kompleks, yaitu, nilai kompleks untuk lookbehind palsu ... panjangnya mungkin data yang paling bermasalah untuk didapatkan.

dan, dalam checkerkasus beberapa kemungkinan nilai yang tidak diinginkan untuk $ di belakang, kita harus mengujinya dengan regex lain (untuk di-cache (dibuat) di luar checkeradalah yang terbaik, untuk menghindari objek regex yang sama dibuat pada setiap panggilan untuk checker) untuk mengetahui apakah itu yang ingin kita hindari atau tidak.

harap saya sudah jelas; jika tidak, jangan ragu, saya akan mencoba lebih baik. :)

Homer Simpson
sumber
1

Menggunakan case Anda, jika Anda ingin mengganti m dengan sesuatu, misalnya mengubahnya menjadi huruf besar M, Anda dapat meniadakan set dalam kelompok penangkap.

cocok ([^a-g])m, ganti dengan$1M

"jim jam".replace(/([^a-g])m/g, "$1M")
\\jiM jam

([^a-g])akan mencocokkan karakter apa pun yang tidak ( ^) dalam a-gjangkauan, dan menyimpannya di grup penangkap pertama, sehingga Anda dapat mengaksesnya dengan $1.

Jadi kita menemukan imdi jimdan menggantinya dengan iMyang hasil dalam jiM.

Traxo
sumber
1

Seperti yang disebutkan sebelumnya, JavaScript memungkinkan lookbehinds sekarang. Di browser lama, Anda masih membutuhkan solusi.

Saya yakin kepala saya tidak ada cara untuk menemukan regex tanpa lookbehind yang memberikan hasil dengan tepat. Yang dapat Anda lakukan hanyalah bekerja dengan kelompok. Misalkan Anda memiliki regex (?<!Before)Wanted, di mana Wantedregex yang ingin Anda cocokkan dan Beforeregex yang menghitung apa yang tidak boleh mendahului pencocokan. Hal terbaik yang dapat Anda lakukan adalah meniadakan regex Beforedan menggunakan regex NotBefore(Wanted). Hasil yang diinginkan adalah kelompok pertama $1.

Dalam kasus Anda Before=[abcdefg]yang mudah dinegasikan NotBefore=[^abcdefg]. Jadi regexnya adalah [^abcdefg](m). Jika Anda membutuhkan posisi Wanted, Anda harus mengelompokkan NotBeforejuga, sehingga hasil yang diinginkan adalah kelompok kedua.

Jika kecocokan Beforepola memiliki panjang tetap n, yaitu, jika pola tidak berisi token berulang, Anda dapat menghindari meniadakan Beforepola dan menggunakan ekspresi reguler (?!Before).{n}(Wanted), tetapi tetap harus menggunakan grup pertama atau menggunakan ekspresi reguler (?!Before)(.{n})(Wanted)dan menggunakan yang kedua kelompok. Dalam contoh ini, pola Beforesebenarnya memiliki panjang tetap, yaitu 1, jadi gunakan regex (?![abcdefg]).(m)atau (?![abcdefg])(.)(m). Jika Anda tertarik dengan semua pertandingan, tambahkan gbendera, lihat cuplikan kode saya:

function TestSORegEx() {
  var s = "Donald Trump doesn't like jam, but Homer Simpson does.";
  var reg = /(?![abcdefg])(.{1})(m)/gm;
  var out = "Matches and groups of the regex " + 
            "/(?![abcdefg])(.{1})(m)/gm in \ns = \"" + s + "\"";
  var match = reg.exec(s);
  while(match) {
    var start = match.index + match[1].length;
    out += "\nWhole match: " + match[0] + ", starts at: " + match.index
        +  ". Desired match: " + match[2] + ", starts at: " + start + ".";   
    match = reg.exec(s);
  }
  out += "\nResulting string after statement s.replace(reg, \"$1*$2*\")\n"
         + s.replace(reg, "$1*$2*");
  alert(out);
}
Dietrich Baumgarten
sumber
0

Ini secara efektif melakukannya

"jim".match(/[^a-g]m/)
> ["im"]
"jam".match(/[^a-g]m/)
> null

Cari dan ganti contoh

"jim jam".replace(/([^a-g])m/g, "$1M")
> "jiM jam"

Perhatikan bahwa string lihat-balik negatif harus sepanjang 1 karakter agar berfungsi.

Curtis Yallop
sumber
1
Tidak terlalu. Dalam "jim", saya tidak menginginkan "i"; hanya mereka". Dan "m".match(/[^a-g]m/)yeilds nulljuga. Saya ingin "m" dalam kasus itu juga.
Andrew Ensley
-1

/(?![abcdefg])[^abcdefg]m/gi ya ini tipuan.

Techsin
sumber
5
Pemeriksaan (?![abcdefg])ini benar-benar berlebihan, karena [^abcdefg]sudah melakukan tugasnya untuk mencegah karakter tersebut cocok.
nhahtdh
2
Ini tidak akan cocok dengan 'm' tanpa karakter sebelumnya.
Andrew Ensley