Я думаю, что-то вроде этого - то, что вы хотите.
Для чередующихся символов:
(?=(.)(?!\1)(.))(?:\1\2){2,}
\0
будет всей чередующейся последовательностью, \1
и \2
являются двумя (различными) чередующимися символами.
Для набора символов N и M, возможно разделенных другими символами (замените N
и M
числами здесь):
(?=(.))\1{N}.*?(?=(?!\1)(.))\2{M}
\0
будет весь матч, включая инфикс. \1
символ повторяется (по крайней мере) N
раз, \2
символ повторяется (по крайней мере) M
раз.
Вот тестовый набор на Java.
import java.util.regex.*;
public class Regex3 {
static String runNrunM(int N, int M) {
return "(?=(.))\\1{N}.*?(?=(?!\\1)(.))\\2{M}"
.replace("N", String.valueOf(N))
.replace("M", String.valueOf(M));
}
static void dumpMatches(String text, String pattern) {
Matcher m = Pattern.compile(pattern).matcher(text);
System.out.println(text + " <- " + pattern);
while (m.find()) {
System.out.println(" match");
for (int g = 0; g <= m.groupCount(); g++) {
System.out.format(" %d: [%s]%n", g, m.group(g));
}
}
}
public static void main(String[] args) {
String[] tests = {
"foobababababaf foobaafoobaaaooo",
"xxyyyy axxayyyya zzzzzzzzzzzzzz"
};
for (String test : tests) {
dumpMatches(test, "(?=(.)(?!\\1)(.))(?:\\1\\2){2,}");
}
for (String test : tests) {
dumpMatches(test, runNrunM(3, 3));
}
for (String test : tests) {
dumpMatches(test, runNrunM(2, 4));
}
}
}
Это дает следующий вывод:
foobababababaf foobaafoobaaaooo <- (?=(.)(?!\1)(.))(?:\1\2){2,}
match
0: [bababababa]
1: [b]
2: [a]
xxyyyy axxayyyya zzzzzzzzzzzzzz <- (?=(.)(?!\1)(.))(?:\1\2){2,}
foobababababaf foobaafoobaaaooo <- (?=(.))\1{3}.*?(?=(?!\1)(.))\2{3}
match
0: [aaaooo]
1: [a]
2: [o]
xxyyyy axxayyyya zzzzzzzzzzzzzz <- (?=(.))\1{3}.*?(?=(?!\1)(.))\2{3}
match
0: [yyyy axxayyyya zzz]
1: [y]
2: [z]
foobababababaf foobaafoobaaaooo <- (?=(.))\1{2}.*?(?=(?!\1)(.))\2{4}
xxyyyy axxayyyya zzzzzzzzzzzzzz <- (?=(.))\1{2}.*?(?=(?!\1)(.))\2{4}
match
0: [xxyyyy]
1: [x]
2: [y]
match
0: [xxayyyy]
1: [x]
2: [y]
Объяснение
(?=(.)(?!\1)(.))(?:\1\2){2,}
состоит из двух частей
(?=(.)(?!\1)(.))
устанавливает \1
и \2
, используя lookahead
- Вложенный отрицательный прогноз гарантирует, что
\1
! = \2
- Использование перехватчика для захвата позволяет
\0
иметь полное совпадение (а не только "хвостовой" конец)
(?:\1\2){2,}
фиксирует последовательность \1\2
, которая должна повторяться как минимум дважды.
(?=(.))\1{N}.*?(?=(?!\1)(.))\2{M}
состоит из трех частей
(?=(.))\1{N}
захватывает \1
в перспективе, а затем сопоставляет его N
раз
- Использование Lookahead для захвата означает, что повторение может быть
N
вместо N-1
.*?
позволяет инфиксу разделять два прогона, не желая делать его как можно более коротким
(?=(?!\1)(.))\2{M}
- аналогично первой части
- Вложенный отрицательный прогноз гарантирует, что
\1
! = \2
Регулярное выражение запуска будет соответствовать более длинным циклам, например run(2,2)
совпадений "xxxyyy"
:
xxxyyy <- (?=(.))\1{2}.*?(?=(?!\1)(.))\2{2}
match
0: [xxxyy]
1: [x]
2: [y]
Кроме того, он не позволяет перекрывать совпадения. То есть есть только один run(2,3)
в "xx11yyy222"
.
xx11yyy222 <- (?=(.))\1{2}.*?(?=(?!\1)(.))\2{3}
match
0: [xx11yyy]
1: [x]
2: [y]