Java 这个正则表达式的更简洁的版本?

Java 这个正则表达式的更简洁的版本?,java,regex,Java,Regex,我试图匹配一个整数或十进制数,后跟两个点。,后跟另一个整数或十进制数。在点的每一侧,数字是可选的,但它们必须出现在一侧或另一侧。我将使用Java进行实际实现(String#matches()) 例如,这些应匹配: .12 12.. 12..24 12.23..14.25 但这些不应: 。 foo foo..bar bazz12..12buzz foo11..22 11..22efg 这是我能想到的最好的: (^(\d+\.?\d+)\.\.(\d+\.?\d+)?$)|(^(\d+\.?

我试图匹配一个整数或十进制数,后跟两个点。,后跟另一个整数或十进制数。在点的每一侧,数字是可选的,但它们必须出现在一侧或另一侧。我将使用Java进行实际实现(
String#matches()

例如,这些应匹配:

  • .12
  • 12..
  • 12..24
  • 12.23..14.25
但这些不应:

  • foo
  • foo..bar
  • bazz12..12buzz
  • foo11..22
  • 11..22efg
这是我能想到的最好的:

(^(\d+\.?\d+)\.\.(\d+\.?\d+)?$)|(^(\d+\.?\d+)?\.\.(\d+\.?\d+)$)
我觉得会更好。我错了吗?注意,我有两个与中间的管道基本上相同的子句,唯一的区别是一个匹配<代码> .. 12 < /代码>和其他匹配<代码> 12 .. < /代码>

编辑:
感谢所有的投入!我选择了@anubhava,因为我要的是最短的。也谢谢你指出我原始表达中的错误

您可以使用这种模式,它不是最短的,而是更有效的,而且没有无用的东西:

^(?:\d+(?:\.\d+)?\.\.(?:\d+(?:\.\d+)?)?|\.\.\d+(?:\.\d+)?)$

注:

有时默认情况下,
\d
可以匹配所有unicode数字。更准确地说,您可以用
[0-9]
替换所有这些字符,以获得更快的结果(需要测试的字符更少)

如果使用
matches()
方法(在本例中这似乎是合乎逻辑的),则该模式将隐式锚定,因此您可以编写:

\d+(?:\.\d+)?\.\.(?:\d+(?:\.\d+)?)?|\.\.\d+(?:\.\d+)?

如果您想要编写最短的模式(perl、pcre、ruby,但不是Java!):


它很短,但效率很低。

您可以使用前瞻来缩短正则表达式:

^(?=\.*\d)(?:\d+(?:\.\d+)?)?\.\.(?:\d+(?:\.\d+)?)?$
Java正则表达式:

Pattern p = 
        Pattern.compile("^(?=\\.*\\d)(?:\\d+(?:\\.\\d+)?)?\\.\\.(?:\\d+(?:\\.\\d+)?)?$");


(?=\.*\d)
是一种积极的前瞻,它确保至少有一个数字,从而确保我们不只是匹配
作为有效的输入。

@Casimir是一个令人惊讶的难以击败的人!我尝试了各种排列,我能想到的唯一改进是他已经做了一个——用
[0-9]
替换
\d
。以下是一些统计数据:

Author: OP, Len: 63, Memory: 784, Time: 0.9156907489523292
Author: Casimir, Len: 59, Memory: 544, Time: 0.7456484709400684
Author: Casimir0-9, Len: 77, Memory: 568, Time: 0.7377533189719543
Author: anubhava, Len: 51, Memory: 472, Time: 0.8746482610004023
编辑:根据评论中的建议,使用“1..2”测试用例以及超长用例进行更新

Author: Anony-Mousse, Len: 45, Memory: 456, Time: 1.3653777639847249
Author: Casimir, Len: 59, Memory: 544, Time: 1.1941137500107288
Author: anubhava, Len: 51, Memory: 472, Time: 1.5450064099859446
Author: OP, Len: 63, Memory: 784, Time: 1.82177433592733
Failed: should match '1..2'
Author: Casimir0-9, Len: 77, Memory: 568, Time: 1.1341593150282279
下面是我如何测试的:

import re
import sys
from timeit import timeit

Compiled_re = None
Failures = None

Should_match = (
    "1..2",          # EDIT: Updated per comments
    "..12",
    "12..",
    "12..24",
    "12.23..14.25",
    "123.456789012345..98765.43210",
)

Shouldnt_match = (
    "..",
    "foo",
    "foo..bar",
    "bazz12..12buzz",
    "foo11..22",
    "11..22efg",
    "123.456789012345..98765.43210.",
)

def test_re():

    cre = Compiled_re
    global Failures
    Failures = {}

    for test in Should_match:
        if cre.match(test) is not None:
            pass
        else:
            Failures[test] = "Failed: should match '{:s}'".format(test)

    for test in Shouldnt_match:
        if cre.match(test) is None:
            pass
        else:
            Failures[test] = "Failed: should not match '{:s}'".format(test)


candidates = {
    r"(^(\d+\.?\d+)\.\.(\d+\.?\d+)?$)|(^(\d+\.?\d+)?\.\.(\d+\.?\d+)$)":"OP",
    r"^(?:\d+(?:\.\d+)?\.\.(?:\d+(?:\.\d+)?)?|\.\.\d+(?:\.\d+)?)$":"Casimir",
    #r"^(\d+(?:\.\d+)?)?\.\.(\d+(?:\.\d+)?)?$":"dasblinkenlight",
    r"^(?=\.*\d)(?:\d+(?:\.\d+)?)?\.\.(?:\d+(?:\.\d+)?)?$":"anubhava",
    r"^(?:[0-9]+(?:\.[0-9]+)?\.\.(?:[0-9]+(?:\.[0-9]+)?)?|\.\.[0-9]+(?:\.[0-9]+)?)$":"Casimir0-9",
    r"^(?:\d+\.)?\d*(?:\d\.\.|\.\.\d)(?:\d+\.)?\d*$":"Anony-Mousse",
}

for pattern,author in candidates.items():
    Compiled_re = re.compile(pattern)
    length = len(pattern)
    mem = sys.getsizeof(Compiled_re)
    time = timeit('test_re()', setup='from __main__ import test_re',number=100000)
    print("Author: {author}, Len: {length}, Memory: {mem}, Time: {time}".format(
        author=author, length=length, mem=mem, time=time))
    if Failures:
        for test in Should_match + Shouldnt_match:
            if test in Failures:
                print(Failures[test])

为了提高效率,尽量缩短备选方案

^(?:\d+\.)?\d*(?:\d\.\.|\.\.\d)(?:\d+\.)?\d*$
应该做到这一点,不要引起太多的回溯

然而,我不认为滥用regexp解析数字是一个好主意。他们不是为了这个。例如,这可能是模式的有效输入:

1.2345678901234567890..
但它超过了双精度。还有科学符号:
1e-10..1e10
而不是
0.00000001..1000000000
。你仍然不知道左手边是否比右手边小


我建议你坚持在1点之前做这件事。在
处拆分。
,2。使用经过良好测试的双解析器解析两侧,3。检查附加约束,例如至少设置了一个边,例如左,如果您首先使用“向前看”来确保字符串不仅仅是
,那么您可以将两个数字都设置为可选,并避免交替:

^(?!\.\.$)(\d+(\.\d+)?)?\.\.(\d+(\.\d+)?)?$
另外,
(\d+\.?\d+
要求至少有两位数字,即使没有小数点。也许这对你来说没关系,但这种方式也更有效


通常,您希望避免正则表达式的相邻部分与字符串的相同部分相匹配。当不可能匹配时,正则表达式引擎将花费大量时间尝试将一些数字与第一个
\d+
和第二个
\d+
以各种可能的组合进行匹配。

你能告诉我为什么它更有效吗?它不会匹配
.12
,可能你的意思是
^(?:\。\d+。\。(?:\d+(?:\.\d+)?(?:\.\d+)$
@eric:anchors
^
$
是因子,因此它们只测试一次。使用
(?:…)
而不是捕获组保留内存。在第二个分支中,我删除了第一个数字(因为如果它在这里,第一个分支就会成功,不需要把它放在第二个分支中)。这就是我喜欢的正则表达式,功能强大,但完全不可维护;-)@JérémieB:regexes就像任何其他语言一样,一旦你理解了它们,就很容易阅读和编辑。但是如果你想把它做好,最好的办法是重新编写(write | think)它们。根据您的描述,
应该匹配,但您将其作为不应该匹配的示例。此外,“整数或十进制数”是否匹配允许一个符号?指数符号?请在你所追求的内容中具体化。这是什么样的正则表达式?这将是一个错误。你可能会尝试拆分<代码>…/COD>,然后分别匹配每一方,并逐字检查是否有一个代码>< < /代码>另一个是一个数字。你考虑<代码> 0。< /代码>
.0
是否为十进制数(如在Java中)?即
.0…1
是否匹配?此外,您的regexp可能与
1..2
不匹配?值得一提的是:如果您正在编写标记器(lexer),您可能想看一看。浮点加范围可能是一个挑战。它较短,但效率不高。有关数字,请参阅我的答案。@AustinHastings:OP没有提到效率,这怎么可能有什么关系?这个答案很好!(但请去掉斜杠;这是Java,不是JavaScript。)@OP说Alanmore“可能更好”。他是对的,他自己的正则表达式至少有一次失败。但better还包括“更快”,因为正则表达式通常用于解析。Anu的回答是“可爱”,而不是“聪明”,因为它解决了所需的部分问题,但需要对字符串进行双重扫描。(非gre)
^(?!\.\.$)(\d+(\.\d+)?)?\.\.(\d+(\.\d+)?)?$