【问题标题】:Java repetitive pattern matching (2)Java重复模式匹配(二)
【发布时间】:2011-12-03 15:39:13
【问题描述】:

考虑以下正则表达式:

(([^\|])*\|)*([^\|]*)

这匹配类型的重复字符串模式

("whatever except |" |) {0 to any times} ("whatever except |" |) {1 time}

所以它应该匹配下面的字符串,它有17个子字符串(16个重复,加上“z”作为最后一个)。

"abcd  | e | fg | hijk | lmnop | |   | qrs |   t| uv| w |||||x   y|  z"

确实,RegexPal 会验证给定的正则表达式是否与上述字符串匹配。

现在,我想获取每个子字符串(即“abcd |”、“e |”、“fg |”等),对于它们的数量、长度等没有先验知识。

根据类似标题的previous StackOverflow postMatcherfind() 方法的文档,我只需要做类似的事情

Pattern pattern = Pattern.compile(regex); // regex is the above regex
Matcher matcher = pattern.matcher(input); // input is the above string

while (matcher.find())
{
   System.out.println(matcher.group(1));
}

但是,当我这样做时,我只打印出 2 个字符串:最后一个重复的子字符串 ("x y|") 和一个空值;绝对不是我期望的 16 个子字符串。

在运行 find() 循环之前,检查是否确实发生了匹配也是一件好事,但我不确定是 matches()groupCount() > 0 还是其他一些考虑到find() 也进行匹配,所以应该使用条件,而不做两次匹配工作。

所以,问题

  1. 如何获取所有 16 个重复的子字符串?
  2. 如何获取最后一个子字符串?
  3. 如何检查字符串是否匹配?

【问题讨论】:

    标签: java regex matching repeat


    【解决方案1】:

    恐怕你把事情搞糊涂了。每当您使用重复(“*”、“+”等)时,都无法匹配所有实例。使用((xxx)*) 之类的内容,您可以将整个字符串匹配为group(1),将最后一部分匹配为group(2),仅此而已。

    考虑使用String.split 或更好的Guava 的Splitter


    广告 1。你不能。使用像

    这样的简单模式
    \G([^\|])*(\||$)
    

    find() 一起按顺序获取所有匹配项。请注意 \G 锚定到上一个匹配项。


    广告 2. 如何获取最后一个子字符串?

    作为最后一个结果 find 返回。


    广告 3. 如何检查字符串是否匹配?

    在你最后一个find 之后检查是否matcher.end() == input.length。但是使用这种模式你不需要检查任何东西,因为它总是匹配的。

    【讨论】:

    • 我不确定这是如何工作的,但谢谢。所以,find() 确实会遍历所有匹配项!
    • 我的模式比你的简单,那你缺少什么? \G 确保您的下一场比赛在上一场比赛结束时开始。第一组表示非管道上的任何数字,第二组表示管道或末端。您可能想使用\Z' or '\z 而不是$
    【解决方案2】:

    如果必须使用正则表达式...

    1) 如何获取所有 16 个重复的子串?

    见下文。骑自行车进行比赛时,您不需要匹配所有内容,只需要您想要的部分。 (我得到了 17 场比赛——这是正确的吗?)

    2) 如何获取最后一个子字符串?

    将分隔符切换到正则表达式的开头并允许'^'。

    3) 如何检查字符串是否匹配?

    什么情况会导致不匹配?任何字符串都会匹配。


    这是一个使用正则表达式的解决方案:

    String input = "abcd  | e | fg | hijk | lmnop | |   | qrs |   t| uv| w |||||x   y|  z";
    int expectedSize = 17;
    List<String> expected = new ArrayList<String>(Arrays.asList("abcd  ", " e ", " fg ", " hijk ", " lmnop ", " ", "   ", " qrs ", "   t", " uv", " w ", "",
        "", "", "", "x   y", "  z"));
    
    List<String> matches = new ArrayList<String>();
    
    // Pattern pattern = Pattern.compile("(?:\\||^)([^\\|]*)");
    Pattern pattern = Pattern.compile("(?:_?\\||^)([^\\|]*?)(?=_?\\||$)"); // Edit: allows _| or | as delim
    
    for (Matcher matcher = pattern.matcher(input); matcher.find();)
    {
      matches.add(matcher.group(1));
    }
    
    for (int idx = 0, len = matches.size(); idx < len; idx++)
    {
      System.out.format("[%-2d] \"%s\"%n", idx + 1, matches.get(idx));
    }
    
    assertSame(expectedSize, matches.size());
    assertEquals(expected, matches);
    

    输出

    [1 ] "abcd  "
    [2 ] " e "
    [3 ] " fg "
    [4 ] " hijk "
    [5 ] " lmnop "
    [6 ] " "
    [7 ] "   "
    [8 ] " qrs "
    [9 ] "   t"
    [10] " uv"
    [11] " w "
    [12] ""
    [13] ""
    [14] ""
    [15] ""
    [16] "x   y"
    [17] "  z"
    

    【讨论】:

    • 非常感谢您提供的出色解决方案!我可以要求稍微扩展一下吗?分隔符有时以下划线 (_) 为前缀,即 _|子字符串之间,除非子字符串为空,在这种情况下它不会出现。所以情况可能类似于“abcd _| e || fg _|||| hij”。换句话说,我们在 | 之前有一个“可选”下划线。我想在它出现时将其关闭(它没有出现在子字符串中)。我尝试修改你的正则表达式,但我想出的没有用。
    • @PNS:所以使用\G([^\|]+?)_?\||\G()\||\G([^\|]*)$ 并获取不为空的组作为您的文本。第一部分为非空数据,后跟分隔符,第二部分为空数据,后跟分隔符,第三部分为末尾的数据。
    猜你喜欢
    • 2012-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-27
    相关资源
    最近更新 更多