4

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

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"

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

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

4

6 回答 6

5

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一个好的候选人本来是(?<=..)(?=..)(分割点是零宽度位置,我的右边有两个字符,左边有两个字符),但同样,因为split你最终得到的没有重叠[12, 3, 45](这是接近的,但没有雪茄。)

编辑2

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

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

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

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" 
于 2010-03-13T03:47:43.347 回答
5

我不认为这是可能的split(),但find()它很简单。只需在内部使用带有捕获组的前瞻:

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

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

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

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

于 2010-03-13T09:10:55.817 回答
1

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

于 2010-03-13T03:26:55.670 回答
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

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

于 2010-03-13T03:29:42.000 回答
0

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

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

 # Output:
 # 12 23 34 45

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

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

 # Output:
 # 12 23 34 45

编辑:对不起,没有看到所有这些都已经发布了。

于 2010-03-15T18:05:45.413 回答
0

String#split正如其他答案已经说明的那样,不可能创建重叠匹配。但是,可以在它之前添加一个正则表达式替换来准备字符串,然后使用拆分来创建常规对:

"12345".replaceAll(".(?=(.).)","$0$1")
       .split("(?<=\\G..)")

.replaceAll(".(?=(.).)","$0$1")意志转变"12345"为. "12233445"它基本上将每个123子字符串替换为1223,然后每个2342334(注意它是重叠的)等等。换句话说,它将复制每个字符,除了第一个和最后一个字符。

.(?=(.).)  # Replace-regex:
.          #  A single character
 (?=    )  #  followed by (using a positive lookahead):
     . .   #   two more characters
    ( )    #   of which the first is saved in capture group 1

$0$1       # Replacement-regex:
$0         #  The entire match, which is the character itself since everything
           #  else was inside a lookahead
  $1       #  followed by capture group 1

之后,.split("(?<=\\G..)")将这个新字符串分成对:

(?<=\G..) # Split-regex:
(?<=    ) #  A positive lookbehind:
    \G    #   Matching the end of the previous match
          #   (or the start of the string initially)
      ..  #   followed by two characters

更多信息.split("(?<=\\G..)")可以在这里找到。

在线尝试。

于 2022-02-23T14:33:05.130 回答