Jak podzielić ciąg, ale także zachować ograniczniki?


243

Mam ciąg wielowierszowy, który jest ograniczony przez zestaw różnych ograniczników:

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

Mogę podzielić ten ciąg na jego części, używając String.split, ale wydaje się, że nie mogę uzyskać rzeczywistego ciągu, który pasowałby do wyrażenia regularnego ogranicznika.

Innymi słowy, otrzymuję to:

  • Text1
  • Text2
  • Text3
  • Text4

To jest to czego chcę

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

Czy istnieje jakiś sposób JDK na podzielenie łańcucha przy użyciu wyrażenia regularnego ogranicznika, ale także zachowanie ograniczników?


Zastanów się, gdzie chcesz zatrzymać ograniczniki? Wraz ze słowami czy osobno? Czy w pierwszym przypadku dołączasz je do poprzedzającego lub następującego słowa? W drugim przypadku potrzebuję mojej odpowiedzi ...
PhiLho,

Właśnie wdrożyłem klasę, która powinna pomóc Ci osiągnąć to, czego szukasz. Zobacz poniżej
VonC

Odpowiedzi:


366

Możesz użyć Lookahead i Lookbehind. Lubię to:

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("((?<=;)|(?=;))")));

I dostaniesz:

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

Ostatni jest tym, czego chcesz.

((?<=;)|(?=;))oznacza wybranie pustego znaku przed ;lub po ;.

Mam nadzieję że to pomoże.

EDYCJA Komentarze Fabian Steeg na temat czytelności są prawidłowe. Czytelność zawsze stanowi problem dla RegEx. Jedną rzeczą, którą robię, aby to złagodzić, jest stworzenie zmiennej, której nazwa reprezentuje to, co robi wyrażenie regularne, i użycie do tego formatu Java String. Lubię to:

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

To trochę pomaga. :-RE


2
Bardzo dobrze! Tutaj znów możemy zobaczyć siłę wyrażeń regularnych !!
George

1
Miło jest widzieć, że istnieje sposób, aby to zrobić za pomocą funkcji String # split, choć chciałbym, aby istniał sposób uwzględnienia ograniczników, tak jak w przypadku StringTokenizer - split(";", true)byłby o wiele bardziej czytelny niż split("((?<=;)|(?=;))").
Fabian Steeg

3
Powinno to być: String.format(WITH_DELIMITER, ";");ponieważ format jest metodą statyczną.
john16384

8
Jedną z komplikacji, które właśnie napotkałem, są ograniczniki o zmiennej długości (powiedzmy [\\s,]+), które chcesz całkowicie dopasować. Wymagane wyrażenia regularne stają się jeszcze dłuższe, ponieważ potrzebujesz dodatkowego negatywnego spojrzenia, aby uniknąć dopasowania ich w środku, np. (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
Michał Politowski

3
co jeśli chcę podzielić na dwa separatory? powiedzmy ';' lub „.”
cud-doh

78

Chcesz użyć lookaroundów i podzielić na dopasowania o zerowej szerokości. Oto kilka przykładów:

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

I tak, to potrójnie zagnieżdżone twierdzenie w ostatnim wzorze.

Powiązane pytania

Zobacz też


1
Zauważ, że będzie to działać tylko w przypadku stosunkowo prostych wyrażeń; Dostałem „Grupa z tyłu nie ma oczywistej maksymalnej długości”, próbując użyć tego z wyrażeniem regularnym reprezentującym wszystkie liczby rzeczywiste.
daveagp

2
FYI:
Scalono

30

Bardzo naiwnym rozwiązaniem, które nie wymaga wyrażenia regularnego, byłoby wykonanie zamiany ciągu na separatorze zgodnie z (zakładając przecinek dla separatora):

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

Gdzie możesz zastąpić tilda (~) odpowiednim unikalnym ogranicznikiem.

Więc jeśli zrobisz podział na swoim nowym ograniczniku, to wierzę, że uzyskasz pożądany wynik.


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: ""
*/

Naprawdę nie podoba mi się inny sposób, w którym dostajesz pusty element z przodu iz tyłu. Separator zwykle nie znajduje się na początku ani na końcu łańcucha, dlatego najczęściej marnujesz dwa dobre miejsca na tablicę.

Edycja: Naprawiono przypadki limitów. Skomentowane źródło z przypadkami testowymi można znaleźć tutaj: http://snippets.dzone.com/posts/show/6453


Wahoo ... Dziękujemy za udział! Ciekawe podejście Nie jestem pewien, czy może to być konsekwentnie pomocne (z tym, że czasami jest ogranicznik, czasem nie ma), ale +1 za wysiłek. Jednak nadal musisz odpowiednio rozwiązać przypadki limitów (wartości puste lub zerowe)
VonC

Zapraszam do odpowiedniego wzmocnienia tej klasy, dogłębnego jej udokumentowania, przejścia z findbugs i checkstyle, a następnie opublikowania jej na stronie z fragmentami kodu (aby uniknąć zaśmiecania tej strony tonami kodu)
VonC

Wygrałeś wyzwanie! Errr ... gratulacje! Jak wiecie, z wątku kodowego wyzwanie nie byłoby dla tego specjalnych punktów ani odznak ... (westchnienie): stackoverflow.com/questions/172184 . Ale dziękuję za ten wkład.
VonC

@VonC Przez większość czasu nullpoprawnym argumentem jest podanie NPE na argument. Cicha obsługa prowadzi do późniejszych błędów.
maaartinus

@maaartinus Zgadzam się, ale na pewno są przypadki, w których chcesz wysłać wiadomość bardziej przyjazną dla użytkownika niż tylko NPE, prawda?
VCC

11

Przybyłem tu późno, ale wracając do pierwotnego pytania, dlaczego nie użyć po prostu spojrzeń?

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")));

wynik:

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

EDYCJA: To, co widzisz powyżej, pojawia się w wierszu poleceń, gdy uruchamiam ten kod, ale teraz widzę, że jest to trochę mylące. Trudno jest śledzić, które przecinki są częścią wyniku, a które zostały dodane Arrays.toString(). Podświetlanie składni SO też nie pomaga. W nadziei, że wyróżnienie będzie działać ze mną zamiast ze mną, oto jak wyglądałyby te tablice, ogłaszając je w kodzie źródłowym:

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

Mam nadzieję, że łatwiej to odczytać. Dzięki za heads-up, @finnw.


Wiem, że wygląda to źle - wyglądało to źle, kiedy wróciłem do niego właśnie teraz, rok po fakcie. Próbka została źle wybrana; Zmienię post i postaram się wyjaśnić.
Alan Moore,

FYI:
Scalono

10

Wiem, że to bardzo stare pytanie i odpowiedź została zaakceptowana. Ale nadal chciałbym udzielić bardzo prostej odpowiedzi na pierwotne pytanie. Rozważ ten kod:

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] + '"');
}

WYNIK:

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"

Po prostu używam granicy słów \bdo rozgraniczenia słów, chyba że jest to początek tekstu.


1
+1 Najlepsza odpowiedź dla mnie. ale nie działa na separatory alfanumeryczne w łańcuchu alfanumerycznym
Casimir et Hippolyte

@CasimiretHippolyte: Dziękujemy za opinię. Czy możesz podać przykładowe dane wejściowe tam, gdzie to nie zadziałało.
anubhava

2
na przykład nie działa to abcdefz deogranicznikiem, ale problem można rozwiązać za pomocą(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
Casimir et Hippolyte

1
Zwróć uwagę na pierwsze twierdzenie, aby uniknąć pustego łańcucha w wyniku, gdy łańcuch kończy się na separatorze, tj.(?!^|$)
Casimir et Hippolyte

1
FYI:
Scalono

9

Spojrzałem na powyższe odpowiedzi i szczerze mówiąc, żadna z nich nie jest dla mnie zadowalająca. To, co chcesz zrobić, to w zasadzie naśladować funkcjonalność podziału Perla. Dlaczego Java nie pozwala na to i gdzieś ma metodę join (), jest poza mną, ale dygresuję. Tak naprawdę nie potrzebujesz nawet klasy. To tylko funkcja. Uruchom ten przykładowy program:

Niektóre z wcześniejszych odpowiedzi mają nadmierne sprawdzanie wartości zerowej, co niedawno napisałem w odpowiedzi na pytanie tutaj:

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

W każdym razie kod:

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"
    }
}

Jestem zdezorientowany: Java ma metodę split (), która jest wzorowana na Perlu, ale znacznie mniej wydajna. Problem polega na tym, że funkcja split () w Javie nie daje możliwości zwrócenia ograniczników, co można osiągnąć w Perlu, umieszczając wyrażenie regularne w przechwytywaniu nawiasów.
Alan Moore,

FYI:
Scalono

7

Podoba mi się pomysł StringTokenizer, ponieważ jest on wyliczalny.
Ale jest również przestarzały i zastąpiony przez String.split, które zwracają nudny String [] (i nie zawiera ograniczników).

Zaimplementowałem więc StringTokenizerEx, który jest iterowalny i który wymaga prawdziwego wyrażenia regularnego do podzielenia łańcucha.

Prawdziwe wyrażenie regularne oznacza, że ​​nie jest to „sekwencja znaków” powtarzana w celu utworzenia separatora:
„o” będzie pasować tylko do „o” i podzieli „ooo” na trzy separatory z dwoma pustymi ciągami wewnątrz:

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

Ale wyrażenie regularne o + zwróci oczekiwany wynik po podzieleniu „aooob”

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

Aby użyć tego 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();
}

Kod tej klasy jest dostępny na stronie DZone Snippets .

Jak zwykle w przypadku odpowiedzi na wyzwanie kodu (jedna samodzielna klasa z dołączonymi przypadkami testowymi), skopiuj ją i wklej (w katalogu 'src / test') i uruchom . Jego metoda main () ilustruje różne zastosowania.


Uwaga: (edycja z końca 2009 r.)

Artykuł Final Thoughts: Java Puzzler: Splitting Hairs dobrze się spisuje wyjaśniając dziwne zachowanie String.split().
Josh Bloch skomentował nawet w odpowiedzi na ten artykuł:

Tak, to jest ból. FWIW, zrobiono to z bardzo dobrego powodu: kompatybilności z Perlem.
Facet, który to zrobił, to Mike „madbot” McCloskey, który teraz współpracuje z nami w Google. Mike upewnił się, że wyrażenia regularne Javy przeszły praktycznie każdy z testów wyrażeń regularnych 30 K Perl (i działały szybciej).

Guava z wspólnej biblioteki Google zawiera również Splitter, który jest:

  • prostszy w użyciu
  • utrzymywany przez Google (a nie przez ciebie)

Więc może warto to sprawdzić. Z ich wstępnej wstępnej dokumentacji (pdf) :

JDK ma to:

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

Można to wykorzystać, jeśli chcesz dokładnie to, co robi: - wyrażenie regularne - wynik jako tablica - sposób obsługi pustych elementów

Mini-puzzler: „, a ,, b”. Split („,”) zwraca ...

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

Odpowiedź: (e) Żadne z powyższych.

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

Pomijane są tylko puste opróżnienia! (Kto zna obejście, aby zapobiec pomijaniu? To zabawne ...)

W każdym razie nasz Splitter jest po prostu bardziej elastyczny: Domyślne zachowanie jest uproszczone:

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

Jeśli chcesz dodatkowych funkcji, poproś o nie!

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

Kolejność metod konfiguracji nie ma znaczenia - podczas podziału przycinanie odbywa się przed sprawdzeniem pustych miejsc.


FYI:
Scalono

6

Przekaż trzeci instrument jako „prawdziwy”. Zwróci również ograniczniki.

StringTokenizer(String str, String delimiters, true);

4

Oto prosta czysta implementacja, która jest spójna z Pattern#splitwzorcami o zmiennej długości i działa ze wzorcami o zmiennej długości, które nie są w stanie obsłużyć i są łatwiejsze w użyciu. Jest podobny do rozwiązania dostarczonego przez @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]);
}

Nie robię tutaj kontroli zerowej, Pattern#splitnie, dlaczego powinienem. Nie podoba mi się ifna końcu, ale jest to wymagane dla spójności z Pattern#split. W przeciwnym razie dołączałbym bezwarunkowo, w wyniku czego pusty łańcuch byłby ostatnim elementem wyniku, jeśli łańcuch wejściowy kończy się wzorem.

Przekształcam na String [] w celu zachowania spójności Pattern#split, używam new String[0]raczej niż new String[result.size()], zobacz tutaj, dlaczego.

Oto moje testy:

@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

Zamieszczę również moje działające wersje (pierwsza jest naprawdę podobna do Markusa).

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()]);
}

A oto drugie rozwiązanie i jego runda o 50% szybsza niż pierwsze:

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

Inne rozwiązanie kandydujące z użyciem wyrażenia regularnego. Zachowuje kolejność tokenów, poprawnie dopasowuje wiele tokenów tego samego typu w rzędzie. Minusem jest to, że regex jest trochę nieprzyjemny.

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);
    }
}

Przykładowe dane wyjściowe:

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

1

Nie wiem o istniejącej funkcji w API Java, która to robi (co nie znaczy, że nie istnieje), ale oto moja własna implementacja (jeden lub więcej separatorów zostanie zwróconych jako pojedynczy token; jeśli chcesz każdy separator, który zostanie zwrócony jako osobny token, będzie wymagał trochę adaptacji):

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]);
}

FYI:
Scalono

1

Sugeruję użycie Wzorca i Matchera, który prawie na pewno osiągnie to, czego chcesz. Twoje wyrażenie regularne będzie musiało być nieco bardziej skomplikowane niż to, czego używasz w String.split.


+1, to jest właściwa droga. StringTokenizer wyświetli ograniczniki, jeśli umieścisz je w grupach przechwytywania, ale jest to zasadniczo przestarzałe. Używanie lookahead z split () jest hackerskie z powodów opisanych w komentarzach do zaakceptowanej odpowiedzi - głównie dlatego, że staje się bałaganem, gdy występuje więcej niż jeden separator. Ale możesz mieć prawdziwy tokenizer w kilku liniach z Pattern and Matcher.
johncip

1

Nie sądzę, aby było to możliwe String#split, ale możesz użyć StringTokenizerznaku, chociaż nie pozwoli ci to zdefiniować separatora jako wyrażenia regularnego, ale tylko jako klasę znaków jednocyfrowych:

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

Nie mogę zdefiniować wyrażenia regularnego w celu określenia moich ograniczników.
Daniel Rikowski

1
StringTokenizer dopuszcza jednak tylko ograniczniki jednoznakowe.
Michael Borgwardt,

1

Jeśli możesz sobie na to pozwolić, użyj metody Java zastępowania (cel CharSequence, zamiana CharSequence) i wypełnij inny separator, aby podzielić. Przykład: Chcę podzielić ciąg „boo: and: foo” i zachować ciąg „:” na prawym ciągu.

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

Ważna uwaga: Działa to tylko wtedy, gdy w łańcuchu nie ma już „newdelimiter”! Dlatego nie jest to ogólne rozwiązanie. Ale jeśli znasz CharSequence, możesz być pewien, że nigdy nie pojawi się on w ciągu, jest to bardzo proste rozwiązanie.


FYI:
Scalono

0

Szybka odpowiedź: użyj podziałów niefizycznych, takich jak \ b, aby podzielić. Spróbuję eksperymentować, aby sprawdzić, czy to działa (użyłem tego w PHP i JS).

Jest to możliwe i rodzaj pracy, ale może się rozdzielić. W rzeczywistości zależy to od ciągu, który chcesz podzielić, i wyniku, którego potrzebujesz. Podaj więcej szczegółów, pomożemy ci lepiej.

Innym sposobem jest wykonanie własnego podziału, przechwycenie separatora (zakładając, że jest zmienny) i dodanie go później do wyniku.

Mój szybki test:

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

Wynik:

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

Trochę za dużo... :-)


FYI:
Scalono

0

Poprawiono Pattern.split (), aby dołączyć dopasowany wzór do listy

Dodany

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

Pełne źródło

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);
}

FYI:
Scalono

0

Oto świetna wersja oparta na powyższym kodzie, na wypadek gdyby pomógł. W każdym razie jest krótki. Warunkowo obejmuje głowę i ogon (jeśli nie są puste). Ostatnia część to przypadek demonstracyjny / testowy.

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)
}

FYI:
Scalono

0

Niezwykle naiwne i nieefektywne rozwiązanie, które mimo wszystko działa, użyj podwójnego podziału na łańcuchu, a następnie połącz dwa tablice

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));

Z Scanner scanner = new Scanner("((A+B)*C-D)*E"); scanner.useDelimiter("((?<=[\\+\\*\\-\\/\\(\\)])|(?=[\\+\\*\\-\\/\\(\\)]))"); while (scanner.hasNext()) { System.out.print(" " + scanner.next()); }
wyrażeniem regularnym

0

Jedna z subtelności w tym pytaniu dotyczy pytania „wiodącego separatora”: jeśli masz mieć kombinację tablic tokenów i separatorów, musisz wiedzieć, czy zaczyna się od tokena, czy separatora. Można oczywiście założyć, że ogranicznik wiodący należy odrzucić, ale wydaje się to nieuzasadnionym założeniem. Możesz także chcieć wiedzieć, czy masz końcową granicę, czy nie. Ustawia to odpowiednio dwie flagi boolowskie.

Napisane w Groovy, ale wersja Java powinna być dość oczywista:

            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

Nie znam zbyt dobrze Javy, ale jeśli nie możesz znaleźć metody Split, która to robi, proponuję po prostu stworzyć własną.

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);

Nie jest zbyt elegancki, ale da radę.


ale co, jeśli masz wiele ograniczników z rzędu?
Kip

FYI:
Scalono
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.