【问题标题】:Regex split into overlapping strings正则表达式拆分为重叠字符串
【发布时间】:2011-01-27 02:01:04
【问题描述】:

我正在探索正则表达式的力量,所以我只是想知道这样的事情是否可行:

public class StringSplit {
    public static void main(String args[]) {
        System.out.println(
            java.util.Arrays.deepToString(
                "12345".split(INSERT_REGEX_HERE)
            )
        ); // prints "[12, 23, 34, 45]"
    }
}

如果可能,那么只需提供正则表达式(并预先说明它的工作原理)。

如果它仅适用于 Java 以外的某些正则表达式风格,那么也可以随意提供。

如果不可能,请解释原因。


奖励问题

同样的问题,但使用find() 循环而不是split

    Matcher m = Pattern.compile(BONUS_REGEX).matcher("12345");
    while (m.find()) {
        System.out.println(m.group());
    } // prints "12", "23", "34", "45"

请注意,与其说我有具体的任务来完成某种方式,不如说我想了解正则表达式。我不需要做我想做的代码;我想要正则表达式,如果它们存在,我可以在上面的代码中使用它来完成任务(或者其他风格的正则表达式,可以将代码“直接翻译”成另一种语言)。

如果它们不存在,我想要一个很好的可靠解释原因。

【问题讨论】:

    标签: java regex split overlapping-matches


    【解决方案1】:

    我认为split() 无法做到这一点,但find() 则非常简单。只需在内部使用带有捕获组的前瞻:

    Matcher m = Pattern.compile("(?=(\\d\\d)).").matcher("12345");
    while (m.find())
    {
      System.out.println(m.group(1));
    }
    

    许多人没有意识到在前瞻或后瞻中捕获的文本可以在匹配后引用,就像任何其他捕获一样。在这种情况下尤其违反直觉,捕获是“整个”匹配的超集。

    事实上,即使整个正则表达式不匹配,它也可以工作。从上面的正则表达式中删除点 ("(?=(\\d\\d))"),你会得到相同的结果。这是因为,只要成功匹配不消耗任何字符,正则表达式引擎就会在尝试再次匹配之前自动向前移动一个位置,以防止无限循环。

    不过,这种技术没有 split() 等价物,至少在 Java 中没有。尽管您可以拆分环视和其他零宽度断言,但没有办法让相同的字符出现在多个结果子字符串中。

    【讨论】:

    • +1!太棒了! “很多人没有意识到……”
    • 我的眼睛肯定是睁开的。我一直认为 group(0) 必然是所有其他组的超字符串。
    • +1 指出捕获 inside 零宽度断言不是零宽度(尽管捕获外部是零宽度。):)
    【解决方案2】:

    这种使用Matcher.find 而不是split 的繁重实现也可以工作,尽管当您必须为这样一个微不足道的任务编写for 循环时,您不妨完全放弃正则表达式并使用子字符串(对于类似的编码复杂度减去 CPU 周期):

    import java.util.*;
    import java.util.regex.*;
    
    public class StringSplit { 
        public static void main(String args[]) { 
            ArrayList<String> result = new ArrayList<String>();
            for (Matcher m = Pattern.compile("..").matcher("12345"); m.find(result.isEmpty() ? 0 : m.start() + 1); result.add(m.group()));
            System.out.println( result.toString() ); // prints "[12, 23, 34, 45]" 
        } 
    } 
    

    编辑1

    match():到目前为止,没有人能够编造像您的BONUS_REGEX 这样的正则表达式的原因在于Matcher,它将继续寻找前一组结束的下一组(即没有重叠) ,与前一组开始的位置相反——也就是说,没有明确地重新指定开始搜索位置(上图)。 BONUS_REGEX 的一个很好的候选者应该是 "(.\\G.|^..)",但不幸的是,\G-anchor-in-the-middle 技巧不适用于 Java 的 Match(但在 Perl 中工作得很好):

     perl -e 'while ("12345"=~/(^..|.\G.)/g) { print "$1\n" }'
     12
     23
     34
     45
    

    split(): 至于INSERT_REGEX_HERE,一个好的候选人应该是(?&lt;=..)(?=..)(分割点是零宽度位置,我的右边有两个字符,左边有两个字符),但同样,因为@ 987654336@ 认为没有重叠,你最终会得到[12, 3, 45](很接近,但没有雪茄。)

    EDIT2

    为了好玩,你可以欺骗split() 做你想做的事,首先将非边界字符加倍(这里你需要一个保留字符值来拆分):

    Pattern.compile("((?<=.).(?=.))").matcher("12345").replaceAll("$1#$1").split("#")
    

    我们可以聪明地利用零宽度的look-ahead断言(与look-behind不同)可以有无限长度的事实来消除对保留字符的需求;因此,我们可以围绕距离加倍字符串末尾偶数个字符的所有点(并且距离其开头至少两个字符)进行拆分,产生与上述相同的结果:

    Pattern.compile("((?<=.).(?=.))").matcher("12345").replaceAll("$1$1").split("(?<=..)(?=(..)*$)")
    

    或者以类似的方式欺骗​​match()(但不需要保留字符值):

    Matcher m = Pattern.compile("..").matcher(
      Pattern.compile("((?<=.).(?=.))").matcher("12345").replaceAll("$1$1")
    );
    while (m.find()) { 
        System.out.println(m.group()); 
    } // prints "12", "23", "34", "45" 
    

    【讨论】:

    • +1 提醒我注意 \G-in-the-middle 技巧,即使它确实仅在 Perl 中有效。
    【解决方案3】:

    Split 将字符串切成多段,但不允许重叠。您需要使用循环来获取重叠部分。

    【讨论】:

      【解决方案4】:

      我认为你不能用 split() 做到这一点,因为它会丢弃与正则表达式匹配的部分。

      在 Perl 中这是有效的:

      my $string = '12345';
      my @array = ();
      while ( $string =~ s/(\d(\d))/$2/ ) {
          push(@array, $1);
      }
      print join(" ", @array);
      # prints: 12 23 34 45
      

      查找和替换表达式说:匹配前两个相邻的数字,并在字符串中用两个数字中的第二个替换它们。

      【讨论】:

        【解决方案5】:

        另一种选择,使用 Perl 的普通匹配。应该在前瞻的任何地方工作。而且这里不需要循环。

         $_ = '12345';
         @list = /(?=(..))./g;
         print "@list";
        
         # Output:
         # 12 23 34 45
        

        但是,如前所述,如果 \G 技巧有效,这个会更好:

         $_ = '12345';
         @list = /^..|.\G./g;
         print "@list";
        
         # Output:
         # 12 23 34 45
        

        编辑:抱歉,没有看到所有已经发布。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-19
          • 2011-06-18
          • 2011-10-16
          • 1970-01-01
          相关资源
          最近更新 更多