Eşleşecek Normal İfade Kalıbı, Hariç tutulacağı zaman… / Şunun dışında


108

--Düzenle-- Mevcut cevapların bazı yararlı fikirleri var ama ben% 100 anlayıp yeniden kullanabileceğim daha eksiksiz bir şey istiyorum; bu yüzden bir ödül koydum. Ayrıca her yerde çalışan fikirler benim için standart sözdiziminden daha iyi\K

Bu soru, bazı durumlar s1 s2 s3 dışında bir kalıbı nasıl eşleştirebileceğim hakkındadır. Anlamımı göstermek için belirli bir örnek veriyorum ancak% 100 anlayabileceğim genel bir cevabı tercih ediyorum, böylece başka durumlarda tekrar kullanabiliyorum.

Misal

Ben \b\d{5}\bs1 s2 s3 kullanarak beş haneyi eşleştirmek istiyorum ama üç durumda değil:

s1: Bu cümle gibi nokta ile biten bir satırda değil.

s2: Parens içinde hiçbir yerde değil.

s3: ile başlayan if(ve biten bir bloğun içinde değil//endif

S1 s2 s3'ten herhangi birini bir ileri-geri ve arkaya bakma ile nasıl çözeceğimi biliyorum, özellikle C # backbehind veya \KPHP'de.

Örneğin

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

s3 ile C # arkaya bak (?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

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

Ama koşulların bir araya gelmesi kafamı patlatıyor. Daha da kötü haber şu ki, başka bir zamanda başka s4 s5 koşulları eklemem gerekebilir.

İyi haber şu ki, dosyaları PHP, C #, Python veya komşumun çamaşır makinesi gibi en yaygın dilleri kullanarak işlemem umurumda değil. :) Python ve Java konusunda oldukça acemiyim ama bir çözümü olup olmadığını öğrenmekle ilgileniyorum.

Bu yüzden buraya birinin esnek bir tarif düşünüp düşünmediğini görmeye geldim.

İpuçları sorun değil: bana tam kod vermenize gerek yok. :)

Teşekkür ederim.


1
\Közel bir php sözdizimi değildir. Lütfen ne söylemek istediğinizi detaylandırın ve netleştirin. Bize "karmaşık" bir çözüme ihtiyacınız olmadığını söylemeyi hedefliyorsanız, sizin için neyin karmaşık olduğunu ve neden olduğunu söylemeniz gerekir.
hakre

@hakre Yani Ruby şimdi onu kullanıyor ve perl'de başladı mı?
Hans Schindler

1
Hayır, çünkü PHP (veya Ruby) olmayan PCRE'dir. Perl farklıdır, ancak PCRE Perl Regex uyumlu olmayı amaçlamaktadır .
hakre

S2 ve s3 gereksinimleriniz çelişkili görünüyor. s2 parantezler hep eşleştirilir ve iç içe olabileceğini ima ama s3 gerektirir: "if("aç parantez değil birlikte, kapatılacak ")", daha çok bir ile: "//endif"? Ve eğer s3 için gerçekten if cümlesinin: ile kapatılması gerektiğini kastettiyseniz "//endif)", o zaman s3 gereksinimi s2'nin bir alt kümesidir.
ridgerunner

@hakre Evet PCRE'yi biliyorum ama açıklamak gerekirse, soru programlama diliyle ilgili ... diyor ki especially in C# lookbehind or \K in PHP... Ama C # arkasına bak sadece C # değil, bu yüzden de şikayet edebilirsin C # .NET değil :) Ve cevap olarak diyorum Ruby Onigurama değil, bu da kötü ... PCRE kullanan başka bir dil var mı? Notepad ++ veya sunucu araçları hakkında konuşmuyorum, bu, özelliğin dilde kullanılmasıyla ilgili bir
Hans Schindler

Yanıtlar:


205

Hans, yemi alacağım ve önceki cevabımı çözeceğim. "Daha eksiksiz bir şey" istediğinizi söylediniz, bu yüzden umarım uzun cevabı aldırmazsınız - sadece memnun etmeye çalışıyorum. Biraz arka planla başlayalım.

Öncelikle, bu mükemmel bir soru. Belirli bağlamlar dışında (örneğin, bir kod bloğu içinde veya parantez içinde) belirli kalıpları eşleştirmeyle ilgili sıklıkla sorular vardır. Bu sorular genellikle oldukça garip çözümlere yol açar. Dolayısıyla, çoklu bağlamlarla ilgili sorunuz özel bir sorundur.

Sürpriz

Şaşırtıcı bir şekilde, genel, uygulaması kolay ve bakımı zevkli olan en az bir etkili çözüm var. O tüm regex lezzetleri ile çalışır kodunuzda yakalama grupları incelemek için izin verir. Ve ilk başta sizinkinden farklı olabilecek bir dizi yaygın soruyu yanıtlıyor: "Donutlar hariç her şeyi eşleştir", "... hariç hepsini değiştir", "annemin kara listesindekiler dışındaki tüm kelimeleri eşleştir", "yok say etiketleri "," italik değilse sıcaklıkla eşleşir "...

Ne yazık ki, teknik iyi bilinmemektedir: Onu kullanabilecek yirmi SO sorusunda sadece birinin ondan bahseden bir cevabı olduğunu tahmin ediyorum - bu da belki elli veya altmış cevaptan biri anlamına geliyor. Yorumlarda Kobi ile değişimimi görün. Teknik, onu (iyimser bir şekilde) "şimdiye kadarki en iyi düzenli ifade numarası" olarak adlandıran bu makalede biraz derinlemesine açıklanmıştır . Bu kadar detaya girmeden, tekniğin nasıl çalıştığını size sağlam bir şekilde anlamaya çalışacağım. Çeşitli dillerde daha fazla ayrıntı ve kod örnekleri için bu kaynağa başvurmanızı öneririm.

Daha İyi Bilinen Bir Varyasyon

Perl ve PHP'ye özgü sözdizimini kullanan ve aynı şeyi yapan bir varyasyon var. Bunu, CasimiretHippolyte ve HamZa gibi regex ustalarının elinde SO'da göreceksiniz . Aşağıda size bununla ilgili daha fazla bilgi vereceğim, ancak buradaki odak noktam, tüm normal ifade çeşitleriyle çalışan genel çözümdür (kodunuzdaki yakalama gruplarını inceleyebildiğiniz sürece).

Tüm arka plan için teşekkürler, zx81 ... Ama tarif nedir?

Anahtar Gerçek

Yöntem, 1. Grup yakalamasındaki eşleşmeyi döndürür. Genel maç hiç umurunda değil.

Aslında, işin püf noktası, istemediğimiz çeşitli bağlamları eşleştirmek (bu bağlamları |OR / dönüşüm kullanarak zincirleme ) böylece "onları etkisiz hale getirmektir". Tüm istenmeyen bağlamlar eşleşen sonra, münavebe son bölümü biz eşleşir mi istiyor ve Grup 1'e yakalar bunu.

Genel tarif

Not_this_context|Not_this_either|StayAway|(WhatYouWant)

Bu eşleşecek Not_this_context, ancak bir anlamda bu eşleşme çöp kutusuna gidiyor, çünkü genel eşleşmelere bakmayacağız: sadece Grup 1 yakalamalarına bakıyoruz.

Sizin durumunuzda, rakamlarınız ve görmezden gelinecek üç bağlamınız ile şunları yapabiliriz:

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

Aslında s1, s2 ve s3 ile eşleştiğimiz için, bunlardan kaçınmaya çalışmak yerine, s1, s2 ve s3 için tek tek ifadelerin gün kadar net kalabileceğini unutmayın. (A'nın her iki yanındaki alt ifadelerdir |)

Tüm ifade şu şekilde yazılabilir:

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

Bu demoyu izleyin (ancak sağ alt bölmedeki yakalama gruplarına odaklanın.)

Bu normal |ifadeyi her bir sınırlayıcıda zihinsel olarak bölmeye çalışırsanız , bu aslında yalnızca dört çok basit ifadeden oluşan bir dizidir.

Serbest aralığı destekleyen tatlar için bu özellikle iyi okur.

(?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)

Bunun okunması ve bakımı son derece kolaydır.

Normal ifadeyi genişletme

Daha fazla s4 ve s5 durumunu göz ardı etmek istediğinizde, bunları sol tarafa daha fazla alternatif olarak eklersiniz:

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

Bu nasıl çalışıyor?

İstemediğiniz bağlamlar soldaki alternatifler listesine eklenir: eşleşirler, ancak bu genel eşleşmeler hiçbir zaman incelenmez, bu nedenle bunları eşleştirmek onları bir "çöp kutusuna" koymanın bir yoludur.

Bununla birlikte, istediğiniz içerik Grup 1'e yakalanır. Ardından, Programlı olarak Grup 1'in boş olmadığını ve Grup 1'in ayarlanmış olduğunu kontrol etmeniz gerekir. Bu önemsiz bir programlama görevidir (ve daha sonra nasıl yapıldığından bahsedeceğiz), özellikle de size bir bakışta anlayabileceğiniz ve gerektiği gibi gözden geçirebileceğiniz veya genişletebileceğiniz basit bir normal ifade bıraktığını düşünürsek.

Her zaman görselleştirme hayranı değilim, ancak bu, yöntemin ne kadar basit olduğunu gösterme konusunda iyi bir iş çıkarıyor. Her "satır" potansiyel bir eşleşmeye karşılık gelir, ancak yalnızca alt satır Grup 1'e alınır.

Düzenli ifade görselleştirme

Debuggex Demosu

Perl / PCRE Varyasyonu

Yukarıdaki genel çözümün aksine, en azından @CasimiretHippolyte ve @HamZa gibi normal ifade Tanrılarının elinde, SO'da sıklıkla görülen Perl ve PCRE için bir varyasyon vardır. Bu:

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

Senin durumunda:

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

Bu varyasyonun kullanımı biraz daha kolaydır çünkü s1, s2 ve s3 bağlamlarında eşleşen içerik basitçe atlanır, bu nedenle Grup 1 yakalamalarını incelemeniz gerekmez (parantezlerin kaybolduğuna dikkat edin). Maçlar sadece şunları içerir:whatYouWant

Not (*F), (*FAIL)ve (?!)hepsi aynı şeydir. Daha belirsiz olmak istersen, kullanabilirsin(*SKIP)(?!)

demo Bu sürüm için

Uygulamalar

İşte bu tekniğin genellikle kolayca çözebileceği bazı yaygın sorunlar. Seçme kelimesinin bu problemlerden bazılarının kulağa farklı gelmesine rağmen aslında aynı olduklarını fark edeceksiniz.

  1. Gibi bir etiketin herhangi bir yeri dışında foo'yu nasıl eşleştirebilirim <a stuff...>...</a>?
  2. Bir <i>etiket veya bir JavaScript pasajı dışında foo'yu nasıl eşleştirebilirim (daha fazla koşul)?
  3. Bu kara listede olmayan tüm kelimeleri nasıl eşleştirebilirim?
  4. Bir SUB ... END SUB bloğu içindeki herhangi bir şeyi nasıl göz ardı edebilirim?
  5. ... s1 s2 s3 dışındaki her şeyi nasıl eşleştirebilirim?

1. Grup Yakalamaları Nasıl Programlanır

Kod istemiyordunuz, ama tamamlanması için ... Grup 1'i inceleyecek kod, açıkça seçtiğiniz dile bağlı olacaktır. Her halükarda, eşleşmeleri incelemek için kullanacağınız koda birkaç satırdan fazlasını eklememelidir.

Şüpheniz varsa, daha önce bahsedilen makalenin birkaç dil için kod sunan kod örnekleri bölümüne bakmanızı tavsiye ederim .

Alternatifler

Sorunun karmaşıklığına ve kullanılan normal ifade motoruna bağlı olarak birkaç alternatif vardır. Birden çok koşul dahil olmak üzere çoğu durum için geçerli olabilecek ikisi aşağıda verilmiştir. Benim görüşüme göre, s1|s2|s3|(whatYouWant)sadece netlik her zaman kazanırsa, her ikisi de tarif kadar çekici değildir .

1. Değiştirin ve ardından Eşleştirin.

Keskin görünen ancak birçok ortamda iyi çalışan iyi bir çözüm, iki adımda çalışmaktır. İlk normal ifade, potansiyel olarak çakışan dizeleri değiştirerek yok saymak istediğiniz bağlamı etkisiz hale getirir. Yalnızca eşleştirmek istiyorsanız, boş bir dizeyle değiştirebilir, ardından ikinci adımda eşleşmenizi çalıştırabilirsiniz. Değiştirmek istiyorsanız, önce yok sayılacak dizeleri farklı bir şeyle değiştirebilirsiniz, örneğin rakamlarınızı sabit genişlikte bir zincir ile çevrelemek @@@. Bu değiştirmeden sonra, gerçekten istediğiniz şeyi değiştirmekte özgürsünüz, ardından ayırt edici @@@dizelerinizi geri almanız gerekecek .

2. Bakışlar.

Orijinal gönderiniz, bakma çözümleri kullanarak tek bir koşulu nasıl hariç tutacağınızı anladığınızı gösterdi. C # 'ın bunun için harika olduğunu söylediniz ve haklısınız, ancak tek seçenek bu değil. Örneğin C #, VB.NET ve Visual C ++ 'da bulunan .NET regex tatları ve Python'da regexdeğiştirilecek hala deneysel modül re, sonsuz genişlikte bakmayı destekleyen bildiğim tek iki motordur. Bu araçlarla, bir bakışta bir koşul, sadece arkaya değil, aynı zamanda maça ve maçın ötesine de bakabilir ve bir bakışla koordine etme ihtiyacını ortadan kaldırabilir. Daha fazla koşul mu? Daha fazla bakış.

C # 'da s3 için sahip olduğunuz regex'i geri dönüştürmek, tüm kalıp şöyle görünecektir.

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

Ama şimdiye kadar bunu önermediğimi biliyorsun, değil mi?

Silmeler

@HamZa ve @Jerry, sadece silmek istediğiniz durumlar için ek bir numaradan bahsetmemi önerdi WhatYouWant. Eşleşecek tarifin WhatYouWant(onu Grup 1'e çekerek) olduğunu hatırlıyorsunuz s1|s2|s3|(WhatYouWant), değil mi? Tüm örneğini silmek WhatYouWantiçin normal ifadeyi şu şekilde değiştirirsiniz:

(s1|s2|s3)|WhatYouWant

Değiştirme dizesi için kullanırsınız $1. Burada olan s1|s2|s3şey, eşleştirilen her bir örnek için , $1değiştirmenin bu örneği kendisiyle değiştirmesidir (tarafından başvurulur $1). Öte yandan, WhatYouWanteşleştirildiğinde, boş bir grupla değiştirilir ve başka hiçbir şeyle değiştirilmez - ve bu nedenle silinir. Bu demoyu izleyin, bu harika eklemeyi önerdiğiniz için @HamZa ve @ Jerry'ye teşekkür ederiz.

Değiştirmeler

Bu bizi kısaca değineceğim değiştirmelere getiriyor.

  1. Hiçbir şeyle değiştirirken yukarıdaki "Silinmeler" numarasına bakın.
  2. Değiştirirken, Perl veya PCRE kullanılıyorsa, (*SKIP)(*F)tam olarak istediğinizi eşleştirmek için yukarıda belirtilen varyasyonu kullanın ve doğrudan bir değiştirme yapın.
  3. Diğer tatlarda, değiştirme işlevi çağrısı içinde, bir geri arama veya lambda kullanarak eşleşmeyi inceleyin ve Grup 1 ayarlanmışsa değiştirin. Bununla ilgili yardıma ihtiyacınız varsa, daha önce atıfta bulunulan makale size çeşitli dillerde kod verecektir.

İyi eğlenceler!

Hayır, bekle, dahası var!

Ah, nah, bunu önümüzdeki ilkbaharda yayınlanacak yirmi ciltlik anılarım için saklayacağım.


2
@Kobi İki parçalı cevap. Evet, dün gece yazmaktan vazgeçtim ve altına, üzerinde uyuyup sonra toparlayacağımı yazdım. :) Evet işin püf noktası basit, ancak "temel" olduğu hakkındaki algınızı paylaşmıyorum çünkü bu, insanların dışlama sorunlarını çözmek için kullandıkları yaygın araçların bir parçası gibi görünmüyor. SO'da "hariç" veya "olmadıkça" veya "içeride değil" sorunları için Google'da arama yaptığımda, yalnızca bir yanıt (oysuz) önerdi, diğerlerinin hiçbiri yapmadı. Bu arada, müthiş cevaplarınızı görmemiştim. :)
zx81

2
Üzgünüm, ama Rex'in "en iyi numarası" ( güvenilir bir şekilde ) işe yaramıyor . Eşleştirmek istediğinizi söyleyin Tarzan, ancak çift tırnak içinde herhangi bir yerde değil. : /no|no|(yes)/Trick regex şöyle bir şey olabilir: /"[^"]*"|Tarzan/(kaçan karakterleri yok sayarak). Bu, pek çok durum için çalışır, ancak aşağıdaki geçerli bir JavaScript metne uygulandığında tamamen başarısız olur: var bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";. Rex'in hilesi yalnızca TÜM olası yapılar eşleştiğinde işe yarar - başka bir deyişle,% 100 doğruluğu garanti etmek için metni tamamen ayrıştırmanız gerekir.
ridgerunner

1
Sesim sert çıkarsa özür dilerim - kesinlikle niyetim bu değildi. Demek istediğim (yukarıdaki orijinal soruya ikinci yorumumda olduğu gibi), doğru bir çözümün, aranan hedef metne büyük ölçüde bağlı olduğudur. Örneğimde, tek tırnaklı bir dize içinde bir çift tırnak bulunan hedef metin olarak JavaScript kaynak kodu var. Birebir RegExp olabilirdi, örneğin: var bug1 = /"[^"]*"|(Tarzan)/gi;ve aynı etkiye sahip olabilirdi (ve bu ikinci örnek kesinlikle bir uç durum değildir). Bu tekniğin güvenilir bir şekilde çalışmadığı yerlerde alıntı yapabileceğim daha birçok örnek var.
ridgerunner

1
@ridgerunner Senden haber almak her zaman hoşuma gidiyor, bana haksız yere sert geliyor. Dizelerimizin "yanlış uyarılar" içerebileceğini bildiğimizde, hepimiz kalıplarımızı düzeltiriz. Örneğin, bir dizi eşleştiriciyi atabilecek kaçmış tırnak işaretleri içerebilecek bir dizeyi eşleştirmek için, (?<!\\)"(?:\\"|[^"\r\n])*+" bir nedeniniz yoksa Büyük silahları çekmeyin seçeneğini kullanabilirsiniz . Çözümün ilkesi hala geçerlidir. Sol tarafa koyacağımız bir kalıp ifade edemiyorsak, bu farklı bir hikaye, farklı bir çözüme ihtiyacımız var. Ancak çözüm, reklamını yaptığı şeyi yapar.
zx81

1
Bu cevap, @funkwurm kullanıcısı tarafından Yığın Taşması Normal İfadeler SSS bölümüne eklendi.
aliteralmind

11

Üç farklı eşleştirme yapın ve program içi koşullu mantığı kullanarak üç durumun birleşimini gerçekleştirin. Her şeyi dev bir normal ifadede halletmenize gerek yok.

DÜZENLEME: Biraz genişletmeme izin verin çünkü soru daha da ilginç hale geldi :-)

Burada yakalamaya çalıştığınız genel fikir, belirli bir normal ifade kalıbıyla eşleşmektir, ancak test dizesinde bulunan belirli başka (herhangi bir sayı olabilir) kalıplar olduğunda değil. Neyse ki, programlama dilinizden yararlanabilirsiniz: normal ifadeleri basit tutun ve sadece bileşik koşullu kullanın. En iyi uygulama, bu fikri yeniden kullanılabilir bir bileşende yakalamak olabilir, öyleyse hadi bir sınıf ve onu uygulayacak bir yöntem oluşturalım:

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));
    }
  }
}

Yukarıda, arama dizesini (beş basamak), birden çok istisna dizesini ( s1 , s2 ve s3 ) oluşturduk ve ardından birkaç test dizesiyle eşleşmeye çalıştık. Yazdırılan sonuçlar, her bir test dizisinin yanındaki yorumlarda gösterildiği gibi olmalıdır.


2
Yani arka arkaya üç normal ifadeyi eşleştirmek gibi mi? Normal ifade 1 durum 1'i ortadan kaldırır (belki sadece bozuk rakamı siler), r2 s2'yi kaldırır, r3 s3'ü kaldırır ve kalan rakamlarla eşleşir mi? Bu ilginç fikir.
Hans Schindler

Tabii, bu yüzden sana oy verdim. :) Beni yanlış anlamayın, yine de bu özel durumda cevabımın daha verimli ve sürdürülebilir olduğunu düşünüyorum. Dün eklediğim boş alan sürümünü gördünüz mü? Bu tek geçiştir ve okunması ve bakımı son derece kolaydır. Ama çalışmanızı ve genişletilmiş cevabınızı beğendim. Üzgünüm tekrar oy veremem, yoksa yapardım. :)
zx81

2

Tüm durumları doyurmak imkansız olarak parantez içinde olmama şartınız. Yani, bir şekilde (solda ve )sağda bir a bulabilirseniz , bu her zaman parantez içinde olduğunuz anlamına gelmez. Örneğin.

(....) + 55555 + (.....)- değil Pars içeride henüz vardır (ve )sola ve sağa

Şimdi kendinizi zeki düşünebilir ve (ancak daha )önce karşılaşmazsanız solu arayabilir ve sağda tam tersi olabilir. Bu, bu durumda işe yaramaz:

((.....) + 55555 + (.....))- kapatma olmasına rağmen parantez içinde )ve (sola ve sağa.

Normal ifade kullanarak parantez içinde olup olmadığınızı öğrenmek imkansızdır çünkü regex, kaç parenin açıldığını ve kaçının kapatıldığını sayamaz.

Şu daha kolay görevi düşünün: normal ifadeyi kullanarak, bir dizedeki tüm (muhtemelen iç içe geçmiş) parenlerin kapatılıp kapatılmadığını, yani (bulmanız gereken her şeyi düşünün ). Çözmenin imkansız olduğunu göreceksiniz ve eğer bunu regex ile çözemezseniz, o zaman bir kelimenin tüm durumlar için parantez içinde olup olmadığını anlayamazsınız, çünkü eğer dize içindeki bir pozisyonda çözemezsiniz. tüm öncekilerin bir (karşılığı vardır ).


2
İç içe geçmiş parantez hakkında kimse bir şey söylemedi ve 1 numaralı durumunuz zx81'in cevabına göre gayet iyi ele alındı.
Dan Bechard

Güzel düşünceleriniz için teşekkür ederim :) ama iç içe geçmiş parantezler beni endişelendirmiyor bu soru için daha çok kötü durumlar fikriyle ilgili s1 s2 s3
Hans Schindler

Tabii ki imkansız değil! Tam da bu nedenle şu anda ayrıştırmakta olduğunuz parantez düzeyini izlemeniz gerekir.
MrWonderful

OP'nin yaptığı gibi bir tür CFG'yi ayrıştırıyorsanız, bununla ilgili problemleri olmayan bir LALR veya benzeri bir ayrıştırıcı oluşturarak daha iyi hizmet alırsınız.
RokL

2

Hans sakıncası yoksa komşunun perl denen çamaşır makinesini kullandım :)

Düzenlendi: Sözde bir kodun altında:

  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

İnput.txt dosyası verildiğinde:

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

Ve validator.pl betiği:

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";
    }
}

Yürütme:

tiago @ dell: ~ $ cat input.txt | perl validator.pl
12345 ile eşleşmelidir
12345 ile eşleşmelidir
12345 ile eşleşmelidir

2

Bunun size yardımcı olup olmayacağından emin değilim, ancak aşağıdaki varsayımları dikkate alarak bir çözüm sunuyorum -

  1. Tüm koşulları kontrol etmek için zarif bir çözüme ihtiyacınız var
  2. Koşullar gelecekte ve her an değişebilir.
  3. Bir koşul diğerlerine bağlı olmamalıdır.

Ancak şunu da düşündüm:

  1. Verilen dosyada minimum hata var. Eğer öyleyse, kodumun bununla başa çıkmak için bazı değişikliklere ihtiyacı olabilir.
  2. if(Blokları takip etmek için Stack kullandım .

Tamam işte çözüm -

Yapılandırılabilir ayrıştırıcıları uygulamak için C # ve onunla birlikte MEF (Microsoft Genişletilebilirlik Çerçevesi) kullandım. Buradaki fikir, ayrıştırmak için tek bir ayrıştırıcı ve satırı doğrulamak ve doğrulamaya göre doğru veya yanlış döndürmek için yapılandırılabilir doğrulayıcı sınıflarının bir listesini kullanmaktır. Ardından istediğiniz zaman herhangi bir doğrulayıcı ekleyebilir veya kaldırabilir veya isterseniz yenilerini ekleyebilirsiniz. Şimdiye kadar bahsettiğiniz S1, S2 ve S3 için zaten uyguladım, 3. noktada sınıfları kontrol edin. Gelecekte ihtiyacınız olursa s4, s5 için sınıflar eklemelisiniz.

  1. Önce Arayüzleri Oluşturun -

    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. Ardından dosya okuyucu ve denetleyici gelir -

    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. Sonra bireysel dama uygulaması gelir, sınıf isimleri kendinden açıklamalıdır, bu yüzden daha fazla tanımlamaya ihtiyaçları olduğunu düşünmüyorum.

    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();
            }
        }
    }

Test için @ Tiago'nun Test.txtaşağıdaki satırlara sahip örnek dosyasını aldım -

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

Çıkışı verir -

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

Bunun size yardımcı olup olmayacağını bilmiyorum, onunla oynarken çok eğlendim .... :)

En iyi yanı, yeni bir koşul eklemek için tek yapmanız gereken bir uygulama sağlamak IPatternMatcher, otomatik olarak çağrılacak ve böylece doğrulanacaktır.


2

@ Zx81'inki ile aynı, (*SKIP)(*F)ancak olumsuz bir önden okuma iddiası kullanıyor.

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

DEMO

Python'da bunu kolayca yapardım,

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.

Çıktı:

000
111
222
333
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.