Вопрос-ответ

How can I count the number of matches for a regex?

Как я могу подсчитать количество совпадений для регулярного выражения?

Допустим, у меня есть строка, которая содержит это:

HelloxxxHelloxxxHello

Я компилирую шаблон для поиска 'Hello'

Pattern pattern = Pattern.compile("Hello");
Matcher matcher = pattern.matcher("HelloxxxHelloxxxHello");

Должно быть найдено три совпадения. Как я могу подсчитать, сколько совпадений было?

Я пробовал различные циклы и использовал matcher.groupCount() но это не сработало.

Переведено автоматически
Ответ 1

matcher.find() не находит все совпадения, только следующее совпадение.

Решение для Java 9+

long matches = matcher.results().count();

Решение для Java 8 и старше

Вам нужно будет сделать следующее. (Начиная с Java 9, есть более удобное решение)

int count = 0;
while (matcher.find())
count++;

Кстати, matcher.groupCount() это нечто совершенно другое.

Полный пример:

import java.util.regex.*;

class Test {
public static void main(String[] args) {
String hello = "HelloxxxHelloxxxHello";
Pattern pattern = Pattern.compile("Hello");
Matcher matcher = pattern.matcher(hello);

int count = 0;
while (matcher.find())
count++;

System.out.println(count); // prints 3
}
}

Обработка перекрывающихся совпадений

При подсчете совпадений из aa in aaaa приведенный выше фрагмент даст вам 2.

aaaa
aa
aa

Чтобы получить 3 совпадения, т.е. Такое поведение:

aaaa
aa
aa
aa

Вы должны искать совпадение по индексу <start of last match> + 1 следующим образом:

String hello = "aaaa";
Pattern pattern = Pattern.compile("aa");
Matcher matcher = pattern.matcher(hello);

int count = 0;
int i = 0;
while (matcher.find(i)) {
count++;
i = matcher.start() + 1;
}

System.out.println(count); // prints 3
Ответ 2

Это должно сработать для совпадений, которые могут перекрываться:

public static void main(String[] args) {
String input = "aaaaaaaa";
String regex = "aa";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
int from = 0;
int count = 0;
while(matcher.find(from)) {
count++;
from = matcher.start() + 1;
}
System.out.println(count);
}
Ответ 3

Начиная с Java 9, вы можете использовать поток, предоставляемый Matcher.results()

long matches = matcher.results().count();
Ответ 4

Если вы хотите использовать потоки Java 8 и у вас аллергия на while циклы, вы могли бы попробовать это:

public static int countPattern(String references, Pattern referencePattern) {
Matcher matcher = referencePattern.matcher(references);
return Stream.iterate(0, i -> i + 1)
.filter(i -> !matcher.find())
.findFirst()
.get();
}

Отказ от ответственности: это работает только для непересекающихся совпадений.

Пример:

public static void main(String[] args) throws ParseException {
Pattern referencePattern = Pattern.compile("PASSENGER:\\d+");
System.out.println(countPattern("[ \"PASSENGER:1\", \"PASSENGER:2\", \"AIR:1\", \"AIR:2\", \"FOP:2\" ]", referencePattern));
System.out.println(countPattern("[ \"AIR:1\", \"AIR:2\", \"FOP:2\" ]", referencePattern));
System.out.println(countPattern("[ \"AIR:1\", \"AIR:2\", \"FOP:2\", \"PASSENGER:1\" ]", referencePattern));
System.out.println(countPattern("[ ]", referencePattern));
}

Это выводит:

2
0
1
0

Это решение для непересекающихся совпадений с потоками:

public static int countPattern(String references, Pattern referencePattern) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
new Iterator<Integer>() {
Matcher matcher = referencePattern.matcher(references);
int from = 0;

@Override
public boolean hasNext() {
return matcher.find(from);
}

@Override
public Integer next() {
from = matcher.start() + 1;
return 1;
}
},
Spliterator.IMMUTABLE), false).reduce(0, (a, c) -> a + c);
}
java regex