Pola Regex untuk Dicocokkan, Tidak Termasuk ketika… / Kecuali antara


108

--Edit-- Jawaban saat ini memiliki beberapa ide berguna tetapi saya ingin sesuatu yang lebih lengkap sehingga saya dapat 100% memahami dan menggunakan kembali; itulah mengapa saya menetapkan bounty. Juga gagasan yang bekerja di mana-mana lebih baik bagi saya daripada tidak seperti sintaks standar\K

Pertanyaan ini adalah tentang bagaimana saya bisa mencocokkan pola kecuali beberapa situasi s1 s2 s3. Saya memberikan contoh spesifik untuk menunjukkan maksud saya tetapi lebih memilih jawaban umum yang saya dapat 100% mengerti sehingga saya dapat menggunakannya kembali dalam situasi lain.

Contoh

Saya ingin mencocokkan lima digit menggunakan \b\d{5}\btetapi tidak dalam tiga situasi s1 s2 s3:

s1: Bukan pada baris yang diakhiri dengan titik seperti kalimat ini.

s2: Tidak dimanapun di dalam parens.

s3: Tidak di dalam blok yang dimulai dengan if(dan diakhiri dengan//endif

Saya tahu bagaimana menyelesaikan salah satu dari s1 s2 s3 dengan lookahead dan lookbehind, terutama di C # lookbehind atau \Kdi PHP.

Misalnya

s1 (?m)(?!\d+.*?\.$)\d+

s3 dengan C # lookbehind (?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

s3 dengan PHP \ K (?:(?:if\(.*?//endif)\D*)*\K\d+

Tapi campuran kondisi bersama membuat kepalaku meledak. Kabar yang lebih buruk lagi adalah saya mungkin perlu menambahkan kondisi lain s4 s5 di lain waktu.

Kabar baiknya adalah, saya tidak peduli jika saya memproses file menggunakan bahasa yang paling umum seperti PHP, C #, Python, atau mesin cuci tetangga saya. :) Saya cukup banyak pemula di Python & Java tetapi tertarik untuk belajar jika ada solusi.

Jadi saya datang ke sini untuk melihat apakah seseorang memikirkan resep yang fleksibel.

Petunjuknya oke: Anda tidak perlu memberi saya kode lengkap. :)

Terima kasih.


1
\Ktidak ada sintaks php khusus. Harap uraikan dan klarifikasi apa yang ingin Anda katakan. Jika Anda bertujuan memberi tahu kami bahwa Anda tidak memerlukan solusi yang "rumit", Anda harus mengatakan apa yang rumit untuk Anda dan mengapa.
hakre

@hakre Maksud Anda karena ruby ​​sekarang menggunakannya dan dimulai dengan perl?
Hans Schindler

1
Tidak, karena itu PCRE yang bukan PHP (atau Ruby). Perl berbeda namun PCRE bertujuan agar kompatibel dengan Perl Regex.
hakre

Persyaratan s2 dan s3 Anda tampaknya bertentangan. s2 menyiratkan bahwa tanda kurung selalu cocok dan dapat disarangkan, tetapi s3 mensyaratkan bahwa: "if("buka paren ditutup, bukan dengan a ")", melainkan dengan "//endif":? Dan jika untuk s3 Anda benar-benar bermaksud bahwa klausa if harus ditutup dengan "//endif)":, maka persyaratan s3 adalah bagian dari s2.
ridgerunner

@hakre Ya saya tahu PCRE tetapi untuk menjelaskan, pertanyaannya adalah tentang bahasa pemrograman ... katanya especially in C# lookbehind or \K in PHP... Tapi C # lihat di belakang bukan hanya C # itu. NET jadi Anda bisa mengeluh juga Saya katakan C # bukan .NET :) Dan sebagai balasan saya katakan Ruby bukan Onigurama itu buruk juga ... Apakah ada bahasa lain yang menggunakan PCRE? Tidak berbicara tentang Notepad ++ atau alat server ini adalah pertanyaan tentang penggunaan fitur dalam bahasa Saya harap penjelasan dan maaf jika terlihat salah
Hans Schindler

Jawaban:


205

Hans, saya akan mengambil umpan dan menyempurnakan jawaban saya sebelumnya. Anda bilang ingin "sesuatu yang lebih lengkap" jadi saya harap Anda tidak keberatan dengan jawaban yang panjang — hanya mencoba menyenangkan. Mari kita mulai dengan beberapa latar belakang.

Pertama, ini adalah pertanyaan yang bagus. Sering ada pertanyaan tentang mencocokkan pola tertentu kecuali dalam konteks tertentu (misalnya, dalam blok kode atau di dalam tanda kurung). Pertanyaan-pertanyaan ini sering kali menimbulkan solusi yang cukup canggung. Jadi pertanyaan Anda tentang berbagai konteks merupakan tantangan khusus.

Mengherankan

Anehnya, setidaknya ada satu solusi efisien yang umum, mudah diterapkan, dan menyenangkan untuk dipelihara. Ia bekerja dengan semua ragam regex yang memungkinkan Anda untuk memeriksa grup tangkapan dalam kode Anda. Dan itu kebetulan menjawab sejumlah pertanyaan umum yang mungkin pada awalnya terdengar berbeda dari pertanyaan Anda: "cocokkan semuanya kecuali Donat", "ganti semua kecuali ...", "cocokkan semua kata kecuali yang ada di daftar hitam ibu saya", "abaikan tag "," cocok dengan suhu kecuali dicetak miring "...

Sayangnya, teknik ini tidak begitu dikenal: Saya memperkirakan bahwa dalam dua puluh pertanyaan SO yang dapat menggunakannya, hanya satu yang memiliki satu jawaban yang menyebutkannya — yang berarti mungkin satu dari lima puluh atau enam puluh jawaban. Lihat pertukaran saya dengan Kobi di komentar. Teknik ini dijelaskan secara mendalam dalam artikel ini yang menyebutnya (secara optimis) sebagai "trik regex terbaik yang pernah ada". Tanpa membahas terlalu detail, saya akan mencoba memberi Anda pemahaman yang kuat tentang cara kerja teknik ini. Untuk detail lebih lanjut dan contoh kode dalam berbagai bahasa, saya mendorong Anda untuk melihat sumber daya itu.

Variasi yang Lebih Diketahui

Ada variasi menggunakan sintaks khusus untuk Perl dan PHP yang melakukan hal yang sama. Anda akan melihatnya di SO di tangan master regex seperti CasimiretHippolyte dan HamZa . Saya akan memberi tahu Anda lebih banyak tentang ini di bawah, tetapi fokus saya di sini adalah pada solusi umum yang berfungsi dengan semua rasa regex (selama Anda dapat memeriksa grup tangkapan dalam kode Anda).

Terima kasih untuk semua latar belakangnya, zx81 ... Tapi apa resepnya?

Fakta Kunci

Metode ini mengembalikan kecocokan dalam penangkapan Grup 1. Itu sama sekali tidak peduli tentang pertandingan secara keseluruhan.

Faktanya, triknya adalah mencocokkan berbagai konteks yang tidak kita inginkan (merangkai konteks ini menggunakan |OR / alternasi) untuk "menetralkannya". Setelah pencocokan semua konteks yang tidak diinginkan, bagian akhir dari pergantian cocok apa yang kita lakukan inginkan dan menangkap ke Grup 1.

Resep umumnya adalah

Not_this_context|Not_this_either|StayAway|(WhatYouWant)

Ini akan cocok Not_this_context, tetapi dalam arti bahwa pertandingan tersebut masuk ke tempat sampah, karena kita tidak akan melihat pertandingan secara keseluruhan: kita hanya melihat tangkapan Grup 1.

Dalam kasus Anda, dengan angka Anda dan tiga konteks Anda untuk diabaikan, kami dapat melakukan:

s1|s2|s3|(\b\d+\b)

Perhatikan bahwa karena kami benar-benar mencocokkan s1, s2, dan s3 daripada mencoba menghindarinya dengan lookarounds, ekspresi individu untuk s1, s2 dan s3 dapat tetap jelas sebagai hari. (Mereka adalah subekspresi di setiap sisi a |)

Seluruh ekspresi bisa ditulis seperti ini:

(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)

Lihat demo ini (tapi fokus pada grup pengambilan di panel kanan bawah.)

Jika Anda secara mental mencoba membagi regex ini di setiap |pembatas, sebenarnya itu hanya rangkaian empat ekspresi yang sangat sederhana.

Untuk rasa yang mendukung spasi bebas, bacaan ini sangat baik.

(?mx)
      ### s1: Match line that ends with a period ###
^.*\.$  
|     ### OR s2: Match anything between parentheses ###
\([^\)]*\)  
|     ### OR s3: Match any if(...//endif block ###
if\(.*?//endif  
|     ### OR capture digits to Group 1 ###
(\b\d+\b)

Ini sangat mudah dibaca dan dipelihara.

Memperluas regex

Saat Anda ingin mengabaikan lebih banyak situasi s4 dan s5, Anda menambahkannya di lebih banyak alternatif di sebelah kiri:

s4|s5|s1|s2|s3|(\b\d+\b)

Bagaimana cara kerjanya?

Konteks yang tidak Anda inginkan ditambahkan ke daftar alternatif di sebelah kiri: mereka akan cocok, tetapi kecocokan keseluruhan ini tidak pernah diperiksa, jadi mencocokkannya adalah cara untuk meletakkannya di "tempat sampah".

Konten yang Anda inginkan, bagaimanapun, disimpan ke Grup 1. Anda kemudian harus memeriksa secara terprogram bahwa Grup 1 telah disetel dan tidak kosong. Ini adalah tugas pemrograman yang sepele (dan nanti kita akan membicarakan cara melakukannya), terutama mengingat hal itu membuat Anda memiliki regex sederhana yang dapat Anda pahami secara sekilas dan merevisi atau memperluas sesuai kebutuhan.

Saya tidak selalu menyukai visualisasi, tapi yang satu ini menunjukkan betapa sederhananya metodenya. Setiap "baris" sesuai dengan pertandingan potensial, tetapi hanya keuntungan yang diambil ke dalam Grup 1.

Visualisasi ekspresi reguler

Demo Debuggex

Variasi Perl / PCRE

Berbeda dengan solusi umum di atas, terdapat variasi Perl dan PCRE yang sering terlihat di SO, setidaknya di tangan Dewa regex seperti @CasimiretHippolyte dan @HamZa. Ini:

(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant

Dalam kasus Anda:

(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b

Variasi ini sedikit lebih mudah digunakan karena konten yang cocok dengan konteks s1, s2 dan s3 dilewati begitu saja, jadi Anda tidak perlu memeriksa tangkapan Grup 1 (perhatikan tanda kurung tidak ada). Pertandingan hanya berisiwhatYouWant

Perhatikan bahwa (*F), (*FAIL)dan (?!)semuanya sama. Jika Anda ingin lebih kabur, Anda bisa menggunakan(*SKIP)(?!)

demo untuk versi ini

Aplikasi

Berikut adalah beberapa masalah umum yang seringkali dapat diselesaikan dengan mudah oleh teknik ini. Anda akan melihat bahwa pilihan kata dapat membuat beberapa masalah ini terdengar berbeda padahal sebenarnya mereka hampir identik.

  1. Bagaimana saya bisa mencocokkan foo kecuali di mana saja di tag seperti <a stuff...>...</a>?
  2. Bagaimana cara mencocokkan foo kecuali dalam <i>tag atau cuplikan javascript (ketentuan lainnya)?
  3. Bagaimana saya bisa mencocokkan semua kata yang tidak ada di daftar hitam ini?
  4. Bagaimana saya bisa mengabaikan apapun di dalam blok SUB ... END SUB?
  5. Bagaimana saya bisa mencocokkan semuanya kecuali ... s1 s2 s3?

Bagaimana Memprogram Tangkapan Grup 1

Anda tidak memberikan kode, tetapi, untuk penyelesaiannya ... Kode untuk memeriksa Grup 1 jelas akan bergantung pada bahasa pilihan Anda. Bagaimanapun itu tidak boleh menambahkan lebih dari beberapa baris ke kode yang akan Anda gunakan untuk memeriksa kecocokan.

Jika ragu, saya sarankan Anda melihat bagian contoh kode dari artikel yang disebutkan sebelumnya, yang menyajikan kode untuk beberapa bahasa.

Alternatif

Bergantung pada kompleksitas pertanyaan, dan pada mesin regex yang digunakan, ada beberapa alternatif. Berikut ini dua hal yang dapat diterapkan pada sebagian besar situasi, termasuk beberapa ketentuan. Dalam pandangan saya, tidak ada yang semenarik s1|s2|s3|(whatYouWant)resepnya, jika hanya karena kejelasan selalu menang.

1. Ganti lalu Cocokkan.

Solusi bagus yang terdengar meretas tetapi berfungsi dengan baik di banyak lingkungan adalah bekerja dalam dua langkah. Regex pertama menetralkan konteks yang ingin Anda abaikan dengan mengganti string yang berpotensi konflik. Jika Anda hanya ingin mencocokkan, maka Anda dapat menggantinya dengan string kosong, lalu jalankan kecocokan Anda di langkah kedua. Jika Anda ingin mengganti, Anda dapat mengganti string diabaikan dengan sesuatu yang berbeda, misalnya mengelilingi digit Anda dengan rantai lebar tetap @@@. Setelah penggantian ini, Anda bebas mengganti apa yang sebenarnya Anda inginkan, lalu Anda harus mengembalikan @@@string khusus Anda .

2. Pengamatan.

Postingan asli Anda menunjukkan bahwa Anda memahami cara mengecualikan satu ketentuan menggunakan lookarounds. Anda mengatakan bahwa C # bagus untuk ini, dan Anda benar, tetapi itu bukan satu-satunya pilihan. Rasa .NET regex yang ditemukan di C #, VB.NET dan Visual C ++ misalnya, serta regexmodul yang masih eksperimental untuk diganti redengan Python, adalah satu-satunya dua mesin yang saya tahu yang mendukung tampilan lebar tak terbatas. Dengan alat ini, satu syarat dalam satu tampilan di belakang dapat menjaga tidak hanya melihat ke belakang tetapi juga pada pertandingan dan di luar pertandingan, menghindari kebutuhan untuk berkoordinasi dengan seorang lookahead. Lebih banyak kondisi? Lebih banyak pencarian.

Mendaur ulang regex yang Anda miliki untuk s3 di C #, keseluruhan pola akan terlihat seperti ini.

(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

Tapi sekarang Anda tahu saya tidak merekomendasikan ini, bukan?

Penghapusan

@HamZa dan @Jerry menyarankan agar saya menyebutkan trik tambahan untuk kasus-kasus ketika Anda hanya ingin menghapus WhatYouWant. Anda ingat bahwa resep yang cocok WhatYouWant(memasukkannya ke dalam Grup 1) adalah s1|s2|s3|(WhatYouWant), bukan? Untuk menghapus semua instance WhatYouWant, Anda mengubah regex menjadi

(s1|s2|s3)|WhatYouWant

Untuk string pengganti, Anda menggunakan $1. Apa yang terjadi di sini adalah untuk setiap instance s1|s2|s3yang cocok, penggantian $1mengganti instance itu dengan dirinya sendiri (direferensikan oleh $1). Di sisi lain, ketika WhatYouWantcocok, itu diganti dengan grup kosong dan tidak ada yang lain - dan karena itu dihapus. Lihat demo ini , terima kasih @HamZa dan @Jerry karena menyarankan tambahan yang luar biasa ini.

Pengganti

Ini membawa kita ke penggantinya, yang akan saya sentuh sebentar.

  1. Saat mengganti dengan apa-apa, lihat trik "Penghapusan" di atas.
  2. Saat mengganti, jika menggunakan Perl atau PCRE, gunakan (*SKIP)(*F)variasi yang disebutkan di atas agar sesuai dengan yang Anda inginkan, dan lakukan penggantian langsung.
  3. Dalam ragam lain, dalam pemanggilan fungsi pengganti, periksa kecocokan menggunakan callback atau lambda, dan ganti jika Grup 1 disetel. Jika Anda memerlukan bantuan untuk ini, artikel yang sudah direferensikan akan memberi Anda kode dalam berbagai bahasa.

Selamat bersenang-senang!

Tidak, tunggu, masih ada lagi!

Ah, nah, aku akan menyimpannya untuk memoarku dalam dua puluh volume, yang akan dirilis Musim Semi mendatang.


2
@Kobi Balasan dua bagian. Ya, terbawa suasana menulis tadi malam dan menulis di bagian bawah bahwa saya akan tidur dan membereskannya nanti. :) Ya, triknya sederhana tetapi saya tidak sependapat dengan Anda bahwa ini "dasar" karena tampaknya ini bukan bagian dari alat umum yang digunakan orang untuk memecahkan masalah pengecualian. Ketika saya mencari di Google untuk masalah "kecuali" atau "kecuali" atau "tidak di dalam" di SO, hanya satu jawaban (tanpa suara) yang menyarankannya, tidak ada jawaban yang lain. Ngomong-ngomong, saya belum melihat jawaban Anda, yang luar biasa. :)
zx81

2
Maaf, tapi "Trik terbaik" Rex tidak bekerja ( dapat diandalkan ). Katakanlah Anda ingin mencocokkan Tarzan, tetapi tidak saat berada di dalam tanda kutip ganda. The: /no|no|(yes)/trick regex akan menjadi seperti ini: /"[^"]*"|Tarzan/(mengabaikan karakter yang lolos). Ini akan bekerja untuk banyak kasus, tetapi gagal sepenuhnya bila diterapkan pada teks JavaScript berlaku sebagai berikut: var bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";. Trik Rex hanya bekerja ketika SEMUA kemungkinan struktur cocok - dengan kata lain - Anda perlu mengurai teks sepenuhnya untuk menjamin akurasi 100%.
ridgerunner

1
Maaf jika saya terdengar kasar - itu jelas bukan maksud saya. Maksud saya (seperti dalam komentar kedua saya untuk pertanyaan awal di atas) adalah bahwa solusi yang tepat sangat bergantung pada teks target yang dicari. Contoh saya memiliki kode sumber JavaScript sebagai teks target yang memiliki satu kutipan ganda yang diapit dalam satu string kutipan. Ini bisa saja dengan mudah menjadi RegExp literal seperti: var bug1 = /"[^"]*"|(Tarzan)/gi;dan memiliki efek yang sama (dan contoh kedua ini tentunya bukan kasus tepi). Ada lebih banyak contoh yang dapat saya kutip di mana teknik ini gagal bekerja dengan andal.
ridgerunner

1
@ridgerunner Saya selalu senang mendengar dari Anda, itu terdengar sangat kasar bagi saya. Saat kita tahu bahwa string kita bisa berisi "peringatan palsu", kita semua menyesuaikan pola kita. Misalnya, untuk mencocokkan string yang mungkin berisi tanda kutip lolos yang mungkin membuat pencocokan string menjadi tidak aktif, Anda dapat menggunakan (?<!\\)"(?:\\"|[^"\r\n])*+" You don't pull the big gun kecuali Anda punya alasan. Prinsip solusinya masih berlaku. Jika kita tidak dapat mengekspresikan pola untuk diletakkan di sisi kiri, itu lain cerita, kita membutuhkan solusi yang berbeda. Tetapi solusinya melakukan apa yang diiklankan.
zx81

1
Jawaban ini telah ditambahkan ke FAQ Ekspresi Reguler Stack Overflow oleh pengguna @funkwurm.
aliteralmind

11

Lakukan tiga pencocokan berbeda dan tangani kombinasi ketiga situasi tersebut menggunakan logika bersyarat dalam program. Anda tidak perlu menangani semuanya dalam satu regex raksasa.

EDIT: izinkan saya memperluas sedikit karena pertanyaannya menjadi lebih menarik :-)

Ide umum yang coba Anda tangkap di sini adalah untuk mencocokkan dengan pola regex tertentu, tetapi tidak jika ada pola tertentu (bisa berupa angka apa pun) yang ada dalam string pengujian. Untungnya, Anda dapat memanfaatkan bahasa pemrograman Anda: buat ekspresi reguler tetap sederhana dan cukup gunakan bersyarat majemuk. Praktik terbaiknya adalah menangkap ide ini dalam komponen yang dapat digunakan kembali, jadi mari buat kelas dan metode yang mengimplementasikannya:

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

public class MatcherWithExceptions {
  private string m_searchStr;
  private Regex m_searchRegex;
  private IEnumerable<Regex> m_exceptionRegexes;

  public string SearchString {
    get { return m_searchStr; }
    set {
      m_searchStr = value;
      m_searchRegex = new Regex(value);
    }
  }

  public string[] ExceptionStrings {
    set { m_exceptionRegexes = from es in value select new Regex(es); }
  }

  public bool IsMatch(string testStr) {
    return (
      m_searchRegex.IsMatch(testStr)
      && !m_exceptionRegexes.Any(er => er.IsMatch(testStr))
    );
  }
}

public class App {
  public static void Main() {
    var mwe = new MatcherWithExceptions();

    // Set up the matcher object.
    mwe.SearchString = @"\b\d{5}\b";
    mwe.ExceptionStrings = new string[] {
      @"\.$"
    , @"\(.*" + mwe.SearchString + @".*\)"
    , @"if\(.*" + mwe.SearchString + @".*//endif"
    };

    var testStrs = new string[] {
      "1." // False
    , "11111." // False
    , "(11111)" // False
    , "if(11111//endif" // False
    , "if(11111" // True
    , "11111" // True
    };

    // Perform the tests.
    foreach (var ts in testStrs) {
      System.Console.WriteLine(mwe.IsMatch(ts));
    }
  }
}

Jadi di atas, kami menyiapkan string pencarian (lima digit), beberapa string pengecualian ( s1 , s2 , dan s3 Anda ), lalu mencoba mencocokkan dengan beberapa string pengujian. Hasil yang dicetak harus seperti yang ditunjukkan pada komentar di samping setiap string pengujian.


2
Maksud Anda mungkin seperti pencocokan tiga ekspresi reguler berturut-turut? Regex 1 menghilangkan situasi 1 (mungkin hanya menghapus angka buruk), r2 menghapus s2, r3 menghapus s3 dan mencocokkan angka yang tersisa? Itu ide yang menarik.
Hans Schindler

Ha, tentu, itulah mengapa saya memuji Anda. :) Jangan salah paham, saya masih berpikir bahwa dalam kasus khusus ini jawaban saya lebih efisien dan dapat dipertahankan. Pernahkah Anda melihat versi spasi bebas yang saya tambahkan kemarin? Itu one-pass dan sangat mudah dibaca dan dirawat. Tetapi saya menyukai pekerjaan Anda dan jawaban Anda yang diperluas. Maaf saya tidak bisa memberi suara positif lagi, kalau tidak saya akan melakukannya. :)
zx81

2

Persyaratan Anda bahwa tidak ada dalam parens tidak mungkin untuk memuaskan untuk semua kasus. Yaitu, jika Anda entah bagaimana dapat menemukan a (ke kiri dan )ke kanan, itu tidak selalu berarti Anda berada di dalam parens. Misalnya.

(....) + 55555 + (.....)- belum di dalam parens namun ada (dan )ke kiri dan kanan

Sekarang Anda mungkin berpikir diri Anda pintar dan mencari (ke kiri hanya jika Anda tidak bertemu )sebelumnya dan sebaliknya ke kanan. Ini tidak akan berfungsi untuk kasus ini:

((.....) + 55555 + (.....))- Di dalam parens meski ada penutup )dan (ke kiri dan ke kanan.

Tidak mungkin untuk mengetahui apakah Anda berada di dalam kurung menggunakan regex, karena regex tidak dapat menghitung berapa banyak kurung yang telah dibuka dan berapa banyak yang ditutup.

Pertimbangkan tugas yang lebih mudah ini: menggunakan regex, cari tahu apakah semua (mungkin bersarang) tanda kurung dalam string ditutup, yaitu untuk setiap (yang perlu Anda temukan ). Anda akan menemukan bahwa itu tidak mungkin untuk diselesaikan dan jika Anda tidak dapat menyelesaikannya dengan regex maka Anda tidak dapat mengetahui apakah sebuah kata berada di dalam tanda kurung untuk semua kasus, karena Anda tidak dapat mengetahui posisi dalam string jika semua yang sebelumnya (memiliki yang sesuai ).


2
Tidak ada yang mengatakan apa pun tentang tanda kurung bersarang, dan kasus Anda # 1 ditangani dengan baik oleh jawaban zx81.
Dan Bechard

Terima kasih atas pemikiran yang bagus :) tetapi tanda kurung bersarang tidak membuat saya khawatir untuk pertanyaan ini, ini lebih tentang gagasan tentang situasi yang buruk s1 s2 s3
Hans Schindler

Tentu saja bukan tidak mungkin! Inilah tepatnya mengapa Anda perlu melacak level tanda kurung yang saat ini Anda parsing.
MrWonderful

Nah, jika Anda mengurai beberapa jenis CFG seperti yang tampaknya dilakukan OP, lebih baik Anda dilayani dengan menghasilkan LALR atau pengurai serupa yang tidak bermasalah dengan ini.
RokL

2

Hans, jika Anda tidak keberatan saya menggunakan mesin cuci tetangga Anda yang disebut perl :)

Diedit: Di bawah kode pseudo:

  loop through input
  if line contains 'if(' set skip=true
        if skip= true do nothing
        else
           if line match '\b\d{5}\b' set s0=true
           if line does not match s1 condition  set s1=true
           if line does not match s2 condition  set s2=true
           if s0,s1,s2 are true print line 
  if line contains '//endif' set skip=false

Diberikan file input.txt:

tiago@dell:~$ cat input.txt 
this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

Dan skrip validator.pl:

tiago@dell:~$ cat validator.pl 
#! /usr/bin/perl
use warnings;
use strict;
use Data::Dumper;

sub validate_s0 {
    my $line = $_[0];
    if ( $line =~ \d{5/ ){
        return "true";
    }
    return "false";
}

sub validate_s1 {
    my $line = $_[0];
    if ( $line =~ /\.$/ ){
        return "false";
    }
    return "true";
}

sub validate_s2 {
    my $line = $_[0];
    if ( $line =~ /.*?\(.*\d{5.*?\).*/ ){
        return "false";
    }
    return "true";
}

my $skip = "false";
while (<>){
    my $line = $_; 

    if( $line =~ /if\(/ ){
       $skip = "true";  
    }

    if ( $skip eq "false" ) {
        my $s0_status = validate_s0 "$line"; 
        my $s1_status = validate_s1 "$line";
        my $s2_status = validate_s2 "$line";

        if ( $s0_status eq "true"){
            if ( $s1_status eq "true"){
                if ( $s2_status eq "true"){
                    print "$line";
                }
            }
        }
    } 

    if ( $line =~ /\/\/endif/) {
        $skip="false";
    }
}

Eksekusi:

tiago @ dell: ~ $ cat input.txt | validator perl.pl
itu harus cocok dengan 12345
itu harus cocok dengan 12345
itu harus cocok dengan 12345

2

Tidak yakin apakah ini akan membantu Anda atau tidak, tetapi saya memberikan solusi dengan mempertimbangkan asumsi berikut -

  1. Anda membutuhkan solusi elegan untuk memeriksa semua kondisi
  2. Kondisi bisa berubah di masa depan dan kapan saja.
  3. Satu syarat tidak harus bergantung pada yang lain.

Namun saya juga mempertimbangkan yang berikut -

  1. File yang diberikan memiliki kesalahan minimal. Jika berhasil maka kode saya mungkin memerlukan beberapa modifikasi untuk mengatasinya.
  2. Saya menggunakan Stack untuk melacak if(blok.

Ok ini solusinya -

Saya menggunakan C # dan dengan itu MEF (Microsoft Extensibility Framework) untuk mengimplementasikan parser yang dapat dikonfigurasi. Idenya adalah, gunakan satu parser untuk mengurai dan daftar kelas validator yang dapat dikonfigurasi untuk memvalidasi baris dan mengembalikan benar atau salah berdasarkan validasi. Kemudian Anda dapat menambah atau menghapus validator kapan saja atau menambahkan yang baru jika Anda mau. Sejauh ini saya sudah menerapkan untuk S1, S2 dan S3 yang Anda sebutkan, periksa kelas di poin 3. Anda harus menambahkan kelas untuk s4, s5 jika Anda membutuhkannya di masa mendatang.

  1. Pertama, Buat Antarmuka -

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.Contracts
    {
        public interface IParser
        {
            String[] GetMatchedLines(String filename);
        }
    
        public interface IPatternMatcher
        {
            Boolean IsMatched(String line, Stack<string> stack);
        }
    }
  2. Kemudian datanglah pembaca file dan pemeriksa -

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using FileParserDemo.Contracts;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Collections;
    
    namespace FileParserDemo.Parsers
    {
        public class Parser : IParser
        {
            [ImportMany]
            IEnumerable<Lazy<IPatternMatcher>> parsers;
            private CompositionContainer _container;
    
            public void ComposeParts()
            {
                var catalog = new AggregateCatalog();
                catalog.Catalogs.Add(new AssemblyCatalog(typeof(IParser).Assembly));
                _container = new CompositionContainer(catalog);
                try
                {
                    this._container.ComposeParts(this);
                }
                catch
                {
    
                }
            }
    
            public String[] GetMatchedLines(String filename)
            {
                var matched = new List<String>();
                var stack = new Stack<string>();
                using (StreamReader sr = File.OpenText(filename))
                {
                    String line = "";
                    while (!sr.EndOfStream)
                    {
                        line = sr.ReadLine();
                        var m = true;
                        foreach(var matcher in this.parsers){
                            m = m && matcher.Value.IsMatched(line, stack);
                        }
                        if (m)
                        {
                            matched.Add(line);
                        }
                     }
                }
                return matched.ToArray();
            }
        }
    }
  3. Kemudian datang penerapan checker individu, nama kelas cukup jelas, jadi saya rasa mereka tidak membutuhkan lebih banyak deskripsi.

    using FileParserDemo.Contracts;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.PatternMatchers
    {
        [Export(typeof(IPatternMatcher))]
        public class MatchAllNumbers : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\d+");
                return regex.IsMatch(line);
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveIfBlock : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("if\\(");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        //push the if
                        stack.Push(m.ToString());
                    }
                    //ignore current line, and will validate on next line with stack
                    return true;
                }
                regex = new Regex("//endif");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        stack.Pop();
                    }
                }
                return stack.Count == 0; //if stack has an item then ignoring this block
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithEndPeriod : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("(?m)(?!\\d+.*?\\.$)\\d+");
                return regex.IsMatch(line);
            }
        }
    
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithInParenthesis : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\(.*\\d+.*\\)");
                return !regex.IsMatch(line);
            }
        }
    }
  4. Program -

    using FileParserDemo.Contracts;
    using FileParserDemo.Parsers;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var parser = new Parser();
                parser.ComposeParts();
                var matches = parser.GetMatchedLines(Path.GetFullPath("test.txt"));
                foreach (var s in matches)
                {
                    Console.WriteLine(s);
                }
                Console.ReadLine();
            }
        }
    }

Untuk pengujian saya mengambil file contoh @ Tiago Test.txtyang memiliki baris berikut -

this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

Memberikan keluaran -

it should match 12345
it should match 12345
it should match 12345

Tidak tahu apakah ini akan membantu Anda atau tidak, saya bersenang-senang bermain dengannya .... :)

Bagian terbaiknya adalah, untuk menambahkan kondisi baru yang harus Anda lakukan adalah menyediakan implementasi IPatternMatcher, itu akan secara otomatis dipanggil dan dengan demikian akan memvalidasi.


2

Sama seperti @ zx81 (*SKIP)(*F)tetapi dengan menggunakan pernyataan kepala tampilan negatif.

(?m)(?:if\(.*?\/\/endif|\([^()]*\))(*SKIP)(*F)|\b\d+\b(?!.*\.$)

DEMO

Dengan python, saya akan melakukannya dengan mudah seperti ini,

import re
string = """cat 123 sat.
I like 000 not (456) though 111 is fine
222 if(  //endif if(cat==789 stuff  //endif   333"""
for line in string.split('\n'):                                  # Split the input according to the `\n` character and then iterate over the parts.
    if not line.endswith('.'):                                   # Don't consider the part which ends with a dot.
        for i in re.split(r'\([^()]*\)|if\(.*?//endif', line):   # Again split the part by brackets or if condition which endswith `//endif` and then iterate over the inner parts.
            for j in re.findall(r'\b\d+\b', i):                  # Then find all the numbers which are present inside the inner parts and then loop through the fetched numbers.
                print(j)                                         # Prints the number one ny one.

Keluaran:

000
111
222
333
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.