2015-12-30 3 views
7

Я пытаюсь подсчитать количество совпадений шаблона регулярного выражения с помощью простого решения на основе Java 8 lambdas/streams. Например, для этой картины/согласовани:Соответствует регулярному выражению с потоками

final Pattern pattern = Pattern.compile("\\d+"); 
final Matcher matcher = pattern.matcher("1,2,3,4"); 

Существует метод splitAsStream который разделяет текст на данном шаблоне вместо сопоставления с шаблоном. Несмотря на то, что это элегантный и сохраняет неизменность, это не всегда верно:

// count is 4, correct 
final long count = pattern.splitAsStream("1,2,3,4").count(); 

// count is 0, wrong 
final long count = pattern.splitAsStream("1").count(); 

Я также попытался (AB) с помощью IntStream. Проблема в том, что я должен угадать, сколько раз я должен называть matcher.find() вместо того, чтобы он возвращал false.

final long count = IntStream 
     .iterate(0, i -> matcher.find() ? 1 : 0) 
     .limit(100) 
     .sum(); 

Я знаком с традиционным решением while (matcher.find()) count++;count где изменчиво. Есть ли простой способ сделать это с помощью Java 8 lambdas/streams?

+1

Попробуйте посмотреть в 'takeWhile': http://stackoverflow.com/a/20765715/1743880 – Tunaki

+3

Расщепление = согласующего!. Вот почему вы получаете нечетные числа. Вы должны отменить свой шаблон, чтобы получить номера и получить то, что вы хотите. – Flown

+0

@ Tunaki 'takeWhile' выглядит довольно интересно. Но он будет доступен в Java 9, по-видимому, а не Java 8. –

ответ

4

Чтобы использовать Pattern::splitAsStream правильно, вы должны инвертировать регулярное выражение.Это означает, что вместо \\d+ (который разделился бы на каждом номере) вы должны использовать \\D+. Это дает вам число в вашей строке.

final Pattern pattern = Pattern.compile("\\D+"); 
// count is 4 
long count = pattern.splitAsStream("1,2,3,4").count(); 
// count is 1 
count = pattern.splitAsStream("1").count(); 
+0

Это решение «* simple *», которое я искал! Но я предпочитаю отрицать шаблон как этот '' (?: \\ d +) ", так как проще или возможно отрицать любой другой шаблон, а не только целые числа. –

+0

Не всегда работает. Для этого входа '' a 2 "' счет равен 2 вместо 1 –

+4

Java 9 имеет прямое решение 'Pattern.compile (" \\ d + "). Matcher (" 1,2,3,4 "). results(). count() '... – Holger

1

Вскоре у вас есть stream of String и String pattern: сколько из этих строк соответствует этому шаблону?

final String myString = "1,2,3,4"; 
Long count = Arrays.stream(myString.split(",")) 
     .filter(str -> str.matches("\\d+")) 
     .count(); 

где первая линия может быть другой способ течь List<String>().stream(), ...

Я ошибаюсь?

+0

Для этого требуется 2 разных шаблона регулярных выражений. 1 для разделителя и 1 для сопоставления данных. Я бы хотел этого избежать. В противном случае он дает правильные результаты. –

3

Вероятно, виноват довольно надуманный язык в javadoc Pattern.splitAsStream.

поток, возвращаемый этим методом, содержит каждую подстроку входной последовательности, которая завершается другой подпоследовательности, которая соответствует этому шаблону или прекращается в конце входной последовательности.

Если распечатать все матчи 1,2,3,4 вы можете быть удивлены заметить, что это на самом деле возвращения запятые, а не цифры.

System.out.println("[" + pattern.splitAsStream("1,2,3,4") 
      .collect(Collectors.joining("!")) + "]"); 

принты [!,!,!,]. Нечетный бит - то, почему он дает вам 4, а не 3.

Очевидно, что это также объясняет, почему "1" дает 0, потому что нет строки между чисел в строке.

Быстрый демо:

private void test(Pattern pattern, String s) { 
    System.out.println(s + "-[" + pattern.splitAsStream(s) 
      .collect(Collectors.joining("!")) + "]"); 
} 

public void test() { 
    final Pattern pattern = Pattern.compile("\\d+"); 
    test(pattern, "1,2,3,4"); 
    test(pattern, "a1b2c3d4e"); 
    test(pattern, "1"); 
} 

печатает

1,2,3,4-[!,!,!,] 
a1b2c3d4e-[a!b!c!d!e] 
1-[] 
+0

Спасибо. Я действительно знаю, что делает 'splitAsStream' и почему он не работает так, как я его использую. Я до сих пор не знаю, как считать матчи. Тем не менее, ваш ответ достаточно информативен и хорошо написан, поэтому вы получаете +1. –

3

Вы можете продлить AbstractSpliterator решить эту проблему:

static class SpliterMatcher extends AbstractSpliterator<Integer> { 
    private final Matcher m; 

    public SpliterMatcher(Matcher m) { 
     super(Long.MAX_VALUE, NONNULL | IMMUTABLE); 
     this.m = m; 
    } 

    @Override 
    public boolean tryAdvance(Consumer<? super Integer> action) { 
     boolean found = m.find(); 
     if (found) 
      action.accept(m.groupCount()); 
     return found; 
    } 
} 

final Pattern pattern = Pattern.compile("\\d+"); 

Matcher matcher = pattern.matcher("1"); 
long count = StreamSupport.stream(new SpliterMatcher(matcher), false).count(); 
System.out.println("Count: " + count); // 1 

matcher = pattern.matcher("1,2,3,4"); 
count = StreamSupport.stream(new SpliterMatcher(matcher), false).count(); 
System.out.println("Count: " + count); // 4 


matcher = pattern.matcher("foobar"); 
count = StreamSupport.stream(new SpliterMatcher(matcher), false).count(); 
System.out.println("Count: " + count); // 0 
+0

Я просто попробовал это, и он дает правильные результаты. Это также очень информативно! Я не уверен, что он квалифицируется как «простое» решение! Тогда, я думаю, мне нужно написать «SpliterMatcher» один раз и повторно использовать его с разными помощниками. –

+1

Нет ничего плохого в создании нового разделителя для каждого потока - это то, что всегда происходит за кулисами. Это также прямой способ реализации еще не существующего потока, и в этом отношении он * прост, он состоит из одного класса, содержащего один конкретный метод и один объект-делегат. Насколько проще это может быть? Но когда вы передаете целые числа вместо 'MatchResult', более эффективно реализовать' Spliterator.OfInt' вместо 'Spliterator ' и создать' IntStream'. И чтобы обеспечить повторное использование, он должен сообщать 'ORDERED' ... – Holger

+0

И я рекомендую переопределить' forEachRemaining', если возможна простая, прямолинейная реализация (как это имеет место здесь). – Holger

0

Java 9

Вы можете использовать Matcher#results() разжиться всех матчей:

Stream<MatchResult>       results()
Возвращает потока результатов матчей для каждой подпоследовательности из последовательность ввода, которая соответствует шаблону. Результаты совпадения происходят в том же порядке, что и соответствующие подпоследовательности во входной последовательности.

Java-8 и нижний

Другое простое решение, основанное на использовании обратной схеме:

String pattern = "\\D+"; 
System.out.println("1".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length); // => 1 

Здесь все не цифры удаляются с самого начала и конца строки, и то строка разбивается на последовательности без цифр, не сообщая о каких-либо пустых элементах пробелов в байтах (поскольку 0 передается как предел аргумента split).

См this demo:

String pattern = "\\D+"; 
System.out.println("1".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length); // => 1 
System.out.println("1,2,3".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length);// => 3 
System.out.println("hz 1".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length); // => 1 
System.out.println("1 hz".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length); // => 1 
System.out.println("xxx 1 223 zzz".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length);//=>2