Cevap, söylemeye gerek yok, EVET! Kesinlikle a n b n ile eşleşecek bir Java normal ifade kalıbı yazabilirsiniz . İddia için pozitif bir ilerleme ve "sayma" için iç içe geçmiş bir referans kullanır.
Bu cevap, kalıbı hemen vermek yerine, okuyuculara onu türetme sürecinde rehberlik edecektir . Çözüm yavaşça inşa edildiğinden çeşitli ipuçları verilmiştir. Bu açıdan, umarım bu yanıt başka bir düzgün düzenli ifade kalıbından çok daha fazlasını içerir. Umarım okuyucular, gelecekte kendi başlarına daha fazla kalıp türetebilmeleri için "normal ifadeyle düşünmeyi" ve çeşitli yapıları uyumlu bir şekilde bir araya getirmeyi öğreneceklerdir.
Çözümü geliştirmek için kullanılan dil, özlü olduğu için PHP olacaktır. Kalıp tamamlandıktan sonra son test Java'da yapılacaktır.
Adım 1: İddia için Önden Bakış
Biz maç için istiyorum: en basit sorunu ile başlayalım a+
bir dize başında, ama hemen arkasından oluyor sadece b+
. Biz kullanabilirsiniz ^
etmek demirlemek bizim maçı ve sadece eşleştirmek istediğiniz beri a+
olmadan b+
kullanabileceğimiz ileriye dönük iddiayı (?=…)
.
İşte basit bir test koşum takımı içeren modelimiz:
function testAll($r, $tests) {
foreach ($tests as $test) {
$isMatch = preg_match($r, $test, $groups);
$groupsJoined = join('|', $groups);
print("$test $isMatch $groupsJoined\n");
}
}
$tests = array('aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb');
$r1 = '/^a+(?=b+)/';
# └────┘
# lookahead
testAll($r1, $tests);
Çıktı ( ideone.com'da görüldüğü gibi ):
aaa 0
aaab 1 aaa
aaaxb 0
xaaab 0
b 0
abbb 1 a
Bu tam olarak istediğimiz çıktıdır: a+
yalnızca dizenin başındaysa ve hemen ardından gelirse eşleşiriz b+
.
Ders : İddialarda bulunmak için bakma yöntemlerinde kalıpları kullanabilirsiniz.
Adım 2: Önden (ve serbest aralık modu) yakalama
Şimdi b+
, maçın bir parçası olmasını istemesek bile , onu yine de 1. grupta yakalamak istiyoruz diyelim. Ayrıca, daha karmaşık bir model olmasını beklediğimiz x
için, serbest boşluk için değiştirici kullanalım, bu yüzden normal ifademizi daha okunaklı hale getirebilir.
Önceki PHP parçacığımızı temel alarak, artık aşağıdaki kalıba sahibiz:
$r2 = '/ ^ a+ (?= (b+) ) /x';
# │ └──┘ │
# │ 1 │
# └────────┘
# lookahead
testAll($r2, $tests);
Çıktı şimdi ( ideone.com'da görüldüğü gibi ):
aaa 0
aaab 1 aaa|b
aaaxb 0
xaaab 0
b 0
abbb 1 a|bbb
Örneğin aaa|b
, join
her grubun yakaladığı şeyin -ing'in sonucu olduğunu unutmayın '|'
. Bu durumda, grup 0 (yani modelin eşleştiği) aaa
ve grup 1 yakalanır b
.
Ders : Bir bakışta çekim yapabilirsiniz. Okunabilirliği artırmak için boş aralık kullanabilirsiniz.
3. Adım: Önden bakışı "döngü" içinde yeniden düzenleme
Sayma mekanizmamızı tanıtmadan önce, modelimizde bir değişiklik yapmamız gerekiyor. Şu anda, önden okuma +
"döngü" tekrarının dışındadır . Bu var ki şu ana kadar sadece assert istedim çünkü gayet b+
bizim şu a+
, ama ne biz gerçekten en sonunda yapmak istediğiniz her biri için assert olur a
, karşılık gelen var biz "döngü" içine eşleşmesini b
onunla gitmek için.
Şimdilik sayma mekanizması hakkında endişelenmeyelim ve sadece aşağıdaki gibi yeniden düzenleme yapalım:
- İlk yeniden
a+
düzenleme (?: a )+
(bunun (?:…)
yakalamayan bir grup olduğuna dikkat edin )
- Ardından bakış açısını bu yakalamayan grubun içine taşıyın
- Şimdi "
a*
görmeden " önce "atlamamız" gerektiğine dikkat edin b+
, bu nedenle kalıbı buna göre değiştirin
Şimdi şunlara sahibiz:
$r3 = '/ ^ (?: a (?= a* (b+) ) )+ /x';
# │ │ └──┘ │ │
# │ │ 1 │ │
# │ └───────────┘ │
# │ lookahead │
# └───────────────────┘
# non-capturing group
Çıktı öncekiyle aynıdır ( ideone.com'da görüldüğü gibi ), bu nedenle bu konuda bir değişiklik yok. Önemli olan şimdi de iddiasını hale getirmesidir her yineleme arasında +
"döngü". Mevcut modelimizle, bu gerekli değildir, ancak daha sonra kendi kendine referans kullanarak grup 1'i bizim için "sayarız".
Ders : Yakalamayan bir grubun içinde çekim yapabilirsiniz. Lookarounds tekrar edilebilir.
4. Adım: Bu, saymaya başladığımız adımdır
İşte yapacağımız şey: 1. grubu şu şekilde yeniden yazacağız:
- İlk yinelemenin sonunda
+
, ilk a
eşleştiğinde,b
- İkinci yinelemenin sonunda, bir başkası
a
eşleştiğinde,bb
- Üçüncü yinelemenin sonunda,
bbb
- ...
- Sonunda , n -inci yineleme, grup 1 çekmeligüvenli b n
b
1. gruba girmek için yeterli değilse , o zaman iddia başarısız olur
Dolayısıyla, şimdi olan 1. grup, (b+)
benzer bir şeye yeniden yazılması gerekecek (\1 b)
. Yani, b
önceki yinelemede yakalanan 1. gruba a "eklemeye" çalışıyoruz .
Burada, bu modelin "temel durumu", yani öz referans olmadan eşleşebileceği durumu, eksik olması nedeniyle küçük bir sorun var. Grup 1 "başlatılmamış" olarak başladığı için temel durum gereklidir; Henüz hiçbir şey yakalamadı (boş bir dizge bile), bu nedenle bir kendine referans girişimi her zaman başarısız olacaktır.
Bununla başa çıkmanın birçok yolu var, ancak şimdilik kendi kendine referans eşleştirmeyi isteğe bağlı yapalım , yani \1?
. Bu mükemmel çalışabilir veya çalışmayabilir, ancak bunun ne işe yaradığını görelim ve herhangi bir sorun varsa, o zaman ona geldiğimizde o köprüyü geçeceğiz. Ayrıca, biz oradayken daha fazla test senaryosu ekleyeceğiz.
$tests = array(
'aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb', 'aabb', 'aaabbbbb', 'aaaaabbb'
);
$r4 = '/ ^ (?: a (?= a* (\1? b) ) )+ /x';
# │ │ └─────┘ | │
# │ │ 1 | │
# │ └──────────────┘ │
# │ lookahead │
# └──────────────────────┘
# non-capturing group
Çıktı şimdi ( ideone.com'da görüldüğü gibi ):
aaa 0
aaab 1 aaa|b # (*gasp!*)
aaaxb 0
xaaab 0
b 0
abbb 1 a|b # yes!
aabb 1 aa|bb # YES!!
aaabbbbb 1 aaa|bbb # YESS!!!
aaaaabbb 1 aaaaa|bb # NOOOOOoooooo....
Aha! Görünüşe göre şu anda çözüme gerçekten çok yaklaştık! Kendi kendine referans kullanarak grup 1'i "saymayı" başardık! Ama bekleyin ... İkinci ve son test durumlarında bir sorun var !! Yeterince b
s yok ve bir şekilde yanlış sayıldı! Bir sonraki adımda bunun neden olduğunu inceleyeceğiz.
Ders : Kendine referans veren bir grubu "başlatmanın" bir yolu, kendi kendine referans eşleşmesini isteğe bağlı hale getirmektir.
Adım 4½: Neyin yanlış gittiğini anlamak
Sorun şu ki, öz referans eşleştirmeyi isteğe bağlı yaptığımız için, "sayaç", yeterli sayıda olmadığında "sıfırlanabilir" b
. aaaaabbb
Girdi olarak modelimizin her yinelemesinde neler olduğunu yakından inceleyelim .
a a a a a b b b
↑
# Initial state: Group 1 is "uninitialized".
_
a a a a a b b b
↑
# 1st iteration: Group 1 couldn't match \1 since it was "uninitialized",
# so it matched and captured just b
___
a a a a a b b b
↑
# 2nd iteration: Group 1 matched \1b and captured bb
_____
a a a a a b b b
↑
# 3rd iteration: Group 1 matched \1b and captured bbb
_
a a a a a b b b
↑
# 4th iteration: Group 1 could still match \1, but not \1b,
# (!!!) so it matched and captured just b
___
a a a a a b b b
↑
# 5th iteration: Group 1 matched \1b and captured bb
#
# No more a, + "loop" terminates
Aha! 4. yinelememizde hala eşleşebilirdik \1
, ancak eşleşemedik \1b
! Öz referans eşleştirmenin isteğe bağlı olmasına izin verdiğimizden \1?
, motor geri adım atar ve "hayır, teşekkürler" seçeneğini aldı, bu da bizim eşleştirme ve tam olarak yakalamamıza olanak tanır b
!
Bununla birlikte, ilk yineleme dışında, her zaman yalnızca öz referansla eşleşebileceğinizi unutmayın \1
. Elbette bu açıktır, çünkü önceki yinelememizde yakaladığımız şeydir ve kurulumumuzda onu her zaman yeniden eşleştirebiliriz (örneğin bbb
, en son yakaladıysak , hala olacağına dair garantimiz var bbb
, ancak olabilir veya bbbb
bu sefer olmayabilir ).
Ders : Geri izleme konusunda dikkatli olun. Düzenli ifade motoru, verilen kalıp eşleşene kadar izin verdiğiniz kadar geri izleme yapacaktır. Bu, performansı (yani yıkıcı geri izleme ) ve / veya doğruluğuetkileyebilir.
Adım 5: Kurtarma için kendi kendine sahip olma!
"Düzeltme" şimdi açık olmalıdır: isteğe bağlı tekrarlamayı iyelik nicelik belirteci ile birleştirin . Olduğunu, yerine basitçe ?
, kullanım ?+
yerine (örneğin, "işbirliği" tüm modelinin bir maç sonuçlanabilir bile, iyelik olarak ölçülür tekrarı sarfınazar değil unutmayın).
Gayri resmi terimlerle, bu ?+
, ?
ve ??
şöyle diyor:
?+
- (isteğe bağlı) "Orada olması gerekmez"
- (iyelik) "ama eğer varsa, onu almalı ve bırakmamalısın!"
?
- (isteğe bağlı) "Orada olması gerekmez"
- (açgözlü) "ama eğer öyleyse, şimdilik alabilirsin,"
- (geri izleme) "ancak daha sonra bırakmanız istenebilir!"
??
- (isteğe bağlı) "Orada olması gerekmez"
- (isteksiz) "ve öyle olsa bile, henüz almak zorunda değilsin,"
- (geri izleme) "ancak daha sonra almanız istenebilir!"
Bizim kurulumunda, \1
orada ilk defa olmayacak, ancak olacak hep orada bundan sonra herhangi bir zaman olabilir ve biz her zaman sonra eşleştirmek istiyoruz. Böylece \1?+
tam olarak istediğimizi başarırdık.
$r5 = '/ ^ (?: a (?= a* (\1?+ b) ) )+ /x';
# │ │ └──────┘ │ │
# │ │ 1 │ │
# │ └───────────────┘ │
# │ lookahead │
# └───────────────────────┘
# non-capturing group
Şimdi çıktı ( ideone.com'da görüldüğü gibi ):
aaa 0
aaab 1 a|b # Yay! Fixed!
aaaxb 0
xaaab 0
b 0
abbb 1 a|b
aabb 1 aa|bb
aaabbbbb 1 aaa|bbb
aaaaabbb 1 aaa|bbb # Hurrahh!!!
Voilà !!! Sorun çözüldü!!! Şimdi doğru sayıyoruz, tam da istediğimiz gibi!
Ders : Açgözlü, isteksiz ve iyelik tekrarları arasındaki farkı öğrenin. İsteğe bağlı sahiplik, güçlü bir kombinasyon olabilir.
6. Adım: Rötuşları tamamlama
Yani şu anda sahip olduğumuz şey, a
tekrar tekrar eşleşen bir model ve eşleşen her a
biri için b
, grup 1'de yakalanan karşılık gelen bir örnek +
var. Daha fazla a
olmadığında veya iddia başarısız olduğunda sona erer , çünkü bir karşılık gelmez b
. bir a
.
İşi bitirmek için, bizim modelimize eklememiz yeterlidir \1 $
. Bu, artık hangi grup 1'in eşleştiğine ve ardından hat bağlantısının sonuna bir geri referanstır. Çapa b
, dizede fazladan 'lerin olmamasını sağlar ; başka bir deyişle, aslında bir n b n'ye sahibiz .
İşte 10.000 karakter uzunluğunda bir tane de dahil olmak üzere ek test senaryolarıyla birlikte son model:
$tests = array(
'aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb', 'aabb', 'aaabbbbb', 'aaaaabbb',
'', 'ab', 'abb', 'aab', 'aaaabb', 'aaabbb', 'bbbaaa', 'ababab', 'abc',
str_repeat('a', 5000).str_repeat('b', 5000)
);
$r6 = '/ ^ (?: a (?= a* (\1?+ b) ) )+ \1 $ /x';
# │ │ └──────┘ │ │
# │ │ 1 │ │
# │ └───────────────┘ │
# │ lookahead │
# └───────────────────────┘
# non-capturing group
: 4 adet eşleşmeleri bulur ab
, aabb
, aaabbb
ve bir 5000 b 5000 . O sürer sadece ideone.com çalıştırmak için 0.06s .
7. Adım: Java testi
Yani kalıp PHP'de çalışır, ancak nihai amaç Java'da çalışan bir kalıp yazmaktır.
public static void main(String[] args) {
String aNbN = "(?x) (?: a (?= a* (\\1?+ b)) )+ \\1";
String[] tests = {
"",
"ab",
"abb",
"aab",
"aabb",
"abab",
"abc",
repeat('a', 5000) + repeat('b', 4999),
repeat('a', 5000) + repeat('b', 5000),
repeat('a', 5000) + repeat('b', 5001),
};
for (String test : tests) {
System.out.printf("[%s]%n %s%n%n", test, test.matches(aNbN));
}
}
static String repeat(char ch, int n) {
return new String(new char[n]).replace('\0', ch);
}
Model beklendiği gibi çalışıyor ( ideone.com'da görüldüğü gibi ).
Ve şimdi sonuca geliyoruz ...
Bu sözü gerektiğini a*
lookahead ve gerçekten de "ana yılında +
döngü", her iki izni geri dönüşlerle. Okuyucular, bunun neden doğruluk açısından bir sorun olmadığını ve neden aynı zamanda her ikisini birden sahiplenmenin de işe yarayacağını doğrulamaya teşvik edilir (belki de zorunlu ve zorunlu olmayan sahiplik belirleyiciyi aynı modelde karıştırmak yanlış algılamalara yol açabilir).
Ayrıca, a n b n ile eşleşecek bir düzenli ifade kalıbı olduğu düzgün olsa da , bunun pratikte her zaman "en iyi" çözüm olmadığı da söylenmelidir . Çok daha iyi bir çözüm, ^(a+)(b+)$
hosting programlama dilinde grup 1 ve 2 tarafından yakalanan dizelerin uzunluğunu basitçe eşleştirmek ve ardından karşılaştırmaktır.
PHP'de şöyle görünebilir (ideone.com'da görüldüğü gibi ):
function is_anbn($s) {
return (preg_match('/^(a+)(b+)$/', $s, $groups)) &&
(strlen($groups[1]) == strlen($groups[2]));
}
Bu makalenin amacı, okuyucuları regex'in neredeyse her şeyi yapabileceğine ikna etmek DEĞİLDİR ; Açıkça olamaz ve yapabileceği şeyler için bile, daha basit bir çözüme yol açıyorsa, barındırma diline en azından kısmi yetkilendirme düşünülmelidir.
En üstte bahsedildiği gibi, bu makale zorunlu [regex]
olarak stackoverflow için etiketlenmiş olsa da, belki de bundan daha fazlasıdır. İddialar, iç içe geçmiş referanslar, iyelik nicelik belirteci, vb. Hakkında öğrenmenin kesinlikle değeri olsa da, belki de buradaki daha büyük ders, bir kişinin sorunları çözmeye çalışabileceği yaratıcı süreç, maruz kaldığınızda genellikle gerektirdiği kararlılık ve sıkı çalışmadır. çeşitli kısıtlamalar, çalışan bir çözüm oluşturmak için çeşitli bölümlerden sistematik kompozisyon vb.
Bonus malzeme! PCRE özyinelemeli desen!
PHP'yi ortaya çıkardığımız için, PCRE'nin özyinelemeli kalıp ve alt yordamları desteklediği söylenmelidir. Bu nedenle, aşağıdaki kalıp preg_match
( ideone.com'da görüldüğü gibi ) için çalışır :
$rRecursive = '/ ^ (a (?1)? b) $ /x';
Şu anda Java'nın normal ifadesi yinelemeli kalıbı desteklemiyor.
Daha da fazla bonus malzeme! Eşleşen bir n- b , n c n !!
Yani , normal olmayan, ancak yine de bağlamdan bağımsız olan a n b n'yi nasıl eşleştireceğimizi gördük , ancak bağlamdan bağımsız bile olmayan a n b n c n'yi de eşleştirebilir miyiz ?
Cevap elbette EVET! Okuyucuların bunu kendi başlarına çözmeye çalışmaları önerilir, ancak çözüm aşağıda sunulmuştur ( ideone.com'da Java'da uygulama ile ).
^ (?: a (?= a* (\1?+ b) b* (\2?+ c) ) )+ \1 \2 $