代码之家  ›  专栏  ›  技术社区  ›  shsteimer

如何改进这个正则表达式?

  •  1
  • shsteimer  · 技术社区  · 16 年前

    我希望正则表达式将有效输入匹配到 Tags 具有以下属性的输入字段:

    • 1-5标签
    • 每个标签长1-30个字符
    • 有效的标记字符为[A-ZA-Z0-9-]
    • 输入和标记可以用任意数量的空白分隔

    例如:

    有效的: Tag1 Tag2 Tag3带破折号Tag4带更多破折号Taag5带混合大小写

    以下是我迄今为止所拥有的——它似乎有效,但我感兴趣的是如何简化它,或者它是否有任何重大缺陷:

    \s*[a-zA-Z0-9-]{1,30}(\s+[a-zA-Z0-9-]{1,30}){0,4}\s*
    
    // that is: 
    \s*                          // match all beginning whitespace
    [a-zA-Z0-9-]{1,30}           // match the first tag
    (\s+[a-zA-Z0-9-]{1,30}){0,4} // match all subsequent tags
    \s*                          // match all ending whitespace
    

    预处理输入以使空白问题更容易不是一个选项(例如修剪或添加空格)。

    如果这很重要,将在JavaScript中使用。如有任何建议,我们将不胜感激,谢谢!

    6 回复  |  直到 16 年前
        1
  •  3
  •   Steve Wortham    16 年前

    您可以这样简化它:

    ^(?:(?:^|\s+)[a-zA-Z0-9-]{1,30}){1,5}\s*$
    

    这个 (?: ) 语法是一个非捕获组,我认为当您本身不需要组时,应该提高性能。

    接下来的诀窍是:

    (?:^|\s+)
    

    多亏了插入符号,这将匹配行的开头,或者一个或多个空白字符。

    更新:这在我的测试中非常有效,当然也没有多余的代码。不过,我只是用了 benchmarking in Regex Hero 发现原来的regex实际上更快。这可能是因为我的造成了更多的回溯。

    更新2:我发现了另一种方法来完成同样的事情,我认为:

    ^(?:\s*[a-zA-Z0-9-]{1,30}){1,5}\s*$
    

    我意识到我太努力了。 \s* 匹配0个或多个空格,这意味着它对单个标记有效。但是…它也适用于2-5个标记,因为该空间不在您的角色类中。 [ ] . 事实上,它失败了,因为它应该有6个标签。这意味着这是一个更具前瞻性的regex,具有更少的回溯、更好的性能和更少的冗余。

    更新第3号:

    我以自己的方式看到了错误。这样做会更好。

    ^(?:\s*[a-zA-Z0-9-]{1,30}\b){1,5}\s*$
    

    \b 就在最后一个之前 ) 将断言单词边界。允许1-30字符长度规则再次正常工作。

        2
  •  2
  •   Pindatjuh    16 年前

    在性能方面,您可以通过以下方式优化(改进)它:

    ^(?:\s+[a-zA-Z0-9]{1,30}){1,5}\s*$
    

    在测试regexp之前,在前面添加一个空白。

    ^
    (?: // don't keep track of groups
    \s+ // first (necessairy whitespace) or between
      [a-zA-Z0-9-]{1,30} // unchanged
      ){1,5} // 1 to 5 tags
    \s*$
    
        3
  •  1
  •   zigdon    16 年前

    你的答复看起来和你要求的差不多。我可以推荐 不过,在本例中,使用re——只需将空白输入拆分为一个数组,然后单独验证数组中的每个值。

    Res很酷,但有时,它们不是完成工作的最佳方式。)

        4
  •  0
  •   Dark Castle    16 年前

    \w 可以替换 a-zA-Z0-9 但它也包含“如果可以的话”。

    你也可以这样分解它:

    (\s*[a-zA-Z0-9-]{1,30}){0,5}
    

    如果你总是保证有空格分隔你的标签。

        5
  •  0
  •   Ben    16 年前

    你可以把它缩短到

    ([a-zA-Z0-9-]{1,30}\s*){1,5}

    我总是喜欢使正则表达式更简洁(它不会影响性能)。

        6
  •  0
  •   Alan Moore Chris Ballance    16 年前

    你不会改进的。你为缩短长度所做的任何事情都会使阅读变得困难,而且regex在这方面不需要任何帮助。;)

    也就是说,无论如何,你的正则表达式需要更复杂。如前所述,它无法确保标记名不以连字符开头或结尾,或者包含两个或更多连续的连字符。单个标记的regex的结构应该如下所示:

    [A-Za-z0-9]+(?:-[A-Za-z0-9]+)*
    

    那么,与最多五个标签匹配的基本regex将是

    [A-Za-z0-9]+(?:-[A-Za-z0-9]+)*(?:\s+[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*){0,4}
    

    …但这不会强制使用最大标记长度。我认为最简单的方法是将原来的regex放在一个lookahead中:

    /^\s*
     (?=[A-Za-z0-9-]{1,30}(\s+[A-Za-z0-9-]{1,30}){0,4}\s*$)
     (?:[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*\s*)+$
    /
    

    lookahead强制使用标记长度以及由空格分隔的最多五个标记的总体结构。然后主体只需要强制执行单个标记的结构。

    我可以把正则表达式 a-z 在字符类中添加 i 修饰语。我没有这样做,因为你说过在ASP.NET验证器中使用regex,据我所知,它们不允许你使用regex修饰符。而且,因为javascript不支持 (?i) 内联修饰符语法,不区分大小写的验证器正则表达式是不可能的。如果我错了,我希望有人能纠正我。