Soru Tek Bir Regex İfadesinde Yinelenen Bir Deseni Daraltma ve Yakalama


Bir dizeden bir dizi jeton yakalamam gereken durumlara çarpıyorum ve sayısız denemeden sonra süreci basitleştirmenin bir yolunu bulamadım.

Öyleyse yazı şöyle diyelim:

başlangıç: Test-Test-lorem-ipsum-sir-doloret-vs-şey: uç

Bu örnekte 8 öğe var, ancak 3 ile 10 arasında bir öğenin olabileceğini söylüyor.

Böyle bir şeyden hoşlanırdım:
start:(?:(\w+)-?){3,10}:end güzel ve temiz ama sadece son maçı yakalar. buraya bakın

Genellikle basit durumlarda böyle bir şey kullanırım:

start:(\w+)-(\w+)-(\w+)-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?:end

3 grup zorunludur ve başka bir 7, en fazla 10 limit nedeniyle isteğe bağlıdır, ancak bu 'hoş' görünmüyor ve maksimum limit 100 ise ve maçlar daha karmaşıksa, yazmak ve izlemek bir acı olur. gösteri

Şimdiye kadar yapabileceğim en iyi şey:

start:(\w+)-((?1))-((?1))-?((?1))?-?((?1))?-?((?1))?-?((?1))?-?((?1))?:end

Özellikle de maçlar karmaşık olsa da daha da uzundur. gösteri

Herkes 1 regex tek çözüm olarak çalışmayı başardı programlama olmadan?

Bu çoğunlukla PCRE'de nasıl yapılabileceğiyle ilgileniyorum ama diğer tatlar da iyi olurdu.

Güncelleştirme:

Amaç, bir eşleşmeyi doğrulamak ve içindeki bireysel simgeleri yakalamaktır. match 0 Sadece RegEx tarafından, herhangi bir OS / Yazılım / Programlama Dili sınırlaması olmadan

2. Güncelleme (ödül):

@ Nhahthh yardım ile aşağıdaki kullanarak RegExp aldım \G:

(?:start:(?=(?:[\w]+(?:-|(?=:end))){3,10}:end)|(?!^)\G-)([\w]+)

gösteri daha kısa, ancak kodu tekrar etmeden tanımlanabilir

ECMA lezzetiyle de ilgileniyorum ve desteklemediği için \G Başka bir yol olup olmadığını merak ediyorum, özellikle kullanmadan /g değiştirici.


31
2018-03-07 10:18


Menşei


Düzenli ifadeler, kalıpları tanımak için gerçekten tasarlanmıştır, ancak bunu değişen bir desen için kullanmaya çalışıyorsunuz. İşletim sisteminizin ne olduğunu söylemiyorsunuz ama bir Awk (Unix / Linux) veya Powershell (Windows) muhtemelen yapmanız gerekenleri yapacaktı. - Robbie Dee
@RobbieDee: herhangi bir yazılım yardımını kullanmadan karmaşık durumlarda RegEx'i kullanmanın akıllı bir yolunu bulmak için güncellenmiş yayın - CSᵠ
@ kaᵠ, hayır, JS'de bu gibi genel şeyleri tek bir maç / adımda yapamazsınız. Bunu yapmanın tek yolu şudur: .NET (grup içeriğini tekrarlayan yakalar) veya destekli normal ifadelerle \G (veya benzer API özellikleri). - Qtax


Cevaplar:


Önce bunu oku!

Bu yazı, soruna "her şey regex" yaklaşımını desteklemekten ziyade olasılığı göstermektir. Yazar, mevcut çözümlere ulaşmadan önce, her biri tespit etmek zor olan ince bir hataya sahip 3-4 varyasyon yazmıştır.

Spesifik örneğiniz için, eşleştiriciler arasındaki eşleşmeyi eşleştirmek ve bölmek gibi daha iyi olan daha iyi bir çözüm var.

Bu gönderi özel örneğinizle ilgilenir. Tam bir genellemenin mümkün olabileceğinden şüphe duyuyorum, ama arkasındaki fikir benzer durumlar için tekrar kullanılabilir.

özet

  • .NET, yinelenen kalıbı yakalamayı destekler CaptureCollection sınıf.
  • Destekleyen diller için \G ve geriye dönük olarak, global eşleme işleviyle çalışan bir regex oluşturabiliriz. Kolayca buggy regex yazmak için tamamen doğru ve kolay yazmak için kolay değil.
  • Olmayan diller için \G ve geriye dönük destek: taklit etmek mümkündür \G ile ^giriş dizesini tek bir maçtan sonra keserek. (Bu cevapta ele alınmamıştır).

Çözüm

Bu çözüm regex motoru desteklerini varsayar \G maç sınırı, ileriye bakma (?=pattern)ve arkasına bak (?<=pattern). Java, Perl, PCRE, .NET, Ruby regex tatları yukarıdaki tüm bu gelişmiş özellikleri desteklemektedir.

Ancak, .NET'te normal ifadenizi kullanabilirsiniz. .NET, tüm örneklerini yakalamayı desteklediğinden, üzerinden tekrarlanan bir yakalama grubuyla eşleşir. CaptureCollection sınıf.

Sizin durumunuz için, kullanımı ile, bir regex yapılabilir \G sınırı eşleştir ve tekrar sayısını sınırlamak için ileriye bak:

(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)

DEMO. İnşaat \w+- tekrarladı, sonra \w+:end.

(?:start:(?=\w+(?:-\w+){2,9}:end)|(?!^)\G-)(\w+)

DEMO. İnşaat \w+ İlk madde için -\w+ tekrarladı. (Öneri için ka Thanks'ya teşekkürler). Bu yapı, doğruluğu konusunda daha basittir, çünkü daha az dönüşüm vardır.

\G Eşleştirme sınırı, özellikle, motorun ilerlemesini ve geçersiz olması gereken eşleşmeleri uydurmadığından emin olmanız gereken yerlerde, belirtecin yapılması gerektiğinde kullanışlıdır.

açıklama

Regex'i yıkalım:

(?:
  start:(?=\w+(?:-\w+){2,9}:end)
    |
  (?<=-)\G
)
(\w+)
(?:-|:end)

Anlaşılması en kolay kısım (\w+) Sondan önceki satırda, yakalamak istediğiniz sözcük budur.

Son satırın da tanınması oldukça kolaydır: eşleştirilecek sözcük takip edilebilir. - veya :end.

Regexine izin veriyorum serbestçe dizenin herhangi bir yerinde eşleşmeye başlar. Diğer bir deyişle, start:...:end dizede herhangi bir yerde ve herhangi bir sayıda görünebilir; normal ifade tüm sözcüklerle eşleşir. Eşleşen belirteçlerin gerçekte geldiği yerlere döndürülmüş diziyi işlemeniz yeterlidir.

Açıklama gelince, regex başlangıcı dize varlığını kontrol eder. start:ve aşağıdaki göz atma, sözcüklerin sayısının belirtilen sınırlar içinde olduğunu kontrol eder ve :end. Ya o ya da Önceki maçtan önceki karakterin bir kontrol olduğunu -ve önceki maçtan devam et.

Diğer inşaat için:

(?:
  start:(?=\w+(?:-\w+){2,9}:end)
    |
  (?!^)\G-
)
(\w+)

Eşleşmemiz dışında her şey neredeyse aynı. start:\w+ formun tekrarını eşleştirmeden önce -\w+. Eşleştiğimiz ilk inşaatın aksine start:\w+- ilk ve tekrarlanan örnekleri \w+- (veya \w+:end son tekrar için).

Bu regexin dizenin ortasına eşleştirmek için çalışmasını yapmak oldukça zordur:

  • Araya giren kelimelerin sayısını kontrol etmeliyiz. start: ve :end (orijinal regex gereksiniminin bir parçası olarak).

  • \G dizenin başlangıcıyla da eşleşir! (?!^) Bu davranışı önlemek için gereklidir. Bununla ilgilenmeksizin, herhangi bir yokken normal ifade eşleşebilir. start:.

    İlk yapım için, arkadan görünüş (?<=-) zaten bu davayı önleyelim ((?!^) tarafından ima edilir (?<=-)).

  • İlk inşaat için (?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)sonra komik bir şeyle uyuşmadığından emin olmalıyız. :end. Geriye bakmak bu amaç için: sonra herhangi bir çöpü önler :end eşleşiyor.

    İkinci yapı bu sorunla karşılaşmaz, çünkü : (arasında :end) Tüm belirteçleri birbirimize uygun hale getirdikten sonra.

Doğrulama Sürümü

Eğer giriş dizgisinin formatı takip ettiğini (ön ve arkada ekstra bir şey olmadığını) doğrulamak istiyorsanız, ve verileri ayıkladığınızda, çapaları şöyle ekleyebilirsiniz:

(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G-)(\w+)
(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G)(\w+)(?:-|:end)

(Geriye bakmak da gerekli değil ama yine de ihtiyacımız var. (?!^) önlemek \G dize başlangıcını eşleştirmekten).

İnşaat

Bir tekrarın tüm örneklerini yakalamak istediğiniz tüm sorunlar için, normal ifadeyi değiştirmek için genel bir yol olduğunu düşünmüyorum. Dönüştürülmesi gereken bir "zor" (veya imkansız?) Durumunun bir örneği, bir tekrarlamanın, belirli bir koşulu yerine getirmek üzere bir veya daha fazla döngüye geri dönmesi gerektiğidir.

Orijinal regex tüm giriş dizesini (doğrulama tipi) açıkladığında, dizgenin ortasından eşleşmeye çalışan bir regex ile karşılaştırmak genellikle daha kolaydır (eşleşen tip). Ancak, her zaman orijinal ifadeyle bir eşleşme yapabilir ve eşleme türü sorununu doğrulama türü sorununa geri dönüştürebiliriz.

Bu adımları uygulayarak böyle bir regex oluşturuyoruz:

  • Tekrarlamadan önce parçayı kapatan bir normal ifade yazın (ör. start:). Bunu arayalım önek düzenli ifade.
  • İlk örneği eşleştirin ve yakalayın. (Örneğin. (\w+))
    (Bu noktada, ilk örnek ve sınırlayıcı eşleştirilmelidir)
  • Ekle \G bir dönüşüm olarak. Genellikle dizgenin başlangıcını eşleştirmesini de engellemelidir.
  • Sınırlayıcıyı ekleyin (varsa). (Örneğin. -)
    (Bu adımdan sonra, tokenlerin geri kalanı da son belki hariç eşleştirilmelidir)
  • Tekrardan sonra parçayı kaplayan parçayı ekleyin (gerekirse) (örn. :end). Tekrardan sonra parçayı arayalım regex son eki (Yapıma eklememiz önemli değil).
  • Şimdi zor kısmı. Bunu kontrol etmeniz gerekiyor:
    • Bir maç başlatmak için başka bir yol yoktur. önek düzenli ifade. Not al \G dalı.
    • Herhangi bir eşleşmeye başlama şansı yok sonra  regex son eki eşleştirildi. Nasıl olduğuna dikkat et \G şube bir maç başlatır.
    • İlk yapım için, sonek ifadesini karıştırırsanız (ör. :end) sınırlayıcı ile (ör. -) bir değişimde, sonek olarak ifade sonekini izin vermeyeceğinizden emin olun.

34
2018-03-14 19:54



@ kaᵠ: Bir yolun olduğu zaman, yolun tek yoldur makul tekrar sayısının üst sınırı (örneğinizde olduğu gibi). Aksi takdirde, doğru öğeyi seçmek için fazladan işleme ihtiyacınız vardır. Sonuç üzerinde manipülasyon yapmanın mümkün olmadığını anlardım, ancak mümkün olduğunda bunu yapmalısınız. - nhahtdh
@ kaᵠ: Bir başka nokta şu ki, hep bütünüyle eşleşebilirsin start:...:end önce, ardından her bir eşleştirici sınırlayıcı boyunca bölün. Örneğinizde, ayraç yeterince çirkin regex çözümüne bile gerek duymayacak kadar açıktır. - nhahtdh
Aslında biraz düşündükten sonra yolun gerçekten uygun bir çözüm olduğunu keşfettim! Bazı durumlarda tüm grupları bir kerede yakalamaktan daha iyi olabilir! btw: en az 3 gruba izin vermek için normal ifadenizi onardı burayı kontrol et. lütfen cevabınızı güncelleyin, böylece kabul edebilirim - CSᵠ
@ kaᵠ: 3-10 hakkında bölüm için teşekkürler (çok dikkatsiz). Ama seninki de sorun regex101.com/r/zE5hU5. Düzenlemek için muhtemelen 10-15 dakikaya ihtiyacım var. - nhahtdh
@Enissay: Lütfen önceki yorumlarımı çizin. İleriye bakmak genellikle gerekli hepsinden sonra. Aksi halde, her şeyin düzgün bir şekilde tekrarlandığını ve bir son eki olduğunu kontrol etmeden, bir sonek eksik olsa bile normal ifadeler içeriğiyle eşleşebilir. - nhahtdh


Tek bir ifade yazmak teorik olarak mümkün olsa da, önce dış sınırlara uymak ve daha sonra iç kısımda bir bölünme yapmak çok daha pratiktir.

ECMAScript’te bunu şöyle yazdım:

'start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end'
    .match(/^start:([\w-]+):end$/)[1] // match the inner part
    .split('-') // split inner part (this could be a split regex as well)

PHP'de:

$txt = 'start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end';
if (preg_match('/^start:([\w-]+):end$/', $txt, $matches)) {
    print_r(explode('-', $matches[1]));
}

6
2018-04-10 09:39



+1 İki aşamalı çözüm genellikle çok daha okunabilir ve sürdürülebilir. - nhahtdh


Tabi ki bu alıntı dizesinde regex'i kullanabilirsiniz.

"(?<a>\\w+)-(?<b>\\w+)-(?:(?<c>\\w+)" \
"(?:-(?<d>\\w+)(?:-(?<e>\\w+)(?:-(?<f>\\w+)" \
"(?:-(?<g>\\w+)(?:-(?<h>\\w+)(?:-(?<i>\\w+)" \
"(?:-(?<j>\\w+))?" \
")?)?)?" \
")?)?)?" \
")"

Bu iyi bir fikir mi? Hayır, öyle düşünmüyorum.


1
2018-03-14 23:06





Bu şekilde yapabileceğinizden emin değilsiniz, ancak tüm dünyadaki kelimeleri kullanarak, sütunlar arasındaki tüm kelimeleri bulabilirsiniz.

http://regex101.com/r/gK0lX1

Yine de grup sayısını doğrulamak zorunda kalacaksın. Küresel bayrak olmadan sadece tek bir eşleşme elde edersiniz, tüm eşleşmeleri değil - değişimi {3,10} için {1,5} ve bunun yerine sonucu 'efendim' elde edersiniz.

import re

s = "start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end"
print re.findall(r"(\b\w+?\b)(?:-|:end)", s)

üretir

['test', 'test', 'lorem', 'ipsum', 'sir', 'doloret', 'etc', 'etc', 'something']


0
2018-03-07 17:35



maalesef bu geçersiz dize eşleşmeleri bulabilir: invalid:test-lorem-ipsum-sir-doloret:end Benzer dizeleri 3 öğeden daha az ve 10'un üzerinde öğeyle doğrulamanın yanı sıra, eşleştirme işleminin programlanması gerekir. - CSᵠ
Bir seferde çok fazla şey yapmaya çalışıyorsunuz, o zaman bence normal ifadelerin dışında kalıyor. - spiralx
RegExes çok güçlü! Belirli durumlarda doğru şekilde oluşturulmuşsa mükemmel sonuçlar elde edebilirsiniz. Kabul edilen yanıtı gör ^ - CSᵠ


Birleştirdiğinizde:

  1. Gözleminiz: Tek bir yakalama grubunun herhangi bir tekrarı, son çekimin üzerine yazılarak sonuçlanacak ve böylece yalnızca yakalama grubunun son yakalanmasına dönecektir.
  2. Bilgi: Bütünün yerine parçalara dayalı her türlü yakalama, regex motorunun tekrar edeceği zaman miktarını sınırlamak imkansız hale getirir. Sınır meta veri (regex değil) olmalıdır.
  3. Cevabın, programlama (döngü) ile ilgili bir gereksinim olmaması veya sorularınızda yaptığınız gibi basitçe kopyala-yapıştırıcı capturgo gruplarını içeren bir cevap olması gerekir.

Yapılamayacağı sonucuna varılabilir.

Güncelleştirme: P için bazı normal ifade motorları vardır. 1 mutlaka doğru değildir. Bu durumda belirttiğiniz normal ifade start:(?:(\w+)-?){3,10}:end işi yapacakkaynak).


0
2018-03-08 21:10



İle yapılabilir \G. - nhahtdh
Aslında hayır olamaz. \ G döngü veya tekrarlama gerektirir (s. 3) ve tekrarlama sınırını (3 ila 10) normal ifadenin kendisinde ayarlamanıza izin vermez (s. 2). - Lodewijk Bogaards
İle yapabilirsin preg_match_allBu oldukça basit ve regex sadece çözüm sağlar. Yakalama sınırı göz önünde bulundurularak mümkün olabilir. \G). (Her durum için işe yaramadığını iddia etmiyorum, ancak bunun işe yaradığı bir dava sınıfı var). - nhahtdh
Yine de bu durumda çalışmayacak. - Lodewijk Bogaards
Eh, mümkün, ancak doğru bir tane yazmak kolay değil. Cevabımı gör. - nhahtdh