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

时间:2016-02-25 21:37:51

标签: 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+\.?\d+)?\.\.(\d+\.?\d+)$)

我觉得它可能会更好。我错了吗?请注意,我有两个与中间管道几乎相同的子句,唯一的区别是一个匹配..12而另一个匹配12..

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

5 个答案:

答案 0 :(得分:4)

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

^(?=\.*\d)(?:\d+(?:\.\d+)?)?\.\.(?:\d+(?:\.\d+)?)?$

Java正则表达式:

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

RegEx Demo

(?=\.*\d)是确保至少有一位数字的正向前瞻,从而确保我们不仅仅将..与有效输入相匹配。

答案 1 :(得分:2)

你可以使用这种模式,这种模式不是最短但更有效率的,没有无用的东西:

\d

test

注意:

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

如果你使用\d+(?:\.\d+)?\.\.(?:\d+(?:\.\d+)?)?|\.\.\d+(?:\.\d+)? 方法(在这种情况下似乎是合乎逻辑的),模式是隐式锚定的,所以你可以写:

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

如果你想要最短的模式你可以写(perl,pcre,ruby,但不是Java!):

def apply_filter(x): 
    filterer = { 
        1: 'ether proto 0x88B8', 
        2: 'tcp port 102', 
        3: 'ether proto 0x88BA' 
    } 
    return filterer.get(x, '') 



import unittest 

from new_format import apply_filter 


class test_apply_filter(unittest.TestCase): 

    def setUp(self): 
        pass 
    def tearDown(self): 
        pass 

    def test_filter_by_name(self): 
        self.assertEqual(apply_filter(1),"ether proto 0x88B8") 
        self.assertEqual(apply_filter(2),"tcp port 102") 
        self.assertEqual(apply_filter(3),"ether proto 0x88BA") 

if __name__ == '__main__': 
    unittest.main()

它很短但效率低。

答案 2 :(得分:2)

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

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

答案 3 :(得分:2)

保持替代品的效率不高。

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

应该做的伎俩,而不是引起太多的回溯。

但是,我认为滥用正则数据解析数字是个好主意。他们不是为了这个。例如,这可能是您的模式的有效输入:

1.2345678901234567890..

但它超过了双精度。还有科学记数法:1e-10..1e10而不是0.000000001..10000000000。您仍然不知道左侧是否小于右侧。

我建议你坚持这样做1.在..拆分,2。使用经过充分测试的双解析器解析两边,3。检查其他约束,例如至少一方被设置,例如如果两者都设置,则左< =右。

答案 4 :(得分:1)

如果您首先使用前瞻以确保字符串不仅仅是..,那么您可以将这两个数字设为可选并避免更改:

^(?!\.\.$)(\d+(\.\d+)?)?\.\.(\d+(\.\d+)?)?$

顺便说一句,(\d+\.?\d+)要求至少有两位数字,即使没有小数点也是如此。也许在你的情况下没问题,但这种方式也更有效率。

通常,您希望避免使正则表达式的相邻部分与字符串的相同部分匹配。如果无法匹配,则正则表达式引擎将花费大量时间尝试将某些数字与第一个\d+匹配,而某些数字与第二个\d+匹配,以及每种可能的组合。