Как разбить строку, но также сохранить разделители? - PullRequest
209 голосов
/ 05 февраля 2010

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

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

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

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

  • Text1
  • Text2
  • Text3
  • Text4

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

  • Text1
  • DelimiterA
  • Text2
  • DelimiterC
  • Text3
  • DelimiterB
  • Text4

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

Ответы [ 23 ]

319 голосов
/ 05 февраля 2010

Вы можете использовать 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]

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

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

Надеюсь, это поможет.

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

static public final String <b>WITH_DELIMITER = "((?<=%1$s)|(?=%1$s))"</b>;
...
public void someMethod() {
...
final String[] aEach = "a;b;c;d".<b>split(String.format(WITH_DELIMITER, ";"))</b>;
...
}
...

Это немного помогает. : -D

72 голосов
/ 17 мая 2010

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

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][!!]"
    }
}

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

Похожие вопросы

Смотри также

27 голосов
/ 05 февраля 2010

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

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

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

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

21 голосов
/ 09 ноября 2008
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

10 голосов
/ 11 ноября 2008

Я пришел сюда поздно, но возвращаясь к первоначальному вопросу, почему бы просто не использовать lookarounds?

Pattern p = Pattern.compile("(?<=\\w)(?=\\W)|(?<=\\W)(?=\\w)");
System.out.println(Arrays.toString(p.split("'ab','cd','eg'")));
System.out.println(Arrays.toString(p.split("boo:and:foo")));

выход:

[', ab, ',', cd, ',', eg, ']
[boo, :, and, :, foo]

РЕДАКТИРОВАТЬ: Вы видите выше, что появляется в командной строке, когда я запускаю этот код, но теперь я вижу, что это немного сбивает с толку. Трудно отследить, какие запятые являются частью результата, а какие были добавлены Arrays.toString(). Подсветка синтаксиса SO тоже не помогает. В надежде заставить подсветку работать с мной, а не против меня, вот как эти массивы будут выглядеть так: я объявил их в исходном коде:

{ "'", "ab", "','", "cd", "','", "eg", "'" }
{ "boo", ":", "and", ":", "foo" }

Надеюсь, это легче читать. Спасибо за внимание, @ finnw.

9 голосов
/ 13 апреля 2011

Я знаю, что это очень-очень старый вопрос, и ответ также был принят. Но все же я хотел бы представить очень простой ответ на оригинальный вопрос. Рассмотрим этот код:

String str = "Hello-World:How\nAre You&doing";
inputs = str.split("(?!^)\\b");
for (int i=0; i<inputs.length; i++) {
   System.out.println("a[" + i + "] = \"" + inputs[i] + '"');
}

ВЫВОД:

a[0] = "Hello"
a[1] = "-"
a[2] = "World"
a[3] = ":"
a[4] = "How"
a[5] = "
"
a[6] = "Are"
a[7] = " "
a[8] = "You"
a[9] = "&"
a[10] = "doing"

Я просто использую границу слова \b для разделения слов , за исключением , когда это начало текста.

8 голосов
/ 11 ноября 2008

Я посмотрел на приведенные выше ответы и, честно говоря, ни один из них я не нашел удовлетворительным. То, что вы хотите сделать, по сути, имитировать функциональность разделения Perl. Почему в Java это не разрешено и где-то есть метод join (), я не знаю, но я отвлекся. Тебе даже не нужен класс для этого. Это просто функция. Запустите этот пример программы:

Некоторые из более ранних ответов имеют чрезмерную проверку нуля, на которую я недавно написал ответ на вопрос:

https://stackoverflow.com/users/18393/cletus

В любом случае, код:

public class Split {
    public static List<String> split(String s, String pattern) {
        assert s != null;
        assert pattern != null;
        return split(s, Pattern.compile(pattern));
    }

    public static List<String> split(String s, Pattern pattern) {
        assert s != null;
        assert pattern != null;
        Matcher m = pattern.matcher(s);
        List<String> ret = new ArrayList<String>();
        int start = 0;
        while (m.find()) {
            ret.add(s.substring(start, m.start()));
            ret.add(m.group());
            start = m.end();
        }
        ret.add(start >= s.length() ? "" : s.substring(start));
        return ret;
    }

    private static void testSplit(String s, String pattern) {
        System.out.printf("Splitting '%s' with pattern '%s'%n", s, pattern);
        List<String> tokens = split(s, pattern);
        System.out.printf("Found %d matches%n", tokens.size());
        int i = 0;
        for (String token : tokens) {
            System.out.printf("  %d/%d: '%s'%n", ++i, tokens.size(), token);
        }
        System.out.println();
    }

    public static void main(String args[]) {
        testSplit("abcdefghij", "z"); // "abcdefghij"
        testSplit("abcdefghij", "f"); // "abcde", "f", "ghi"
        testSplit("abcdefghij", "j"); // "abcdefghi", "j", ""
        testSplit("abcdefghij", "a"); // "", "a", "bcdefghij"
        testSplit("abcdefghij", "[bdfh]"); // "a", "b", "c", "d", "e", "f", "g", "h", "ij"
    }
}
6 голосов
/ 09 ноября 2008

Мне нравится идея StringTokenizer, потому что это Enumerable.
Но он также устарел и заменяется на String.split, который возвращает скучную строку [] (и не включает разделители).

Итак, я реализовал StringTokenizerEx, который является Iterable, и для разбиения строки требуется истинное регулярное выражение.

Истинное регулярное выражение означает, что это не «последовательность символов», повторяемая для формирования разделителя:
«o» будет соответствовать только «o» и разделит «ooo» на три разделителя с двумя пустыми строками внутри:

[o], '', [o], '', [o]

Но регулярное выражение o + вернет ожидаемый результат при разбиении "aooob"

[], 'a', [ooo], 'b', []

Чтобы использовать этот StringTokenizerEx:

final StringTokenizerEx aStringTokenizerEx = new StringTokenizerEx("boo:and:foo", "o+");
final String firstDelimiter = aStringTokenizerEx.getDelimiter();
for(String aString: aStringTokenizerEx )
{
    // uses the split String detected and memorized in 'aString'
    final nextDelimiter = aStringTokenizerEx.getDelimiter();
}

Код этого класса доступен по адресу DZone Snippets .

Как обычно для ответа code-challenge (один автономный класс с включенными контрольными примерами), copy-paste * (в a 'src / test ') и запустите его . Его метод main () иллюстрирует различные варианты использования.


Примечание: (редактирование в конце 2009 г.)

Статья Заключительные мысли: Java Puzzler: Splitting Hairs делает хорошую работу, объясняя причудливое поведение в String.split().
Джош Блох даже прокомментировал в ответ на эту статью:

Да, это боль. FWIW, это было сделано по очень веской причине: совместимость с Perl.
Парнем, который это сделал, является Майк «сумасшедший» Макклоски, который сейчас работает с нами в Google. Майк позаботился о том, чтобы регулярные выражения Java проходили практически все тесты регулярных выражений Perl 30K (и работали быстрее).

Google общая библиотека Guava содержит также разделитель, который:

  • проще в использовании
  • поддерживается Google (а не вами)

Так что, возможно, стоит проверить. Из их исходной грубой документации (pdf) :

У JDK есть это:

String[] pieces = "foo.bar".split("\\.");

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

Мини-головоломка: ", a ,, b,". Split (",") возвращает ...

(a) "", "a", "", "b", ""
(b) null, "a", null, "b", null
(c) "a", null, "b"
(d) "a", "b"
(e) None of the above

Ответ: (e) Ничего из вышеперечисленного.

",a,,b,".split(",")
returns
"", "a", "", "b"

Пропускаются только конечные тары! (Кто знает обходной путь для предотвращения пропуска? Это забавно ...)

В любом случае наш Splitter просто более гибок: поведение по умолчанию упрощено:

Splitter.on(',').split(" foo, ,bar, quux,")
--> [" foo", " ", "bar", " quux", ""]

Если вам нужны дополнительные функции, попросите их!

Splitter.on(',')
.trimResults()
.omitEmptyStrings()
.split(" foo, ,bar, quux,")
--> ["foo", "bar", "quux"]

Порядок методов конфигурации не имеет значения - во время разделения происходит обрезка перед проверкой на пустые значения.

4 голосов
/ 20 мая 2017

Вот простая чистая реализация, которая соответствует Pattern#split и работает с шаблонами переменной длины, которые не поддерживаются, и их проще использовать.Это похоже на решение , предоставляемое @ cletus.

public static String[] split(CharSequence input, String pattern) {
    return split(input, Pattern.compile(pattern));
}

public static String[] split(CharSequence input, Pattern pattern) {
    Matcher matcher = pattern.matcher(input);
    int start = 0;
    List<String> result = new ArrayList<>();
    while (matcher.find()) {
        result.add(input.subSequence(start, matcher.start()).toString());
        result.add(matcher.group());
        start = matcher.end();
    }
    if (start != input.length()) result.add(input.subSequence(start, input.length()).toString());
    return result.toArray(new String[0]);
}

Я не делаю здесь нулевые проверки, Pattern#split нет, почему я долженкак if в конце, но это требуется для согласованности с Pattern#split.В противном случае я бы безоговорочно добавил, что привело бы к пустой строке в качестве последнего элемента результата, если входная строка заканчивается шаблоном.

Я преобразую в String [] для согласованности с Pattern#split, я использую new String[0] вместо new String[result.size()], см. здесь , почему ..

Вот мои тесты:

@Test
public void splitsVariableLengthPattern() {
    String[] result = Split.split("/foo/$bar/bas", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/", "$bar", "/bas" }, result);
}

@Test
public void splitsEndingWithPattern() {
    String[] result = Split.split("/foo/$bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/", "$bar" }, result);
}

@Test
public void splitsStartingWithPattern() {
    String[] result = Split.split("$foo/bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "", "$foo", "/bar" }, result);
}

@Test
public void splitsNoMatchesPattern() {
    String[] result = Split.split("/foo/bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/bar" }, result);
}
4 голосов
/ 10 марта 2015

Передайте 3-й тур как "true". Он также вернет разделители.

StringTokenizer(String str, String delimiters, true);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...