Wie teile ich einen String, behalte aber auch die Trennzeichen?

243

Ich habe eine mehrzeilige Zeichenfolge, die durch eine Reihe verschiedener Trennzeichen begrenzt ist:

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

Ich kann diese Zeichenfolge mithilfe von in ihre Teile aufteilen String.split, aber es scheint, dass ich die tatsächliche Zeichenfolge, die mit dem Regex des Trennzeichens übereinstimmt, nicht erhalten kann.

Mit anderen Worten, das bekomme ich:

  • Text1
  • Text2
  • Text3
  • Text4

Das ist was ich will

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

Gibt es eine JDK-Möglichkeit, die Zeichenfolge mithilfe eines Trennzeichen-Regex zu teilen, aber auch die Trennzeichen beizubehalten?

Daniel Rikowski
quelle
Kommen Sie und denken Sie darüber nach, wo möchten Sie die Begrenzer aufbewahren? Zusammen mit Worten oder getrennt? Würden Sie sie im ersten Fall an vorhergehende oder folgende Wörter anhängen? Im zweiten Fall ist meine Antwort, was Sie brauchen ...
PhiLho
Sie haben gerade eine Klasse implementiert, die Ihnen helfen soll, das zu erreichen, wonach Sie suchen. Siehe unten
VonC

Antworten:

366

Sie können Lookahead und Lookbehind verwenden. So was:

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

Und du wirst bekommen:

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

Der letzte ist was du willst.

((?<=;)|(?=;))entspricht der Auswahl eines leeren Zeichens vor ;oder nach ;.

Hoffe das hilft.

BEARBEITEN Fabian Steeg Kommentare zur Lesbarkeit sind gültig. Die Lesbarkeit ist für RegEx immer das Problem. Eine Sache, die ich mache, um dies zu vereinfachen, besteht darin, eine Variable zu erstellen, deren Name die Funktion des regulären Ausdrucks darstellt, und das Java-String-Format zu verwenden, um dies zu unterstützen. So was:

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

Das hilft ein bisschen. :-D

NawaMan
quelle
2
Sehr schön! Hier können wir wieder die Kraft der regulären Ausdrücke sehen !!
George
1
Schön zu sehen, dass es eine Möglichkeit gibt, dies mit String # split zu tun, obwohl ich mir wünschte, es gäbe eine Möglichkeit, die Trennzeichen einzuschließen, wie es sie für den StringTokenizer gab - split(";", true)wäre so viel besser lesbar als split("((?<=;)|(?=;))").
Fabian Steeg
3
Das sollte sein: String.format(WITH_DELIMITER, ";");as Format ist eine statische Methode.
John16384
8
Eine Komplikation, auf die ich gerade gestoßen bin, sind Begrenzer mit variabler Länge (sagen wir [\\s,]+), die Sie vollständig anpassen möchten. Die erforderlichen regulären Ausdrücke werden noch länger, da Sie zusätzliche negative Blicke benötigen, um zu vermeiden, dass sie in der Mitte übereinstimmen, z. (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
Michał Politowski
3
Was ist, wenn ich durch zwei Trennzeichen aufgeteilt werden möchte? sagen wir ';' oder '.'
Miracle-Doh
78

Sie möchten Lookarounds verwenden und auf Übereinstimmungen mit der Breite Null aufteilen. Hier sind einige Beispiele:

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

Und ja, das ist eine dreifach verschachtelte Behauptung im letzten Muster.

Verwandte Fragen

Siehe auch

Polygenschmierstoffe
quelle
1
Beachten Sie, dass dies nur für relativ einfache Ausdrücke funktioniert. Ich habe eine "Look-Behind-Gruppe hat keine offensichtliche maximale Länge", die versucht, diese mit einem regulären Ausdruck zu verwenden, der alle reellen Zahlen darstellt.
Daveagp
2
Zu Ihrer
Information
30

Eine sehr naive Lösung, bei der kein regulärer Ausdruck erforderlich ist, besteht darin, eine Zeichenfolge an Ihrem Trennzeichen zu ersetzen (unter der Annahme von Komma für Trennzeichen):

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

Hier können Sie tilda (~) durch ein geeignetes eindeutiges Trennzeichen ersetzen.

Wenn Sie dann einen Split für Ihr neues Trennzeichen vornehmen, werden Sie meiner Meinung nach das gewünschte Ergebnis erzielen.

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

Ich mag den anderen Weg nicht wirklich, wo man vorne und hinten ein leeres Element bekommt. Ein Trennzeichen befindet sich normalerweise nicht am Anfang oder am Ende der Zeichenfolge. Daher verschwenden Sie am häufigsten zwei gute Array-Slots.

Bearbeiten: Feste Grenzfälle. Eine kommentierte Quelle mit Testfällen finden Sie hier: http://snippets.dzone.com/posts/show/6453

Markus Jarderot
quelle
Wahoo ... Danke für die Teilnahme! Interessanter Ansatz. Ich bin mir nicht sicher, ob es konsequent helfen kann (dabei gibt es manchmal ein Trennzeichen, manchmal nicht), aber +1 für den Aufwand. Sie müssen jedoch die Grenzfälle (leere oder Nullwerte) noch richtig
angehen
Ich lade Sie ein, diese Klasse ordnungsgemäß zu verstärken, sie gründlich zu dokumentieren, einen Pass mit Findbugs und Checkstyle zu erstellen und sie dann auf einer Snippets-Website zu veröffentlichen (um zu vermeiden, dass diese Seite mit Tonnen von Code
überladen wird
Du hast die Herausforderung gewonnen! Ähm ... Glückwunsch! Wie Sie wissen, gibt es aus dem Code-Challenge-Thread keine speziellen Punkte oder Abzeichen dafür ... ( seufz ): stackoverflow.com/questions/172184 . Aber danke für diesen Beitrag.
VonC
@VonC Meistens nullist es der richtige Weg , NPE auf Argumente zu werfen . Wenn Sie es stillschweigend behandeln, werden später Fehler angezeigt.
Maaartinus
@maaartinus Ich stimme zu, aber es gibt sicherlich Fälle, in denen Sie eine benutzerfreundlichere Nachricht als nur NPE senden möchten, oder?
VonC
11

Ich bin spät hier angekommen, aber zurück zur ursprünglichen Frage, warum nicht einfach Lookarounds verwenden?

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

Ausgabe:

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

BEARBEITEN: Was Sie oben sehen, wird in der Befehlszeile angezeigt, wenn ich diesen Code ausführe, aber jetzt sehe ich, dass es etwas verwirrend ist. Es ist schwierig zu verfolgen, welche Kommas Teil des Ergebnisses sind und welche von hinzugefügt wurden Arrays.toString(). Die Syntaxhervorhebung von SO hilft auch nicht. In der Hoffnung, dass die Hervorhebung mit mir anstatt gegen mich funktioniert, würden diese Arrays folgendermaßen aussehen: Ich habe sie im Quellcode deklariert:

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

Ich hoffe das ist leichter zu lesen. Danke für das Heads-up, @finnw.

Alan Moore
quelle
Ich weiß, dass es falsch aussieht - es sah für mich falsch aus, als ich gerade ein Jahr später darauf zurückkam. Der Probeneingang war schlecht gewählt; Ich werde den Beitrag bearbeiten und versuchen, die Dinge zu klären.
Alan Moore
Zu Ihrer
Information
10

Ich weiß, dass dies eine sehr, sehr alte Frage ist und die Antwort auch akzeptiert wurde. Trotzdem möchte ich eine sehr einfache Antwort auf die ursprüngliche Frage geben. Betrachten Sie diesen Code:

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

AUSGABE:

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"

Ich benutze nur die Wortgrenze \b, um die Wörter abzugrenzen, außer wenn es sich um einen Textanfang handelt.

Anubhava
quelle
1
+1 Die beste Antwort für mich. aber es funktioniert nicht für alphanumerische Trennzeichen in einer alphanumerischen Zeichenfolge
Casimir et Hippolyte
@ CasimiretHippolyte: Danke für deine positive Bewertung. Können Sie bitte eine Beispieleingabe bereitstellen, bei der es nicht funktioniert hat?
Anubhava
2
zum Beispiel funktioniert das nicht für abcdefmit deals Trennzeichen, aber Sie können das Problem mit lösen(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
Casimir et Hippolyte
1
Beachten Sie die erste Behauptung, um eine leere Zeichenfolge im Ergebnis zu vermeiden, wenn die Zeichenfolge mit dem Trennzeichen endet, dh(?!^|$)
Casimir et Hippolyte
1
Zu Ihrer
Information
9

Ich habe mir die obigen Antworten angesehen und ehrlich gesagt finde ich keine zufriedenstellend. Was Sie tun möchten, ist im Wesentlichen die Perl-Split-Funktionalität nachzuahmen. Warum Java dies nicht zulässt und irgendwo eine join () -Methode hat, ist mir ein Rätsel, aber ich schweife ab. Sie brauchen dafür nicht einmal eine Klasse. Es ist nur eine Funktion. Führen Sie dieses Beispielprogramm aus:

Einige der früheren Antworten haben eine übermäßige Nullprüfung, die ich kürzlich hier auf eine Frage beantwortet habe:

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

Wie auch immer, der Code:

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"
    }
}
Cletus
quelle
Ich bin verwirrt: Java hat eine split () -Methode, die Perls nachempfunden ist, aber viel weniger leistungsfähig. Das Problem hierbei ist, dass Javas split () keine Möglichkeit bietet, die Trennzeichen zurückzugeben, was Sie in Perl erreichen können, indem Sie den regulären Ausdruck in Klammern einschließen.
Alan Moore
Zu Ihrer
Information
7

Ich mag die Idee von StringTokenizer, weil es Enumerable ist.
Es ist aber auch veraltet und wird durch String.split ersetzt, das einen langweiligen String [] zurückgibt (und die Trennzeichen nicht enthält).

Also habe ich einen StringTokenizerEx implementiert, der ein Iterable ist und der einen echten regulären Ausdruck benötigt, um einen String zu teilen.

Ein wahrer regulärer Ausdruck bedeutet, dass es sich nicht um eine 'Zeichenfolge' handelt, die wiederholt wird, um das Trennzeichen zu bilden:
'o' stimmt nur mit 'o' überein und teilt 'ooo' in drei Trennzeichen mit zwei leeren Zeichenfolgen auf:

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

Der reguläre Ausdruck o + gibt jedoch das erwartete Ergebnis zurück, wenn "aooob" aufgeteilt wird.

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

So verwenden Sie diesen 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();
}

Der Code dieser Klasse ist bei DZone Snippets verfügbar .

Kopieren Sie sie wie üblich für eine Code-Challenge- Antwort (eine in sich geschlossene Klasse mit Testfällen), fügen Sie sie ein (in ein 'src / test'-Verzeichnis) und führen Sie sie aus . Die main () -Methode veranschaulicht die verschiedenen Verwendungen.


Hinweis: (Ende 2009 bearbeiten)

Der Artikel Final Thoughts: Java Puzzler: Splitting Hairs leistet eine gute Arbeit, um das bizarre Verhalten in zu erklären String.split().
Josh Bloch antwortete sogar auf diesen Artikel:

Ja, das ist ein Schmerz. FWIW, es wurde aus einem sehr guten Grund gemacht: Kompatibilität mit Perl.
Der Typ, der es getan hat, ist Mike "madbot" McCloskey, der jetzt bei Google mit uns zusammenarbeitet. Mike stellte sicher, dass Javas reguläre Ausdrücke praktisch jeden der 30K Perl-Tests für reguläre Ausdrücke bestanden (und schneller liefen).

Die Google Common-Library Guava enthält auch einen Splitter:

  • einfacher zu bedienen
  • gepflegt von Google (und nicht von Ihnen)

Es kann sich also lohnen, ausgecheckt zu werden. Aus ihrer anfänglichen groben Dokumentation (pdf) :

JDK hat dies:

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

Es ist in Ordnung, dies zu verwenden, wenn Sie genau das wollen, was es tut: - regulärer Ausdruck - Ergebnis als Array - seine Art, mit leeren Teilen umzugehen

Mini-Puzzler: ", a ,, b,". Split (",") gibt zurück ...

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

Antwort: (e) Keine der oben genannten.

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

Nur nachlaufende Leergut wird übersprungen! (Wer kennt die Problemumgehung, um das Überspringen zu verhindern? Es macht Spaß ...)

In jedem Fall ist unser Splitter einfach flexibler: Das Standardverhalten ist simpel:

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

Wenn Sie zusätzliche Funktionen wünschen, fragen Sie nach!

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

Die Reihenfolge der Konfigurationsmethoden spielt keine Rolle. Während des Aufteilens erfolgt das Zuschneiden, bevor nach Leergut gesucht wird.

VonC
quelle
Zu Ihrer
Information
6

Übergeben Sie das 3. Dokument als "wahr". Es werden auch Trennzeichen zurückgegeben.

StringTokenizer(String str, String delimiters, true);
Haseeb Jadoon
quelle
4

Hier ist eine einfache, saubere Implementierung, die mit Pattern#splitMustern variabler Länge konsistent ist und mit diesen funktioniert, die hinterher nicht unterstützt werden können, und die einfacher zu verwenden ist. Es ähnelt der von @cletus bereitgestellten Lösung.

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

Ich mache hier keine Nullprüfungen, Pattern#splitnicht, warum sollte ich. Ich mag das ifam Ende nicht, aber es ist für die Konsistenz mit dem erforderlich Pattern#split. Andernfalls würde ich bedingungslos anhängen, was zu einer leeren Zeichenfolge als letztes Element des Ergebnisses führt, wenn die Eingabezeichenfolge mit dem Muster endet.

Ich konvertiere in String [], um die Konsistenz mit zu gewährleisten. Pattern#splitIch verwende new String[0]eher als new String[result.size()], siehe hier, warum.

Hier sind meine Tests:

@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);
}
julianisch
quelle
2

Ich werde auch meine Arbeitsversionen veröffentlichen (erstens ist Markus wirklich ähnlich).

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

Und hier ist die zweite Lösung und ihre Runde 50% schneller als die erste:

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()]);
}
Tomasz Mularczyk
quelle
2

Eine weitere mögliche Lösung mit einem regulären Ausdruck. Behält die Token-Reihenfolge bei und stimmt korrekt mit mehreren Token desselben Typs hintereinander überein. Der Nachteil ist, dass der Regex irgendwie böse ist.

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

Beispielausgabe:

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

Ich kenne keine vorhandene Funktion in der Java-API, die dies ausführt (was nicht heißt, dass sie nicht vorhanden ist), aber hier ist meine eigene Implementierung (ein oder mehrere Trennzeichen werden als einzelnes Token zurückgegeben, wenn Sie möchten Jedes Trennzeichen, das als separates Token zurückgegeben werden soll, muss angepasst werden.

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]);
}
bdumitriu
quelle
Zu Ihrer
Information
1

Ich schlage vor, Pattern und Matcher zu verwenden, um mit ziemlicher Sicherheit das zu erreichen, was Sie wollen. Ihr regulärer Ausdruck muss etwas komplizierter sein als das, was Sie in String.split verwenden.

Steve McLeod
quelle
+1, das ist der richtige Weg. StringTokenizer gibt Trennzeichen aus, wenn Sie sie in Erfassungsgruppen einfügen, diese sind jedoch im Wesentlichen veraltet. Die Verwendung von Lookahead mit split () ist aus Gründen, die in den Kommentaren der akzeptierten Antwort dargelegt sind, schwierig - hauptsächlich, weil es zu einem Chaos wird, wenn mehr als ein Trennzeichen vorhanden ist. Mit Pattern und Matcher können Sie jedoch in wenigen Zeilen einen echten Tokenizer erstellen.
Johncip
1

Ich denke nicht, dass dies mit möglich ist String#split, aber Sie können a verwenden StringTokenizer, obwohl Sie damit Ihr Trennzeichen nicht als regulären Ausdruck definieren können, sondern nur als Klasse einstelliger Zeichen:

new StringTokenizer("Hello, world. Hi!", ",.!", true); // true for returnDelims
Fabian Steeg
quelle
Dort kann ich keinen regulären Ausdruck definieren, um meine Trennzeichen anzugeben.
Daniel Rikowski
1
StringTokenizer erlaubt jedoch nur Einzelzeichen-Trennzeichen.
Michael Borgwardt
1

Wenn Sie es sich leisten können, verwenden Sie die Java-Ersetzungsmethode (CharSequence-Ziel, CharSequence-Ersetzung) und geben Sie ein anderes Trennzeichen ein, mit dem Sie teilen möchten. Beispiel: Ich möchte die Zeichenfolge "boo: and: foo" teilen und ':' an der rechten Zeichenfolge belassen.

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

Wichtiger Hinweis: Dies funktioniert nur, wenn Sie keinen weiteren "newdelimiter" in Ihrem String haben! Somit ist es keine allgemeine Lösung. Wenn Sie jedoch eine CharSequence kennen, von der Sie sicher sein können, dass sie niemals in der Zeichenfolge angezeigt wird, ist dies eine sehr einfache Lösung.

Stephan
quelle
Zu Ihrer
Information
0

Schnelle Antwort: Verwenden Sie zum Teilen nicht physische Grenzen wie \ b. Ich werde versuchen zu experimentieren, um zu sehen, ob es funktioniert (verwendet das in PHP und JS).

Es ist möglich und Art der Arbeit, könnte aber zu viel spalten. Tatsächlich hängt es von der Zeichenfolge ab, die Sie teilen möchten, und vom Ergebnis, das Sie benötigen. Geben Sie weitere Details an, wir helfen Ihnen besser.

Eine andere Möglichkeit besteht darin, eine eigene Aufteilung vorzunehmen, das Trennzeichen zu erfassen (vorausgesetzt, es ist variabel) und es anschließend zum Ergebnis hinzuzufügen.

Mein schneller Test:

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

Ergebnis:

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

Ein bisschen zu viel... :-)

PhiLho
quelle
Zu Ihrer
Information
0

Gezwickt Pattern.split () auf abgestimmte Muster auf der Liste umfassen

Hinzugefügt

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

Vollständige Quelle

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);
}
Prashant Bhate
quelle
Zu Ihrer
Information
0

Hier ist eine groovige Version, die auf dem obigen Code basiert, falls es hilft. Es ist sowieso kurz. Beinhaltet bedingt den Kopf und den Schwanz (wenn sie nicht leer sind). Der letzte Teil ist ein Demo / Testfall.

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)
}
Meilen Zarathustra
quelle
Zu Ihrer
Information
0

Eine äußerst naive und ineffiziente Lösung, die dennoch funktioniert. Verwenden Sie die Aufteilung zweimal auf die Zeichenfolge und verketten Sie dann die beiden Arrays

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);
Varun Gangal
quelle
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));
Kanagavelu Sugumar
quelle
Mit Regexp wird dies sein:Scanner scanner = new Scanner("((A+B)*C-D)*E"); scanner.useDelimiter("((?<=[\\+\\*\\-\\/\\(\\)])|(?=[\\+\\*\\-\\/\\(\\)]))"); while (scanner.hasNext()) { System.out.print(" " + scanner.next()); }
Tsolak Barseghyan
0

Eine der Feinheiten in dieser Frage betrifft die Frage "führendes Trennzeichen": Wenn Sie ein kombiniertes Array von Token und Trennzeichen haben möchten, müssen Sie wissen, ob es mit einem Token oder einem Trennzeichen beginnt. Sie könnten natürlich einfach davon ausgehen, dass ein führendes Delim verworfen werden sollte, aber dies scheint eine ungerechtfertigte Annahme zu sein. Vielleicht möchten Sie auch wissen, ob Sie ein nachfolgendes Delim haben oder nicht. Dies setzt zwei boolesche Flags entsprechend.

Geschrieben in Groovy, aber eine Java-Version sollte ziemlich offensichtlich sein:

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

            }
Mike Nagetier
quelle
-2

Ich kenne Java nicht so gut, aber wenn Sie keine Split-Methode finden, die das macht, schlage ich vor, Sie machen einfach Ihre eigene.

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

Es ist nicht zu elegant, aber es wird reichen.

Alon L.
quelle
aber was ist, wenn Sie mehrere Begrenzer hintereinander haben?
Kip
Zu Ihrer
Information