2008-11-06 101 views
9

好了,我有这样的正则表达式:我可以优化此手机正则表达式吗?

(|^|>)(((((((\+|00)(31|32)()?(\(0\))?)|0)([0-9]{2})(-)?()?)?)([0-9]{7}))|((((((\+|00)(31|32)()?(\(0\))?)|0)([0-9]{3})(-)?()?)?)([0-9]{6}))|((((((\+|00)(31|32)()?(\(0\))?)|0)([0-9]{1})(-)?()?)?)([0-9]{8})))(|$|<) 

它格式化荷兰和比利时的电话号码(我只希望那些因此31和32作为国家代码)。

它的破译没有太多乐趣,但你可以看到它也有很多重复。但现在确实很准确

以下所有欧洲格式化的电话号码被接受

00312
0031223234567 
0031612345678 
+31(0)20-1234567 
+31(0)223-234567 
+31(0)6-12345678 
020-1234567 
0223-234567 
06-12345678 
02
0223234567 
0612345678 

和下面的错误格式化哪些不是

06-1234567 (mobile phone number in the Netherlands should have 8 numbers after 06) 
0223-1234567 (area code with home phone) 

与此相反,这是很好处理它。

020-1234567 (area code with 3 numbers has 7 numbers for the phone as opposed to a 4 number area code which can only have 6 numbers for phone number) 

正如你可以看到它的“ - ”字符,使得它有点困难,但我需要它在那里,因为它通常被人们所使用的格式的一部分,我希望能够对其进行解析所有。

现在是我的问题...你看到一种方法来简化这个正则表达式(甚至改善它,如果你看到它的错误),同时保持相同的规则?

您可以在regextester.com

(测试它的“(|^|>)”是检查它是否在与可能一个字也被任何一个新行或之前的开始“ >'。我搜索HTML页面中的电话号码。)

+0

我的第一个问题是:你真的需要所有这些捕获?你不能只抓住重要的部分并重新格式化。什么是相关的部分? – Axeman 2008-11-06 22:20:58

+0

不,我搜索一堆文本中的电话号码,我不知道号码是什么,以什么传统方式格式化。在我发现它后,我基本上不再需要它了 – youri 2008-11-07 00:25:53

回答

12

首先观察:阅读正则表达式是一场噩梦。它呼吁Perl的/ x模式。第二个观察:有很多很多很多,并且在表达式中有大量的括号(42,如果我计算正确的话; 42当然是“生命的答案,宇宙和一切”) - 如果你需要解释,请参阅道格拉斯亚当斯“银河系的Hitchiker指南”)。

比尔蜥蜴注意到你多次使用'(-)?()?'。与'-? ?'或可能'[- ]?'相比,没有什么明显的优势,除非你真的打算单独捕获实际的标点符号(但是有很多捕获括号用于确定哪个'n'项目可以使用硬)。

那么,让我们尝试修改一行代码的副本:

(|^|>) 
(
    ((((((\+|00)(31|32)()?(\(0\))?)|0)([0-9]{2})(-)?()?)?)([0-9]{7})) | 
    ((((((\+|00)(31|32)()?(\(0\))?)|0)([0-9]{3})(-)?()?)?)([0-9]{6})) | 
    ((((((\+|00)(31|32)()?(\(0\))?)|0)([0-9]{1})(-)?()?)?)([0-9]{8})) 
) 
(|$|<) 

OK - 现在我们可以看到正则表达式的规则结构。

从这里可以进行更多的分析。是的,正则表达式可以有很大的改进。首先,显而易见的是提取国际前缀部分,并将其应用一次(可选地或需要​​前导零),然后应用国家规则。

(|^|>) 
(
    (((\+|00)(31|32)()?(\(0\))?)|0) 
    (((([0-9]{2})(-)?()?)?)([0-9]{7})) | 
    (((([0-9]{3})(-)?()?)?)([0-9]{6})) | 
    (((([0-9]{1})(-)?()?)?)([0-9]{8})) 
) 
(|$|<) 

然后我们就可以简化标点符号如前所述,并删除了一些振振有词多余的括号,并提高国家代码识别器:

(|^|>) 
(
    (((\+|00)3[12] ?(\(0\))?)|0) 
    (((([0-9]{2})-? ?)?)[0-9]{7}) | 
    (((([0-9]{3})-? ?)?)[0-9]{6}) | 
    (((([0-9]{1})-? ?)?)[0-9]{8}) 
) 
(|$|<) 

我们可以观察到,正则表达式不会对执行规则移动电话代码(例如,它不坚持'06'后跟8位数字)。它似乎也允许1,2或3位数的“交换”代码是可选的,即使是带有国际前缀的代码也可能不是你想要的,而修复则删除更多的括号。我们可以以后去除更小括号,导致:

(|^|>) 
(
    (((\+|00)3[12] ?(\(0\))?)|0) # International prefix or leading zero 
    ([0-9]{2}-? ?[0-9]{7}) |  # xx-xxxxxxx 
    ([0-9]{3}-? ?[0-9]{6}) |  # xxx-xxxxxx 
    ([0-9]{1}-? ?[0-9]{8})   # x-xxxxxxxx 
) 
(|$|<) 

而且你可以从这里工作了进一步的优化,我倒是希望。

8

好主全能,真是一团糟! :)如果您有高级语义或业务规则(例如您所描述的关于欧洲数字,荷兰数字等的规则),您可能会更好地将单个正则表达式测试分解为几个单独的正则表达式测试,每个高级规则一个。

if number =~ /...../ # Dutch mobiles 
    # ... 
elsif number =~ /..../ # Belgian landlines 
    # ... 
# etc. 
end 

阅读,维护和改变这种方式会比较容易。

+0

并通过最有可能匹配的顺序排列你的测试(假设你足够了解人口统计学)。 – tvanfosson 2008-11-06 14:15:14

+0

@ tvanfosson:当然;同意。 – Pistos 2008-11-06 14:17:25

+0

,我没有想到:P感谢:) – youri 2008-11-06 16:14:00

3

将其拆分为多个表达式。例如(伪代码)...

phone_no_patterns = [ 
    /[0-9]{13}/, # 00312
    /+(31|32)\(0\)\d{2}-\d{7}/ # +31(0)20-1234567 
    # ..etc.. 
] 
def check_number(num): 
    for pattern in phone_no_patterns: 
     if num matches pattern: 
      return match.groups 

然后你只需在每个模式循环,如果每一个匹配检查..

拆分模式可达使得其很容易解决的造成的具体数字问题(这将是可怕的与单片正则表达式)

2

这不是一个优化的,但你在你的正则表达式使用

(-)?()? 

三次。这将导致您匹配像这些

+31(0)6-12345678 
+31(0)6 12345678 

的电话号码,但也将匹配包含一个破折号后面加一个空格的数字,像

+31(0)6- 12345678 

您可以将其替换

(-)?()? 

(-|)? 

匹配破折号的空格。

3

(31 | 32)看起来不好。当匹配32时,正则表达式引擎将首先尝试匹配31(2个字符),失败并回溯两个字符以匹配31.首先匹配3(一个字符),尝试1(失败),回溯一个字符和匹配2.

当然,你的正则表达式在0800-数字上失败;他们不是10位数字。