Вопрос-ответ

How to split a string, but also keep the delimiters?

Как разделить строку, но при этом сохранить разделители?

У меня есть многострочная строка, которая разделена набором разных разделителей:

(Text1)(DelimiterA)(Text2)(DelimiterC)(Text3)(DelimiterB)(Text4)

Я могу разделить эту строку на ее части, используя String.split, но, похоже, я не могу получить фактическую строку, которая соответствовала регулярному выражению с разделителем.

Другими словами, это то, что я получаю:


  • Text1

  • Text2

  • Text3

  • Text4

Это то, что я хочу


  • Text1

  • DelimiterA

  • Text2

  • DelimiterC

  • Text3

  • DelimiterB

  • Text4

Есть ли какой-либо способ JDK разделить строку с помощью регулярного выражения-разделителя, но при этом сохранить разделители?

Переведено автоматически
Ответ 1

Вы можете использовать lookahead и lookbehind , которые являются функциями регулярных выражений.

System.out.println(Arrays.toString("a;b;c;d".split("(?<=;)")));
System.out.println(Arrays.toString("a;b;c;d".split("(?=;)")));
System.out.println(Arrays.toString("a;b;c;d".split("((?<=;)|(?=;))")));

И вы получите:

[a;, b;, c;, d]
[a, ;b, ;c, ;d]
[a, ;, b, ;, c, ;, d]

Последнее - это то, что вы хотите.

((?<=;)|(?=;)) равно выделению пустого символа до ; или после ;.

РЕДАКТИРОВАТЬ: Комментарии Фабиана Стига по удобочитаемости верны. С регулярными выражениями всегда проблема с удобочитаемостью. Одна вещь, которую я делаю, чтобы сделать регулярные выражения более удобочитаемыми, - это создать переменную, имя которой отражает то, что делает регулярное выражение. Вы даже можете поместить заполнители (например, %1$s) и использовать Java String.format для замены заполнителей фактической строкой, которую вам нужно использовать; например:

static public final String WITH_DELIMITER = "((?<=%1$s)|(?=%1$s))";

public void someMethod() {
final String[] aEach = "a;b;c;d".split(String.format(WITH_DELIMITER, ";"));
...
}
Ответ 2

Вы хотите использовать поисковые системы и разделять по совпадениям нулевой ширины. Вот несколько примеров.:

public class SplitNDump {
static void dump(String[] arr) {
for (String s : arr) {
System.out.format("[%s]", s);
}
System.out.println();
}
public static void main(String[] args) {
dump("1,234,567,890".split(","));
// "[1][234][567][890]"
dump("1,234,567,890".split("(?=,)"));
// "[1][,234][,567][,890]"
dump("1,234,567,890".split("(?<=,)"));
// "[1,][234,][567,][890]"
dump("1,234,567,890".split("(?<=,)|(?=,)"));
// "[1][,][234][,][567][,][890]"

dump(":a:bb::c:".split("(?=:)|(?<=:)"));
// "[][:][a][:][bb][:][:][c][:]"
dump(":a:bb::c:".split("(?=(?!^):)|(?<=:)"));
// "[:][a][:][bb][:][:][c][:]"
dump(":::a::::b b::c:".split("(?=(?!^):)(?<!:)|(?!:)(?<=:)"));
// "[:::][a][::::][b b][::][c][:]"
dump("a,bb:::c d..e".split("(?!^)\\b"));
// "[a][,][bb][:::][c][ ][d][..][e]"

dump("ArrayIndexOutOfBoundsException".split("(?<=[a-z])(?=[A-Z])"));
// "[Array][Index][Out][Of][Bounds][Exception]"
dump("1234567890".split("(?<=\\G.{4})"));
// "[1234][5678][90]"

// Split at the end of each run of letter
dump("Boooyaaaah! Yippieeee!!".split("(?<=(?=(.)\\1(?!\\1))..)"));
// "[Booo][yaaaa][h! Yipp][ieeee][!!]"
}
}

И да, в последнем шаблоне это утверждение с тройным вложением.

Вопросы по теме

Смотрите также

Ответ 3

Очень наивным решением, которое не требует регулярного выражения, было бы выполнить замену строки в вашем разделителе в строках (предполагая, что разделитель заменен запятой):

string.replace(FullString, "," , "~,~")

Где вы можете заменить тильду (~) на соответствующий уникальный разделитель.

Тогда, если вы выполните разделение с вашим новым разделителем, я верю, что вы получите желаемый результат.

Ответ 4
import java.util.regex.*;
import java.util.LinkedList;

public class Splitter {
private static final Pattern DEFAULT_PATTERN = Pattern.compile("\\s+");

private Pattern pattern;
private boolean keep_delimiters;

public Splitter(Pattern pattern, boolean keep_delimiters) {
this.pattern = pattern;
this.keep_delimiters = keep_delimiters;
}
public Splitter(String pattern, boolean keep_delimiters) {
this(Pattern.compile(pattern==null?"":pattern), keep_delimiters);
}
public Splitter(Pattern pattern) { this(pattern, true); }
public Splitter(String pattern) { this(pattern, true); }
public Splitter(boolean keep_delimiters) { this(DEFAULT_PATTERN, keep_delimiters); }
public Splitter() { this(DEFAULT_PATTERN); }

public String[] split(String text) {
if (text == null) {
text = "";
}

int last_match = 0;
LinkedList<String> splitted = new LinkedList<String>();

Matcher m = this.pattern.matcher(text);

while (m.find()) {

splitted.add(text.substring(last_match,m.start()));

if (this.keep_delimiters) {
splitted.add(m.group());
}

last_match = m.end();
}

splitted.add(text.substring(last_match));

return splitted.toArray(new String[splitted.size()]);
}

public static void main(String[] argv) {
if (argv.length != 2) {
System.err.println("Syntax: java Splitter <pattern> <text>");
return;
}

Pattern pattern = null;
try {
pattern = Pattern.compile(argv[0]);
}
catch (PatternSyntaxException e) {
System.err.println(e);
return;
}

Splitter splitter = new Splitter(pattern);

String text = argv[1];
int counter = 1;
for (String part : splitter.split(text)) {
System.out.printf("Part %d: \"%s\"\n", counter++, part);
}
}
}

/*
Example:
> java Splitter "\W+" "Hello World!"
Part 1: "Hello"
Part 2: " "
Part 3: "World"
Part 4: "!"
Part 5: ""
*/

Мне не очень нравится другой способ, когда вы получаете пустой элемент спереди и сзади. Разделитель обычно не находится ни в начале, ни в конце строки, таким образом, вы чаще всего теряете два хороших слота массива.

Правка: Исправлены предельные случаи. Прокомментированный источник с тестовыми примерами можно найти здесь: http://snippets.dzone.com/posts/show/6453

java regex