获取唯一的正则表达式匹配结果(不使用映射或列表)

8

有没有一种方法可以仅获取唯一匹配项?在匹配之后不使用列表或映射,我希望匹配器的输出立即是唯一的。

示例输入/输出:

String input = "This is a question from [userName] about finding unique regex matches for [inputString] without using any lists or maps. -[userName].";
Pattern pattern = Pattern.compile("\\[[^\\[\\]]*\\]");
Matcher matcher = pattern.matcher(rawText);
while (matcher.find()) {
    String tokenName = matcher.group(0);
    System.out.println(tokenName);
}

这将输出以下内容:
[userName]
[inputString]
[userName]

但我希望它输出以下内容:
[userName]
[inputString]
1个回答

17

有的。你可以结合使用负向前瞻和反向引用:

"(\\[[^\\[\\]]*\\])(?!.*\\1)"

只有当实际匹配模式没有再次出现在字符串中时,才会进行匹配。这实际上意味着您总是获得每个匹配项的最后一个出现位置,因此您将按不同顺序获取它们:

[inputString]
[userName]

如果顺序对你很重要(比如必须按首次出现的顺序排序),那么仅使用正则表达式就无法实现。你需要使用可变长度后置断言,而Java不支持此功能。

进一步阅读:


关于通用解决方案的一些注意事项

请注意,这适用于任何匹配非零宽度的模式。通用解决方案只需:

(yourPatternHere)(?!.*\1)

如果您希望它与具有零宽度匹配的模式一起工作(因为您只想知道位置,并且出于某种原因仅使用回顾),则可以这样做:

(我忽略了双反斜杠,因为那只适用于少数几种语言。)

(zeroWidthPatternHere)(?!.+\1)

另外,请注意(一般而言),如果您的输入可能包含换行符,则可能需要使用"singleline"或"dotall"选项(否则,前瞻将仅检查当前行)。 如果您不能或不想激活它(因为您的模式包括不应与换行符匹配的句点;或者因为您使用JavaScript),则这是通用解决方案:

(yourPatternHere)(?![\s\S]*\1)

为了使这个答案更加普适,以下是如何仅匹配每个匹配项的第一个出现(在具有可变长度回顾后环境的引擎中,如.NET):

(yourPatternHere)(?<!\1.*\1)
or
(yourPatternHere)(?<!\1[\s\S]*\1)

顺序对我的需求并不重要,所以这很完美。现在我只需要研究一下前瞻和后向引用,才能真正理解语法。干杯! - Isaac
1
@Ibrahim,我在这两个主题上添加了两个链接。 - Martin Ender
有很多人试图解释这个问题...但我只理解了你的解释,因为你是以一般性的方式解释的 +1。 - Sufyan Jabr
@MartinEnder 谢谢您的回复。您的清晰解释帮助我学习了很多。 - Hsehdar
嗨,我正在尝试在Dreamweaver正则表达式搜索和Espresso中测试您的解决方案。但对于我来说不起作用。我正在尝试查找相当大的网站源代码中所有不同的数据源="someDSNname"出现次数。但即使在一个小例子中,如果我放入两个那样的出现次数,都会从正则表达式中返回。 我尝试过: (datasource="(.?)")(?!.\1) (datasource="(.?)")(?!.+\1) - pixelwiz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接