正则表达式分裂成重叠的字符串

时间:2010-03-13 03:01:55

标签: java regex split overlapping-matches

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

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"

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

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

5 个答案:

答案 0 :(得分:5)

我不认为split()可以实现这一点,但find()使用Matcher m = Pattern.compile("(?=(\\d\\d)).").matcher("12345"); while (m.find()) { System.out.println(m.group(1)); } 非常简单。只需使用内置捕获组的前瞻:

"(?=(\\d\\d))"

很多人都没有意识到,在比赛之后,可以像在任何其他捕捉中一样引用在前瞻或后方内部捕获的文本。在这种情况下,它特别违反直觉,其中捕获是“整体”匹配的超集。

事实上,即使正则表达式整体上没有任何匹配也是有效的。从上面的正则表达式(split())中删除点,您将得到相同的结果。这是因为,只要一个成功的匹配不消耗任何字符,正则表达式引擎会在尝试再次匹配之前自动向前移动一个位置,以防止无限循环。

此技术没有{{1}}等效,但至少不是Java。虽然您可以拆分外观和其他零宽度断言,但是无法使相同的字符出现在多个生成的子字符串中。

答案 1 :(得分:4)

使用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]" 
    } 
} 

EDIT1

match():为什么到目前为止还没有人像BONUS_REGEX那样编写正则表达式,Matcher位于BONUS_REGEX内,这将继续查找前一组中的下一个组结束(即没有重叠),因为选择在前一组开始之后 - 也就是说,没有明确地重新指定开始搜索位置(上图)。 "(.\\G.|^..)"的合适人选\G,但不幸的是,Match - 中间锚定技巧不适用于Java的 perl -e 'while ("12345"=~/(^..|.\G.)/g) { print "$1\n" }' 12 23 34 45 (但是有效)在Perl中很好):

split()

INSERT_REGEX_HERE:对于(?<=..)(?=..),一个好的候选人应该是split(分割点是零宽度位置,我右边有两个字符,左边有两个字符) ,但是,再次,因为[12, 3, 45]无关紧要,你最终得到了split()(这很近,但没有雪茄。)

EDIT2

为了好玩,你可以通过先加倍非边界字符来欺骗Pattern.compile("((?<=.).(?=.))").matcher("12345").replaceAll("$1#$1").split("#") 做你想做的事情(这里你需要一个保留的字符值来分开):

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

我们可以聪明并且通过利用零宽度前瞻断言(不同于后视)可以具有无限长度的事实来消除对保留字符的需要;因此,我们可以从doubled字符串的末尾(与其开头至少两个字符)分开所有偶数个字符的点,产生与上面相同的结果:

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}}

答案 2 :(得分:1)

拆分将一个字符串切成多个部分,但这不允许重叠。你需要使用一个循环来获得重叠的部分。

答案 3 :(得分:1)

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

在Perl中,这有效:

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

find-and-replace表达式表示:匹配前两个相邻的数字,并用两个数字中的第二个替换字符串中的数字。

答案 4 :(得分:0)

替代方案,使用与Perl的普通匹配。应该在前瞻性的任何地方工作。这里不需要循环。

 $_ = '12345';
 @list = /(?=(..))./g;
 print "@list";

 # Output:
 # 12 23 34 45

但是,正如之前发布的那样,如果\ G技巧有效,那么这个更好:

 $_ = '12345';
 @list = /^..|.\G./g;
 print "@list";

 # Output:
 # 12 23 34 45

修改:抱歉,没有看到所有已经发布。