每隔第 i 个和第 j 个字符进行拆分

4
我需要在每个第i和第j个字符处拆分字符串,其中i和j可以根据输入参数更改。例如,如果我有一个输入:
String s = "1234567890abcdef";
int i = 2;
int j = 3;

我希望我的输出为一个数组:
[12, 345, 67, 890, ab, cde, f]

我找到了一个紧凑的正则表达式来在每个第n个字符处分割字符串。当n=3时,例如使用"(?<=\\G...)""(?<=\\G.{3})"

String s = "1234567890abcdef";
int n = 3;
System.out.println(Arrays.toString(s.split("(?<=\\G.{"+n+"})")));

//output: [123, 456, 789, 0ab, cde, f]

如何修改上述正则表达式以交替在每个第2第3个字符处分割?
像“(?<=\\G.{2})(?<=\\G.{3})”这样的天真链式表达式无效。

3
你必须使用正则表达式吗?如果不使用正则表达式,这会更容易 - 只需编写一个简单的循环,每次去掉2个字符,然后是3个字符,然后是2个字符,以此类推。如果一定要用正则表达式,只需去掉5个字符,然后知道每组5个字符可以分成2个和3个字符。 - davidalayachew
如果您需要一个伪代码示例,让我知道它会是什么样子。 - davidalayachew
1
@davidalayachew 我也尝试使用循环和子字符串方法,但由于最后一个值可能比2短,仍然遇到了数组越界的问题。即使是伪代码,我也很想看看你的方法。 - wannaBeDev
看起来底下已经有一些非常有用的解决方案了 - 我鼓励你选择其中之一。 - davidalayachew
4个回答

5

我认为你无法使用split()完成这个任务,因为每一个匹配都应该意识到之前匹配的模式。

如果你不想手动迭代字符串的字符,你可以使用下面这个方法:

Matcher m = Pattern.compile("(.{0,2})(.{0,3})").matcher("1234567890abcdef");
List<String> list = new ArrayList<>();
while (m.find()) {
  for (int i = 1; i <= 2; i++) {
    if (!m.group(i).isEmpty()) {
      list.add(m.group(i));
    }
  }
}
System.out.println(list);  // prints [12, 345, 67, 890, ab, cde, f]

不错的解决方案!我建议将list.add(m.group(i));放在一个{}范围内,以使其更易读。 - Most Noble Rabbit
这非常完美,正是我所需要的,如果我的模式是XX XXX XXXXX,它还可以扩展。 - wannaBeDev
2
是的,在这种情况下,请记得将“i <= 2”替换为“i <= 3”(或使用“m.groupCount()”)。 - logi-kal
在for循环中,最好写成: for (int i = 1; i <= m.groupCount(); i++) - Tal Glik
@TalGlik 这正是我在上一条评论中所说的 :-) - logi-kal

2

通过迭代字符的O(n)解决方案:

private static List<String> splitByPattern(String str, List<Integer> pattern) {
    int currentPatternIndex = 0;
    int iterationsTillNextSplit = pattern.get(currentPatternIndex);
    StringBuilder stringBuilder = new StringBuilder();
    List<String> strs = new ArrayList<>();

    for (char c : str.toCharArray()) {
        if (iterationsTillNextSplit == 0) { // Time to split
            strs.add(stringBuilder.toString());
            stringBuilder = new StringBuilder();
            iterationsTillNextSplit = pattern.get(++currentPatternIndex % pattern.size());
        }

        stringBuilder.append(c);
        iterationsTillNextSplit--;
    }

    strs.add(stringBuilder.toString());

    return strs;
}

使用方法:

System.out.println(splitByPattern("1234567890abcdef", Arrays.asList(2, 3)));

输出:

[12, 345, 67, 890, ab, cde, f]

2
有一种有点hack的方法可以使用正则表达式进行split(),但正如@horcrux所提到的:
每个匹配都应该知道之前匹配的模式。
你需要:
a) 首先在每个i + j位置插入一个锚点来添加“不太可能”的字符或字符串(例如换行符),以便进一步回溯引用:
s = s.replaceAll("(.{5})", "$1\n");

a) 所以你的字符串会转换成 12345\n67890\nabcde\nf b) 现在你可以通过查找分割字符串。
String[] result = s.split("(?<=\\G.{2})(?=.{3}\n)|\n");

在搜索零长度匹配时,您可以在左侧具有 i 个字符( (?<=\G.{2})),并跟随以您的“特殊”模式结尾的 j 个字符,或者如果未找到,则仅匹配您的“特殊”模式。
这样可以在位置 i 处或“特殊”模式的匹配处交替拆分。

using hash # as special pattern

完整的一行代码(仅供教育用途):
System.out.println(Arrays.toString(s.replaceAll("(.{"+(i+j)+"})", "$1#").split("(?<=\\G.{"+i+"})(?=.{"+j+"}#)|#")));

2
如果选择的策略是“在第五个字符后拆分,然后在第二个字符后拆分”,我会选择一个功能性的解决方案:System.out.println(Arrays.stream(s.split("(?<=\\G.{"+(i+j)+"})")).map(t -> t.split("(?<=^.{"+i+"})")).flatMap(Arrays::stream).collect(Collectors.toList())); - logi-kal

2

这里有另一个简单的解决方案,它不使用正则表达式:

String s = "1234567890abcdef";
int strLen = s.length();
List<String> list = new ArrayList<>();
for (int lastIndex = 0; lastIndex < strLen;) {
    int numChars = list.size() % 2 == 0 ? 2 : 3; // this alternates substrings of length 2 and 3
    if (strLen - lastIndex < numChars)
        list.add(s.substring(lastIndex));
    else
        list.add(s.substring(lastIndex, lastIndex+numChars));
    lastIndex += numChars;
}
System.out.println(list);  // prints [12, 345, 67, 890, ab, cde, f]

今天因为你学到了一些东西,谢谢你。 - wannaBeDev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接