计数正则表达式与流匹配

时间:2015-12-30 14:35:26

标签: java regex java-8 java-stream

我试图用简单的Java 8 lambdas / streams解决方案来计算正则表达式模式的匹配数。例如,对于这种模式/匹配器:

curl -XPUT localhost:9200/crud_sample/Customer_Info/1 -d '{
    "name": "PH"
}'

方法final Pattern pattern = Pattern.compile("\\d+"); final Matcher matcher = pattern.matcher("1,2,3,4"); 分割给定模式上的文本而不是匹配模式。虽然它很优雅并且保留了不变性,但它并不总是正确的:

splitAsStream

我也试过(ab)使用// 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(); 。问题是我必须猜测我应该多少次调用IntStream而不是直到它返回false。

matcher.find()

我熟悉传统解决方案final long count = IntStream .iterate(0, i -> matcher.find() ? 1 : 0) .limit(100) .sum(); ,其中while (matcher.find()) count++;是可变的。使用Java 8 lambdas / streams有一种简单的方法吗?

5 个答案:

答案 0 :(得分: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();

答案 1 :(得分:3)

Pattern.splitAsStream的javadoc中相当人为的语言可能是罪魁祸首。

  

此方法返回的流包含输入序列的每个子字符串由与此模式匹配的另一个子序列终止,或者由输入序列的末尾终止。

如果您打印出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-[]

答案 2 :(得分: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

答案 3 :(得分: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()...

的另一种方式

我错了吗?

答案 4 :(得分: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作为< em>限制参数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