Як розділити рядок, але також зберегти роздільники?


243

У мене є рядковий рядок, який розмежований набором різних роздільників:

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

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

Іншими словами, ось що я отримую:

  • Text1
  • Text2
  • Text3
  • Text4

Це я хочу

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

Чи є який-небудь спосіб JDK розділити рядок за допомогою відбивача роздільника, але також зберегти роздільники?


Подумайте, де ви хочете тримати роздільники? Поряд зі словами чи окремо? У першому випадку ви додаєте їх до попереднього чи наступного слова? У другому випадку моя відповідь - те, що вам потрібно ...
PhiLho

Щойно реалізований клас, який повинен допомогти вам досягти того, що ви шукаєте. Дивіться нижче
VonC

Відповіді:


366

Можна використовувати 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]

Останнє - те, що ти хочеш.

((?<=;)|(?=;))дорівнює порожньому символу до ;або після ;.

Сподіваюсь, це допомагає.

EDIT Зауваження Фабіяна Стіга щодо "Читання" є дійсним. Читання - це завжди проблема RegEx. Одне, що я хочу допомогти полегшити це - створити змінну, ім’я якої представляє те, що робить регулярний вираз, і використовувати формат Java String для цього. Подобається це:

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, ";"));
...
}
...

Це трохи допомагає. :-D


2
Дуже хороша! Тут ми знову можемо побачити силу регулярних виразів !!
Джордж

1
Приємно бачити, що є спосіб це зробити за допомогою String # split, хоча я б хотів, щоб був спосіб включити роздільники, як це було для StringTokenizer - split(";", true)було б набагато читабельніше, ніж split("((?<=;)|(?=;))").
Fabian Steeg

3
Це має бути так: String.format(WITH_DELIMITER, ";");як формат - це статичний метод.
john16384

8
Одне з ускладнень, з якими я щойно стикався, - це роздільники різної довжини (скажімо [\\s,]+), які ви хочете повністю відповідати Необхідні регулярні вирази стають ще довшими, оскільки вам потрібен додатковий негативний погляд {вперед, позаду} s, щоб уникнути їх узгодження в середині, наприклад. (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
Michał Politowski

3
що робити, якщо я хочу розділити два роздільники? скажімо ';' або "."
чудо-дох

78

Ви хочете скористатися lookarounds і розділити їх на відповідність нульовій ширині. Ось кілька прикладів:

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

І так, це потрійно вкладене твердження там, в останньому шаблоні.

Пов'язані питання

Дивитися також


1
Зауважте, що це буде працювати лише для відносно простих виразів; Я отримав групу "Оглядова група не має очевидної максимальної довжини", намагаючись використати це за допомогою регулярного вираження, що представляє всі реальні числа.
daveagp

2
FYI: Об'єднане з stackoverflow.com/questions/275768 / ...
Shog9

30

Дуже наївним рішенням, яке не передбачає регулярного виразу, було б виконати заміну рядка на вашому роздільнику по рядках (якщо вважати кому для роздільника):

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

Де ви можете замінити tilda (~) відповідним унікальним роздільником.

Тоді, якщо ви зробите розбиття на новому роздільнику, то я вірю, що ви отримаєте бажаний результат.


24
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


Wahoo ... Дякую за участь! Цікавий підхід. Я не впевнений, що це може допомогти послідовно (при цьому іноді є роздільник, іноді немає), але +1 для зусиль. Однак вам все одно належним чином вирішувати граничні випадки (порожні або нульові значення)
VonC

Запрошую вас належним чином підкріпити цей клас, ретельно документувати його, зробити пропуск із Findbugs та checkstyle, а потім опублікувати його на веб-сайті фрагментів (щоб уникнути захаращення цієї сторінки тоннами коду)
VonC

Ви виграли виклик! Помилка ... вітаємо! Як відомо, з теми-коду-виклику не було б спеціальних пунктів чи значків для цього ... (зітхання): stackoverflow.com/questions/172184 . Але дякую за цей внесок.
VonC

@VonC Більшість випадків, якщо nullаргумент NPE є правильним шляхом. Мовчазне поводження з ним призводить до появи помилок пізніше.
maaartinus

@maaartinus Я згоден, але, безумовно, є випадки, коли ви хочете передати більш зручне повідомлення, ніж просто NPE, правда?
VonC

11

Я потрапив сюди пізно, але повертаючись до початкового питання, чому б просто не використати 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]

EDIT: Що ви бачите вище, це те, що з’являється в командному рядку, коли я запускаю цей код, але тепер я бачу, що це трохи заплутано. Важко відслідковувати, які коми є частиною результату та які були додані Arrays.toString(). Підсвічування синтаксису SO також не допомагає. Сподіваючись отримати виділення для роботи зі мною, а не проти мене, ось, як виглядатимуть ці масиви, я оголосив їх у вихідному коді:

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

Я сподіваюся, що це легше читати. Дякую за голову, @finnw.


Я знаю, що це виглядає неправильно - мені це виглядало неправильно, коли я повернувся до цього лише зараз, через рік після цього факту. Вибір вибірки був погано обраний; Я відредагую пост і спробую роз’яснити речі.
Алан Мур


10

Я знаю, що це дуже-дуже старе питання, і відповідь також була прийнята. Але все ж я хотів би надати дуже просту відповідь на оригінальне запитання. Розглянемо цей код:

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для розмежування слів, за винятком випадків, коли це текст починається.


1
+1 Найкраща відповідь для мене. але це не працює для буквено-цифрових роздільників у буквено-цифровій струні
Казимир та Іполіт

@CasimiretHippolyte: Дякуємо за ваш внесок. Чи можете ви надати зразок вводу там, де він не працював.
anubhava

2
наприклад , це не працює abcdefз в deякості роздільника, але ви можете вирішити цю проблему з допомогою(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
Казимир і Іполита

1
Зверніть увагу на перше твердження, щоб уникнути порожнього рядка в результаті, коли рядок закінчується роздільником, тобто(?!^|$)
Казимир та Іполіт

1
FYI: Об'єднане з stackoverflow.com/questions/275768 / ...
Shog9

9

Я переглянув вищезазначені відповіді і, чесно, жоден з них не вважаю задовільним. Що ви хочете зробити, це по суті імітує функціональність Perl split. Чому 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"
    }
}

Мене бентежить: у Java є метод split (), який моделюється на Perl, але набагато менш потужний. Проблема тут полягає в тому, що спліт () Java не дає можливості повернути роздільники, чого ви можете досягти в Perl, включивши регулярний вираз у захоплення дужок.
Алан Мур


7

Мені подобається ідея StringTokenizer, оскільки вона безліч.
Але він також є застарілим і замінює String.split, який повертає нудний рядок [] (і не включає роздільники).

Тож я реалізував StringTokenizerEx, який є Ітерабельним, і який потребує справжнього регулярного вираження, щоб розділити рядок.

Справжнє регулярне вираження означає, що це не послідовність символів, повторювана для формування роздільника:
'o' буде відповідати лише 'o', а розділити 'ooo' на три роздільники, з двома порожніми рядками всередині:

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

Але regexp 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 .

Як зазвичай для відповіді на виклик коду (один автономний клас із включеними тестовими кейсами), скопіюйте його та вставте в каталог 'src / test' та запустіть його . Основний () метод ілюструє різні звичаї.


Примітка: (кінець 2009 року редагувати)

Стаття Остаточні думки: Java Puzzler: Розщеплення волосся робить добру роботу, пояснюючи химерну поведінку в String.split().
Джош Блох навіть прокоментував у відповідь на цю статтю:

Так, це біль. FWIW, це було зроблено з дуже вагомої причини: сумісність з Perl.
Хлопець, який це зробив, - це Майк "божевільний" Макклоскі, який зараз працює з нами в Google. Майк переконався, що регулярні вирази Java пройшли практично кожен тест регулярних виразів 30K Perl (і бігали швидше).

Загальна бібліотека 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

Відповідь: (д) Нічого із зазначеного.

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

Пропускаються лише задні пустоти! (Хто знає рішення, щоб запобігти пропуску? Це весело ...)

У будь-якому випадку наш сплітер просто гнучкіший: поведінка за замовчуванням спрощена:

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

Якщо ви хочете додаткові функції, попросіть їх!

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

Порядок методів конфігурації не має значення - під час розщеплення відбувається обрізка, перш ніж перевірити порожнечі.



6

Передайте 3-й аргумент як "справжній". Він також поверне роздільники.

StringTokenizer(String str, String delimiters, true);

4

Ось проста чиста реалізація, яка відповідає 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);
}

2

Я також розміщу свої робочі версії (перша дійсно схожа на Markus).

public static String[] splitIncludeDelimeter(String regex, String text){
    List<String> list = new LinkedList<>();
    Matcher matcher = Pattern.compile(regex).matcher(text);

    int now, old = 0;
    while(matcher.find()){
        now = matcher.end();
        list.add(text.substring(old, now));
        old = now;
    }

    if(list.size() == 0)
        return new String[]{text};

    //adding rest of a text as last element
    String finalElement = text.substring(old);
    list.add(finalElement);

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

І ось друге рішення і його круглість на 50% швидше, ніж перше:

public static String[] splitIncludeDelimeter2(String regex, String text){
    List<String> list = new LinkedList<>();
    Matcher matcher = Pattern.compile(regex).matcher(text);

    StringBuffer stringBuffer = new StringBuffer();
    while(matcher.find()){
        matcher.appendReplacement(stringBuffer, matcher.group());
        list.add(stringBuffer.toString());
        stringBuffer.setLength(0); //clear buffer
    }

    matcher.appendTail(stringBuffer); ///dodajemy reszte  ciagu
    list.add(stringBuffer.toString());

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

2

Ще одне кандидатське рішення з використанням регулярного вираження. Зберігає порядок маркерів, правильно відповідає декільком жетонам одного типу підряд. Недоліком є ​​те, що регулярний вираз неприємний.

package javaapplication2;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JavaApplication2 {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        String num = "58.5+variable-+98*78/96+a/78.7-3443*12-3";

        // Terrifying regex:
        //  (a)|(b)|(c) match a or b or c
        // where
        //   (a) is one or more digits optionally followed by a decimal point
        //       followed by one or more digits: (\d+(\.\d+)?)
        //   (b) is one of the set + * / - occurring once: ([+*/-])
        //   (c) is a sequence of one or more lowercase latin letter: ([a-z]+)
        Pattern tokenPattern = Pattern.compile("(\\d+(\\.\\d+)?)|([+*/-])|([a-z]+)");
        Matcher tokenMatcher = tokenPattern.matcher(num);

        List<String> tokens = new ArrayList<>();

        while (!tokenMatcher.hitEnd()) {
            if (tokenMatcher.find()) {
                tokens.add(tokenMatcher.group());
            } else {
                // report error
                break;
            }
        }

        System.out.println(tokens);
    }
}

Вибірка зразка:

[58.5, +, variable, -, +, 98, *, 78, /, 96, +, a, /, 78.7, -, 3443, *, 12, -, 3]

1

Я не знаю про існуючу функцію в Java API, яка робить це (що не означає, що він не існує), але ось моя власна реалізація (один або кілька роздільників буде повернено як один маркер; якщо ви хочете кожен роздільник буде повернутий як окремий маркер, знадобиться трохи адаптації):

static String[] splitWithDelimiters(String s) {
    if (s == null || s.length() == 0) {
        return new String[0];
    }
    LinkedList<String> result = new LinkedList<String>();
    StringBuilder sb = null;
    boolean wasLetterOrDigit = !Character.isLetterOrDigit(s.charAt(0));
    for (char c : s.toCharArray()) {
        if (Character.isLetterOrDigit(c) ^ wasLetterOrDigit) {
            if (sb != null) {
                result.add(sb.toString());
            }
            sb = new StringBuilder();
            wasLetterOrDigit = !wasLetterOrDigit;
        }
        sb.append(c);
    }
    result.add(sb.toString());
    return result.toArray(new String[0]);
}


1

Я пропоную використовувати Pattern and Matcher, які майже напевно досягнуть того, що ви хочете. Ваш регулярний вираз повинен бути дещо складнішим, ніж те, що ви використовуєте в String.split.


+1, Це правильний шлях. StringTokenizer видасть роздільники, якщо розмістити їх у групах захоплення, але він, по суті, застарів. Використовувати lookahead з split () хакі є з причин, які викладені в коментарях прийнятої відповіді - головним чином, що це стає безладом, коли є більше одного роздільника. Але ви можете мати справжній токенізатор в декількох рядках із "Шаблон" та "Матчер".
johncip

1

Я не думаю, що це можливо String#split, але ви можете використовувати знак a StringTokenizer, хоча це не дозволить визначити ваш роздільник як регулярний вираз, а лише як клас одноцифрових символів:

new StringTokenizer("Hello, world. Hi!", ",.!", true); // true for returnDelims

Там я не можу визначити регулярний вираз, щоб вказати свої роздільники.
Даніель Ріковський

1
Однак StringTokenizer дозволяє використовувати лише розділові знаки, що містять один символ.
Майкл Боргвардт

1

Якщо ви можете собі дозволити, скористайтеся методом заміни Java (ціль CharSequence, заміна CharSequence) та заповніть інший роздільник, на який потрібно розділити. Приклад: Я хочу розділити рядок "boo: і: foo" і тримати ':' у своїй правому рядку.

String str = "boo:and:foo";
str = str.replace(":","newdelimiter:");
String[] tokens = str.split("newdelimiter");

Важлива примітка. Це працює лише в тому випадку, якщо у вашому рядку більше немає «нового делімітера»! Таким чином, це не є загальним рішенням. Але якщо ви знаєте CharSequence, який ви можете бути впевнені, що він ніколи не з’явиться в String, це дуже просте рішення.



0

Швидка відповідь: використовуйте не фізичні межі, такі як \ b, щоб розділити. Я спробую і поекспериментувати, щоб перевірити, чи працює він (використовували це в PHP та JS).

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

Інший спосіб - зробити власний розкол, захопивши роздільник (припустимо, він є змінним) і додавши його згодом до результату.

Мій швидкий тест:

String str = "'ab','cd','eg'";
String[] stra = str.split("\\b");
for (String s : stra) System.out.print(s + "|");
System.out.println();

Результат:

'|ab|','|cd|','|eg|'|

Трохи забагато ... :-)



0

Tweaked Pattern.split () для включення відповідного шаблону до списку

Додано

// add match to the list
        matchList.add(input.subSequence(start, end).toString());

Повне джерело

public static String[] inclusiveSplit(String input, String re, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<String>();

    Pattern pattern = Pattern.compile(re);
    Matcher m = pattern.matcher(input);

    // Add segments before each match found
    while (m.find()) {
        int end = m.end();
        if (!matchLimited || matchList.size() < limit - 1) {
            int start = m.start();
            String match = input.subSequence(index, start).toString();
            matchList.add(match);
            // add match to the list
            matchList.add(input.subSequence(start, end).toString());
            index = end;
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index, input.length())
                    .toString();
            matchList.add(match);
            index = end;
        }
    }

    // If no match was found, return this
    if (index == 0)
        return new String[] { input.toString() };

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).toString());

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize - 1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);
}


0

Ось обтяжлива версія, заснована на деякому коді вище, на випадок, якщо це допоможе. Це все-таки коротко. Умовно включають голову і хвіст (якщо вони не порожні). Остання частина - демонстраційний / тестовий випадок.

List splitWithTokens(str, pat) {
    def tokens=[]
    def lastMatch=0
    def m = str=~pat
    while (m.find()) {
      if (m.start() > 0) tokens << str[lastMatch..<m.start()]
      tokens << m.group()
      lastMatch=m.end()
    }
    if (lastMatch < str.length()) tokens << str[lastMatch..<str.length()]
    tokens
}

[['<html><head><title>this is the title</title></head>',/<[^>]+>/],
 ['before<html><head><title>this is the title</title></head>after',/<[^>]+>/]
].each { 
   println splitWithTokens(*it)
}


0

Надзвичайно наївне та неефективне рішення, яке все-таки працює. Використовуйте розділити два рази на рядок, а потім з'єднати два масиви.

String temp[]=str.split("\\W");
String temp2[]=str.split("\\w||\\s");
int i=0;
for(String string:temp)
System.out.println(string);
String temp3[]=new String[temp.length-1];
for(String string:temp2)
{
        System.out.println(string);
        if((string.equals("")!=true)&&(string.equals("\\s")!=true))
        {
                temp3[i]=string;
                i++;
        }
//      System.out.println(temp.length);
//      System.out.println(temp2.length);
}
System.out.println(temp3.length);
String[] temp4=new String[temp.length+temp3.length];
int j=0;
for(i=0;i<temp.length;i++)
{
        temp4[j]=temp[i];
        j=j+2;
}
j=1;
for(i=0;i<temp3.length;i++)
{
        temp4[j]=temp3[i];
        j+=2;
}
for(String s:temp4)
System.out.println(s);

0
    String expression = "((A+B)*C-D)*E";
    expression = expression.replaceAll("\\+", "~+~");
    expression = expression.replaceAll("\\*", "~*~");
    expression = expression.replaceAll("-", "~-~");
    expression = expression.replaceAll("/+", "~/~");
    expression = expression.replaceAll("\\(", "~(~"); //also you can use [(] instead of \\(
    expression = expression.replaceAll("\\)", "~)~"); //also you can use [)] instead of \\)
    expression = expression.replaceAll("~~", "~");
    if(expression.startsWith("~")) {
        expression = expression.substring(1);
    }

    String[] expressionArray = expression.split("~");
    System.out.println(Arrays.toString(expressionArray));

З регулярним виразом це буде:Scanner scanner = new Scanner("((A+B)*C-D)*E"); scanner.useDelimiter("((?<=[\\+\\*\\-\\/\\(\\)])|(?=[\\+\\*\\-\\/\\(\\)]))"); while (scanner.hasNext()) { System.out.print(" " + scanner.next()); }
Цолак Барсегян

0

Одна з тонкощів у цьому питанні стосується "провідного роздільника" питання: якщо у вас буде комбінований масив жетонів і роздільників, ви повинні знати, починається він з лексеми чи відмежувача. Звичайно, можна просто припустити, що провідний делім слід відмовитися, але це здається невиправданим припущенням. Можливо, ви також хочете дізнатися, чи є у вас трейлінг делім чи ні. Це відповідно встановлює два булеві прапори.

Написано Groovy, але версія Java повинна бути досить очевидною:

            String tokenRegex = /[\p{L}\p{N}]+/ // a String in Groovy, Unicode alphanumeric
            def finder = phraseForTokenising =~ tokenRegex
            // NB in Groovy the variable 'finder' is then of class java.util.regex.Matcher
            def finderIt = finder.iterator() // extra method added to Matcher by Groovy magic
            int start = 0
            boolean leadingDelim, trailingDelim
            def combinedTokensAndDelims = [] // create an array in Groovy

            while( finderIt.hasNext() )
            {
                def token = finderIt.next()
                int finderStart = finder.start()
                String delim = phraseForTokenising[ start  .. finderStart - 1 ]
                // Groovy: above gets slice of String/array
                if( start == 0 ) leadingDelim = finderStart != 0
                if( start > 0 || leadingDelim ) combinedTokensAndDelims << delim
                combinedTokensAndDelims << token // add element to end of array
                start = finder.end()
            }
            // start == 0 indicates no tokens found
            if( start > 0 ) {
                // finish by seeing whether there is a trailing delim
                trailingDelim = start < phraseForTokenising.length()
                if( trailingDelim ) combinedTokensAndDelims << phraseForTokenising[ start .. -1 ]

                println( "leading delim? $leadingDelim, trailing delim? $trailingDelim, combined array:\n $combinedTokensAndDelims" )

            }

-2

Я не знаю Java занадто добре, але якщо ви не можете знайти метод Split, який це робить, я пропоную вам зробити свій власний.

string[] mySplit(string s,string delimiter)
{
    string[] result = s.Split(delimiter);
    for(int i=0;i<result.Length-1;i++)
    {
        result[i] += delimiter; //this one would add the delimiter to each items end except the last item, 
                    //you can modify it however you want
    }
}
string[] res = mySplit(myString,myDelimiter);

Це не надто елегантно, але все одно.


але що робити, якщо у вас є кілька роздільників підряд?
Кіп

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.