一个关于与固定宽度负向后查找有关的具体问题。

3
我正在编写一个正则表达式(用于Java,如果这很重要),它试图匹配$后面的数字(可以是浮点数),但前面的单词不能是'LOST'。
如果有多个可能的匹配,应返回第一个数字。
为简单起见,假设所有单词都是大写的。
例如,在以下句子中:“我花了$10.12买啤酒”,将匹配到10.12。在句子“我在游戏中输了$11.34”,将没有匹配。在“我在游戏中输了$11.34,并且花了$10.12买啤酒”这个句子中,仍然会匹配到10.12。
我想到的正则表达式是:
.*?(?<!LOST )[$]\s*(?<NUMBER>[0-9]*[.]?[0-9]*).*

我的正则表达式通常都能正常工作,不过我想知道是否有更简单的写法,或者是否有一些特殊情况我没有考虑到。一个小问题是,如果在LOST和$之间有多于一个空格,我仍然不想匹配,但是目前我的正则表达式会匹配。不幸的是,负向回顾需要固定的宽度。
澄清一下:当我说“它前面的单词不是'LOST'时”,我指的是在'$'之前不能有'LOST\s*'。这意味着'LOST $123'和'LOST $123'都不应该匹配123,但是'LOST! $123'可以匹配123。这样做的原因是货币不应该直接受到'LOST'的影响;如果在LOST和$之间有除了空格之外的任何字符,那么货币很可能不是直接受到'LOST'的影响。

2
你能不能先把所有的空格序列合并成一个单独的空格呢? - undefined
另一个想法是正常匹配 \s*,找到紧邻 $ 左侧的下一个位置,该位置不以空格为前导,并使用另一个反向预查 (?<!\s),然后将当前的反向预查放在那里:(?<!\s)(?<!\bLOST)\s*\$(?<NUMBER>\d+(?:\.\d+)?) - undefined
4个回答

4
受到blhsing的回答的启发,我提出了这个正则表达式,它看起来更简洁,并且覆盖了更广泛的边缘情况。
(?:^|(?<!LOST)\b)\W*\$(?<NUMBER>\d+(?:\.\d+)?)

因为Java不能有一个非固定宽度的lookbehind。你放置lookbehind的位置是至关重要的。
1. 由于你不希望在货币之前匹配到单词LOST,你可以先匹配一个单词边界:
\b

然后你需要确保这个单词不会丢失。
(?<!LOST)\b

在此之后,将您的货币匹配放在其后面,并在前面加上可选的非单词字符:
(?<!LOST)\b\W*\$(?<NUMBER>\d+(?:\.\d+)?)

然后,添加一些边缘情况,比如字符串以货币符号开头:
(?:^|(?<!LOST)\b)\W*\$(?<NUMBER>\d+(?:\.\d+)?)

查看测试用例

是的,这个解决方案比@blhsing的解决方案更好地处理了边缘情况。例如,这个解决方案会正确处理“*$11.34 IN THE GAME AND PAID $10.12 FOR THE BEER”。 - undefined
有没有办法修改答案,使得“LOST!$10.12”成为一个匹配项?也就是说,只有在LOST和$之间只有\s*时才没有匹配项。 - undefined

4
假设在“LOST”和美元符号之间可能有1到99个空格。我还假设数字有两位小数,并且没有逗号作为千位分隔符。然后可以尝试使用正则表达式匹配字符串。
(?<!\bLOST\s{1,99})\$(?<NUMBER>(?:0|[1-9]\d*)\.\d{2})\b

如果有一个匹配项,捕获组名为NUMBER的内容将包含利息的金额。 演示 将光标悬停在链接上的正则表达式上,以获取对表达式中每个元素的解释。

另一种方法是尝试匹配正则表达式。
\bLOST\s+\$(?:0|[1-9]\d*)\.\d{2}\b|\$(?<NUMBER>(?:0|[1-9]\d*)\.\d{2})\b

演示

在这种情况下,不要关注那些没有捕获的匹配项;只关注那些有捕获的匹配项,其中捕获组NUMBER将包含感兴趣的货币值。在这里

\bLOST\s+\$(?:0|[1-9]\d*)\.\d{2}\b

匹配,但不捕获,以"LOST"开头,后跟一个或多个空格,再后跟一个美元符号的值。可以说它吞噬了这样的子字符串。

我看到它能够工作,但是我不理解第二种方法。这个想法是匹配一个非捕获组或者一个捕获组。如果非捕获组匹配成功后,为什么捕获组也会被匹配呢?换句话说,假设我有一个正则表达式 'A|B',那么如果A被匹配成功,就不应该再尝试匹配B了对吗? - undefined
1
darkgbm,假设字符串是"我在游戏中丢失了$11.34并支付了$10.12买啤酒"。正则表达式的指针最初位于字符串的开头,在"我"之前。交替的第一部分从那个位置开始不匹配,因此尝试匹配交替的第二部分,但也不匹配。因此,字符串指针向前移动一个位置,现在位于"我"和空格之间。这也不匹配,所以指针移动到"L"之前。.. - undefined
1
...LOST $11.34 然后与交替的第一部分匹配。由于没有捕获,我们不关注该匹配。匹配导致字符串指针移动到匹配的末尾,即在“4”和后面的空格之间。空格没有匹配,所以指针向前移动一个字符,并继续向前移动,直到位于美元符号之前。在该位置,交替的第一部分不匹配,但第二部分匹配,将“10.12”捕获在第1组中。这是我们感兴趣的值,因为它被捕获了。明白了吗? - undefined
1
正如你所看到的,交替的第一部分匹配了我们不想要的位,而第二部分捕获了我们想要的位。你可以在这里阅读更多关于这种技术的内容。如果你不想阅读整篇论文,你可以搜索这一部分:“有史以来最好的正则表达式技巧”。 - undefined
1
我在想,也许你可以将匹配模式缩短为\bLOST\h+\$|\$(?<NUMBER>\d+(?:\.\d+)?),或者当然也可以使用更具体的数字匹配模式。 - undefined
1
@Thefourthbird,说得好。读者们:#4建议在我的第二种方法中,不捕获的第一部分不需要匹配到美元符号之后。(#4:你知道哪种方法更有效吗?)他(从羽毛的颜色可以看出是个男性)还建议使用\h而不是\s,主要是为了避免匹配换行符。我相信水平空白\h是一个制表符或Unicode空格分隔符。 - undefined

3
由于在Java中,回顾模式必须具有固定的宽度,您可以使用负向前瞻模式来排除单词LOST,从而允许可变数量的空格。此外,为了防止匹配发生在行的开头,还可以在交替模式中包含行的开头^
(?:^|(?!LOST\b)\b\w+\s*)\$\s*(?<NUMBER>\d+\.\d+)

演示:https://regex101.com/r/4pqyc0/7

-1
我不会在这里使用回顾断言,而是使用捕获组。
String input = "I LOST $11.34 IN THE GAME AND PAID $10.12 FOR THE BEER";
String pattern = "\\bLOST \\$?(\\d+(?:\\.\\d+)?)";
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(input);
if (m.find()) {
    System.out.println(m.group(1));  // 11.34
}

我的意图是避免匹配在“LOST $”之后的数字,并且不能保证“LOST $”可能出现在一个有效的“$”之前或之后。所以这种方法行不通。还是谢谢你的回答。 - undefined

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