使用正则表达式分割不同长度的字符串

我不知道使用正则表达式是否可行。 我只是想问一下有人知道答案。

我有一个string ="hellohowareyou??" 。 我需要像这样分开它

[h, el, loh, owar, eyou?, ?]

完成分割使得第一串具有长度1,第二长度2等等。 最后一个字符串将包含剩余的字符。 我可以使用像这样的函数在没有正则表达式的情况下轻松完成。

 public ArrayList splitString(String s) { int cnt=0,i; ArrayList sList=new ArrayList(); for(i=0;i+cnt<s.length();i=i+cnt) { cnt++; sList.add(s.substring(i,i+cnt)); } sList.add(s.substring(i,s.length())); return sList; } 

我只是好奇是否可以使用正则表达式完成这样的事情。

以下代码段生成执行该作业的模式( 请参阅在ideone.com上运行 ):

 // splits at indices that are triangular numbers class TriangularSplitter { // asserts that the prefix of the string matches pattern static String assertPrefix(String pattern) { return "(?<=(?=^pattern).*)".replace("pattern", pattern); } // asserts that the entirety of the string matches pattern static String assertEntirety(String pattern) { return "(?<=(?=^pattern$).*)".replace("pattern", pattern); } // repeats an assertion as many times as there are dots behind current position static String forEachDotBehind(String assertion) { return "(?<=^(?:.assertion)*?)".replace("assertion", assertion); } public static void main(String[] args) { final String TRIANGULAR_SPLITTER = "(?x) (?<=^.) | measure (?=(.*)) check" .replace("measure", assertPrefix("(?: notGyet . +NBefore +1After)*")) .replace("notGyet", assertPrefix("(?! \\1 \\G)")) .replace("+NBefore", forEachDotBehind(assertPrefix("(\\1? .)"))) .replace("+1After", assertPrefix(".* \\G (\\2?+ .)")) .replace("check", assertEntirety("\\1 \\G \\2 . \\3")) ; String text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; System.out.println( java.util.Arrays.toString(text.split(TRIANGULAR_SPLITTER)) ); // [a, bc, def, ghij, klmno, pqrstu, vwxyzAB, CDEFGHIJ, KLMNOPQRS, TUVWXYZ] } } 

请注意,此解决方案使用我的正则表达式文章系列中已涵盖的技术。 这里唯一的新事物是\G和前向引用。

参考

这是使用的基本正则表达式构造的简要描述:

  • (?x)是嵌入式标志修饰符,用于启用自由间隔模式,其中忽略未转义的空格(并且#可用于注释)。
  • ^$是开始和结束的锚点\G前一个匹配锚点。
  • | 表示交替 (即“或”)。
  • ? 因为重复说明符表示可选 (即零或一)。 作为例如.*?的重复量词.*? 它表示* (即零或多个)重复是不情愿的 /非贪婪的。
  • (…)用于分组(?:…)是非捕获组。 捕获组保存匹配的字符串; 除其他外,它允许匹配后退/前进/嵌套引用(例如\1 )。
  • (?=…)是一个积极的前瞻 ; 它看起来有权断言给定模式的匹配。 (?<=…)是一个积极的看法; 它向左看。
  • (?!…)是一个负面的前瞻; 它看起来有权断言模式没有匹配。

相关问题

  • [nested-reference]系列中的文章:
    • 这个正则表达式如何找到三角形数字?
    • 我们怎样才能将^ nb ^ n与Java正则表达式匹配?
    • 这个Java正则表达式如何检测回文?
  • 正则表达式(?<=#)[^#]+(?=#)工作?

说明

该模式匹配零宽度断言。 使用相当复杂的算法来断言当前位置是三角形数字 。 主要有两种选择:

  • (?<=^.) ,即我们可以向后看并看到一个点后的字符串的开头
    • 这与索引1匹配,并且是该过程其余部分的关键起点
  • 否则,我们measure重建最后一次匹配的方式(使用\G作为参考点),将测量结果存储在“之前” \G和“之后” \G捕获组中。 然后,我们check当前位置是否是测量规定的位置,以找出下一个匹配应该在哪里。

因此,第一种选择是简单的“基本情况”,第二种选择设置如何在此之后进行所有后续匹配。 Java没有自定义命名的组,但这里是3个捕获组的语义:

  • \1捕获字符串“before” \G
  • \2捕获一些字符串“后” \G
  • 如果\1的长度为例如1 + 2 + 3 + ... + k ,那么\2的长度需要为k
    • 因此\2 . 长度为k + 1 ,应该是我们split的下一个部分!
  • \3捕获我们当前位置右侧的字符串
    • 因此,当我们可以在\1 \G \2 . \3assertEntirety \1 \G \2 . \3 \1 \G \2 . \3 ,我们匹配并设置新的\G

您可以使用数学归纳法来严格certificate此算法的正确性。

为了帮助说明这是如何工作的,让我们通过一个例子来解决。 让我们把abcdefghijklm作为输入,并说我们已经部分拆分了[a, bc, def]

  \G we now need to match here! ↓ ↓ abcdefghijklmn \____1____/ \_2_/ . \__3__/ <--- \1 G \2 . \3 L=1+2+3 L=3 

请记住\G标记最后一场比赛的结束,它出现在三角数字索引处。 如果\G1 + 2 + 3 + ... + k ,那么下一个匹配需要是\G之后的k + 1个位置是三角数索引。

因此,在我们的例子中,给定\G是我们刚刚拆分def ,我们测量了k = 3 ,下一个匹配将按预期分割出ghij

要根据上面的规范构建\1\2 ,我们基本上做了一个“循环”:只要它不是notGyet ,我们按如下方式计算k

  • +NBefore ,即我们将一个扩展为1 forEachDotBehind
  • +1After ,即我们只用一个扩展\2

请注意, notGyet包含对组1的前向引用,后者在模式中稍后定义。 基本上我们做循环直到\1 “命中” \G


结论

不用说,这种特殊的解决方案具有糟糕的性能。 正则表达式引擎只会记住最后一次匹配的位置(使用\G ),并忘记HOW (即在下次尝试匹配时重置所有捕获组)。 然后我们的模式必须重建HOW (传统解决方案中不必要的步骤,其中变量不那么“健忘”),通过一次附加一个字符(即O(N^2) )来精心构建字符串。 每个简单的测量都是线性的而不是恒定的时间(因为它是作为字符串匹配完成的,其中长度是一个因子),并且最重要的是我们做了许多冗余的测量(即延长一个,我们需要首先重新匹配我们已经拥有的东西)。

可能有许多“更好”的正则表达式解决方案。 尽管如此,这个特定解决方案的复杂性和低效率应该正确地表明正则表达式不是为这种模式匹配而设计的。

也就是说,出于学习目的,这是一个绝对精彩的问题,因为在研究和制定解决方案方面有丰富的知识。 希望这个特殊的解决方案及其解释具有指导意义。

正则表达式的目的是识别模式。 在这里,您不会搜索模式,而是搜索长度。 所以正则表达式是不合适的

它是可能的,但不是单个正则表达式:使用正则表达式查找前n个字符,您使用:“^(。{ n })。*”

因此,您可以使用该正则表达式搜索第一个字符。 然后,您创建一个子字符串,然后搜索下两个字符。 等等。

就像@splash所说的那样,它会使代码更加复杂和低效,因为你使用正则表达式来达到目的之外的东西。

 String a = "hellohowareyou??"; int i = 1; while(true) { if(i >= a.length()) { System.out.println(a); break; } else { String b = a.substring(i++); String[] out = a.split(Pattern.quote(b) + "$"); System.out.println(out[0]); a = b; if(b.isEmpty()) break; } }