【问题标题】:Count regex matches with streams计数与流的正则表达式匹配
【发布时间】:2016-04-04 13:06:30
【问题描述】:

我正在尝试使用基于 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 的简单方法?

【问题讨论】:

  • 分割 != 匹配。这就是为什么你得到奇数。你应该否定你的 Pattern 以检索数字并得到你想要的。
  • @Tunaki takeWhile 看起来很有趣。但它显然会在 Java 9 中可用,而不是在 Java 8 中。
  • @Flown 我知道splitAsStream 做了什么以及为什么它不像我使用它那样工作。我刚刚尝试了您的建议来否定该模式,我很惊讶地看到"1,2,3,4""1" 的正确结果。您要发布答案吗?
  • 在 Java-9 中:matcher.results().count();

标签: java regex java-8 java-stream


【解决方案1】:

要正确使用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();

【讨论】:

  • 这是我正在寻找的“简单”解决方案!但我更喜欢否定"(?:\\d+)" 这样的模式,因为否定任何其他模式更容易/可行,而不仅仅是整数。
  • 虽然并不总是有效。对于这个输入"a 2",计数是 2 而不是 1
  • Java 9 有直接的解决方案Pattern.compile("\\d+").matcher("1,2,3,4").results().count()...
  • 有更通用的解决方案吗?我有一个很长的正则表达式字符串,它不只是匹配数字..
  • @JennaKwon 取决于您的用例
【解决方案2】:

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-[]

【讨论】:

  • 谢谢。我实际上知道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

【讨论】:

  • 我刚刚尝试过,它确实产生了正确的结果。它也非常有用!我不确定它是否符合“简单”解决方案!那我想,我只需要写一次SpliterMatcher,然后用不同的匹配器重用它。
  • 为每个流创建一个新的拆分器并没有错——无论如何,这总是在幕后发生的。这也是实现一种尚不存在的流的直接方式,在这方面,它很简单,它由一个包含一个具体方法和一个委托对象的单个类组成。它可以简单到什么程度?但是,当您流式传输整数而不是 MatchResults 时,实现 Spliterator.OfInt 而不是 Spliterator&lt;Integer&gt; 并创建 IntStream 会更有效。并且为了确保可重用性,它应该报告ORDERED...
  • 如果有一个简单、直接的实现可能(这里就是这种情况),我建议覆盖forEachRemaining
【解决方案4】:

很快,您有一个 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&lt;String&gt;().stream()... 的另一种方式

我错了吗?

【讨论】:

  • 这需要 2 种不同的正则表达式模式。 1 用于分隔符,1 用于匹配数据。我想避免这种情况。否则它会产生正确的结果。
【解决方案5】:

Java 9

您可以使用Matcher#results() 获取所有匹配项:

Stream&lt;MatchResult&gt;    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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 2016-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多