代码之家  ›  专栏  ›  技术社区  ›  Marcio Gabe

C#Regex用多个捕获和字符串末尾的匹配来替换奇怪的行为?

  •  2
  • Marcio Gabe  · 技术社区  · 14 年前

    我正试图写一些格式化巴西电话号码的东西,但我希望它从字符串的末尾匹配,而不是从开头匹配,因此它将根据以下模式转换输入字符串:

    "5135554444" -> "(51) 3555-4444"
    "35554444" -> "3555-4444"
    "5554444" -> "555-4444"
    

    s = "5135554444";
    string str = Regex.Replace(s, @"\D", ""); //Get rid of non digits, if any
    str = Regex.Replace(str, @"(\d{0,2})(\d{0,4})(\d{1,4})$", "($1) $2-$3");
    return Regex.Replace(str, @"^\(\) ", ""); //Get rid of empty () at the beginning
    

    返回值与10位数字的预期值相同。但除此之外,它最终表现出一些奇怪的行为。我的结果如下:

    "5135554444" -> "(51) 3555-4444"
    "35554444" -> "(35) 5544-44"
    "5554444" -> "(55) 5444-4"
    

    它似乎忽略了结尾处的$来进行匹配,只是如果我用小于7位的数字进行测试,结果如下:

    "554444" -> "(55) 444-4"
    "54444" -> "(54) 44-4"
    "4444" -> "(44) 4-4"
    

    现在,如果我改变模式,那么在第三次捕获时使用{4}而不是{1,4},结果如下:

    str = Regex.Replace(str, @"(\d{0,2})(\d{0,4})(\d{4})$", "($1) $2-$3");
    
    "5135554444" -> "(51) 3555-4444" //As expected
    "35554444" -> "(35) 55-4444" //The last four are as expected, but "35" as $1?
    "54444" -> "(5) -4444" //Again "4444" in $3, why nothing in $2 and "5" in $1?
    

    我知道这可能是我的愚蠢之举,但如果我想在字符串末尾捕获,那么之前所有的捕获组都将以相反的顺序捕获,这不是更合理吗?

    我认为在最后一个例子中,“54444”会变成“5-4444”。。。那它就不会。。。

    如何做到这一点?

    (我知道也许有更好的方法用不同的方法来完成同样的事情。。。但我真正好奇的是,为什么正则表达式的这种特殊行为看起来很奇怪。因此,这个问题的答案应该集中在解释为什么最后一个捕获被锚定在字符串的末尾,为什么其他捕获没有锚定,如本例所示。所以我对实际的电话格式问题不是特别感兴趣,而是想了解Regex sintax)。。。

    谢谢。。。

    2 回复  |  直到 14 年前
        1
  •  1
  •   Tim Pietzcker    14 年前

    所以你希望第三部分总是有四位数,第二部分是零到四位数,第一部分是零到两位数,但前提是第二部分包含四位数?

    使用

    ^(\d{0,2}?)(\d{0,4})(\d{4})$
    

    作为一个C代码片段,评论道:

    resultString = Regex.Replace(subjectString, 
      @"^             # anchor the search at the start of the string
        (\d{0,2}?)    # match as few digits as possible, maximum 2
        (\d{0,4})     # match up to four digits, as many as possible
        (\d{4})       # match exactly four digits
        $             # anchor the search at the end of the string", 
       "($1) $2-$3", RegexOptions.IgnorePatternWhitespace);
    

    通过添加 ? ?? , *? , +? , {a,b}? )你让它懒惰,我。e。告诉它匹配尽可能少的字符,同时仍然允许找到一个整体匹配。

    没有 ? 在第一组中,当尝试匹配时会发生什么 123456 ?

    \d{0,2} 比赛 12 .

    \d{0,4} 3456 .

    然后 \d{4} 没有任何东西可以匹配,所以regex引擎会回溯,直到这再次成为可能。经过四个步骤后 \d{4} 可以匹配 . 这个 \d{0,4} 为了这个贪婪地放弃了一切。

    现在,一个整体匹配已经找到-不需要尝试任何更多的组合。因此,第一组和第三组将包含部分比赛。

        2
  •  0
  •   cnanney    14 年前

    你必须告诉它,如果第一个匹配的组不在那里,但不是最后一个组,这是可以的:

    (\d{0,2}?)(\d{0,4}?)(\d{1,4})$
    

    在我的测试中正确匹配你的例子。