Come dividere una stringa, ma anche mantenere i delimitatori?


243

Ho una stringa multilinea che è delimitata da una serie di delimitatori diversi:

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

Posso dividere questa stringa nelle sue parti, usando String.split , ma sembra che non riesca a ottenere la stringa effettiva, che corrisponda alla regex del delimitatore.

In altre parole, questo è quello che ottengo:

  • Text1
  • Text2
  • Text3
  • Text4

Questo è quello che voglio

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

Esiste un modo JDK per dividere la stringa usando una regex delimitatore ma anche mantenere i delimitatori?


Vieni a pensarci, dove vuoi mantenere i delimitatori? Insieme a parole o separati? Nel primo caso, li collegheresti alla parola precedente o seguente? Nel secondo caso, la mia risposta è ciò di cui hai bisogno ...
PhiLho,

Ho appena implementato una classe che dovrebbe aiutarti a raggiungere quello che stai cercando. Vedi sotto
VonC

Risposte:


366

Puoi usare Lookahead e Lookbehind. Come questo:

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

E otterrai:

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

L'ultimo è quello che vuoi.

((?<=;)|(?=;))equivale a selezionare un carattere vuoto prima ;o dopo ;.

Spero che questo ti aiuti.

EDIT I commenti di Fabian Steeg sulla leggibilità sono validi. La leggibilità è sempre il problema per RegEx. Una cosa che faccio per facilitare questo è creare una variabile il cui nome rappresenta ciò che fa la regex e usare il formato Java String per aiutarlo. Come questo:

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

Questo aiuta un pochino. :-D


2
Molto bella! Qui possiamo rivedere il potere delle espressioni regolari !!
George,

1
Bello vedere che c'è un modo per farlo con String # split, anche se vorrei che ci fosse un modo per includere i delimitatori come c'era per StringTokenizer - split(";", true)sarebbe molto più leggibile di split("((?<=;)|(?=;))").
Fabian Steeg,

3
Dovrebbe essere: String.format(WITH_DELIMITER, ";");poiché il formato è un metodo statico.
john16384,

8
Una complicazione che ho appena incontrato sono i delimitatori di lunghezza variabile (diciamo [\\s,]+) che vuoi abbinare completamente. Le regex richieste diventano ancora più lunghe, poiché è necessario un ulteriore aspetto negativo {avanti, dietro} per evitare di abbinarle nel mezzo, ad es. (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
Michał Politowski,

3
e se volessi dividere per due delimitatori? diciamo ';' o '.'
miracle-doh,

78

Vuoi usare i lookaround e dividerli su corrispondenze di larghezza zero. Ecco alcuni esempi:

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

E sì, questa è un'asserzione triplicata nell'ultimo schema.

Domande correlate

Guarda anche


1
Nota che funzionerà solo con espressioni relativamente semplici; Ho ottenuto un "gruppo Look-behind non ha una lunghezza massima evidente" cercando di usarlo con una regex che rappresenta tutti i numeri reali.
daveagp,


30

Una soluzione molto ingenua, che non implica regex sarebbe quella di eseguire una sostituzione di stringa sul delimitatore lungo le linee di (assumendo la virgola per delimitatore):

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

Dove è possibile sostituire tilda (~) con un delimitatore univoco appropriato.

Quindi se fai una divisione sul tuo nuovo delimitatore, credo che otterrai il risultato desiderato.


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

Non mi piace molto il contrario, dove ottieni un elemento vuoto davanti e dietro. Un delimitatore di solito non si trova all'inizio o alla fine della stringa, quindi il più delle volte si finisce per sprecare due buoni slot di array.

Modifica: casi limite fissi. La fonte commentata con casi di test è disponibile qui: http://snippets.dzone.com/posts/show/6453


Wahoo ... Grazie per aver partecipato! Approccio interessante Non sono sicuro che possa essere di aiuto in modo coerente (con quello, a volte c'è un delimitatore, a volte non c'è), ma +1 per lo sforzo. Tuttavia, devi ancora affrontare correttamente i casi limite (valori vuoti o nulli)
VonC

Ti invito a rafforzare adeguatamente questa classe, documentarla a fondo, fare un passaggio con findbugs e checkstyle e quindi pubblicarla su un sito Web di frammenti (per evitare di ingombrare questa pagina con tonnellate di codice)
VonC,

Hai vinto la sfida! Errr ... congratulazioni! Come sapete, dal thread della sfida del codice, non ci sarebbero punti speciali o badge per questo ... (sospiro): stackoverflow.com/questions/172184 . Ma grazie per questo contributo.
VonC,

@VonC Il più delle volte, lanciare l'NPE nullsull'argomento è la strada giusta da percorrere. La gestione silenziosa porta a errori visualizzati in seguito.
maaartinus,

@maaartinus Sono d'accordo, ma sicuramente ci sono casi in cui vuoi lanciare un messaggio più intuitivo di un semplice NPE, giusto?
VonC,

11

Sono arrivato tardi, ma tornando alla domanda originale, perché non usare solo le soluzioni?

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

produzione:

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

EDIT: Quello che vedi sopra è ciò che appare sulla riga di comando quando eseguo quel codice, ma ora vedo che è un po 'confuso. È difficile tenere traccia di quali virgole fanno parte del risultato e quali sono state aggiunte Arrays.toString(). L'evidenziazione della sintassi di SO non aiuta neanche. Nella speranza di fare in modo che l'evidenziazione funzioni con me anziché contro di me, ecco come apparirebbero quegli array che li stavo dichiarando nel codice sorgente:

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

Spero sia più facile da leggere. Grazie per l'heads-up, @finnw.


So che sembra sbagliato - mi è sembrato sbagliato quando ci sono tornato proprio ora, un anno dopo il fatto. L'input del campione è stato scelto male; Modificherò il post e proverò a chiarire le cose.
Alan Moore,


10

So che questa è una domanda molto antica e anche la risposta è stata accettata. Ma vorrei comunque inviare una risposta molto semplice alla domanda originale. Considera questo codice:

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

PRODUZIONE:

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"

Sto solo usando il limite \bdi parole per delimitare le parole tranne quando è l'inizio del testo.


1
+1 La migliore risposta per me. ma non funziona per i delimitatori alfanumerici in una stringa alfanumerica
Casimir et Hippolyte,

@CasimiretHippolyte: Grazie per il tuo voto. Potete per favore fornire un input di esempio in cui non ha funzionato.
anubhava,

2
per esempio questo non funziona abcdefcon dedelimitatore, ma puoi risolvere il problema usando(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
Casimir et Hippolyte,

1
Nota la prima asserzione per evitare una stringa vuota nel risultato quando la stringa termina con il delimitatore, ovvero(?!^|$)
Casimir et Hippolyte,


9

Ho dato un'occhiata alle risposte di cui sopra e onestamente nessuna di esse trovo soddisfacente. Quello che vuoi fare è essenzialmente imitare la funzionalità di suddivisione Perl. Perché Java non lo permetta e abbia un metodo join () da qualche parte è oltre me ma sto divagando. Non hai nemmeno bisogno di una lezione per questo davvero. È solo una funzione. Esegui questo programma di esempio:

Alcune delle risposte precedenti hanno un controllo null eccessivo, che di recente ho scritto una risposta a una domanda qui:

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

Ad ogni modo, il codice:

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

Sono confuso: Java ha un metodo split (), che è modellato su Perl, ma molto meno potente. Il problema qui è che split () di Java non fornisce alcun modo per restituire i delimitatori, che puoi ottenere in Perl racchiudendo la regex nella cattura delle parentesi.
Alan Moore,


7

Mi piace l'idea di StringTokenizer perché è enumerabile.
Ma è anche obsoleto e sostituito da String.split che restituisce una stringa noiosa [] (e non include i delimitatori).

Quindi ho implementato un StringTokenizerEx che è un Iterable e che richiede una vera regexp per dividere una stringa.

Una vera regexp significa che non è una 'sequenza di caratteri' ripetuta per formare il delimitatore:
'o' corrisponderà solo a 'o' e dividere 'ooo' in tre delimitatori, con due stringhe vuote all'interno:

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

Ma il regexp o + restituirà il risultato atteso quando si divide "aooob"

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

Per utilizzare questo 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();
}

Il codice di questa classe è disponibile su Snippet DZone .

Come al solito per una risposta a code challenge (una classe autonoma con casi di test inclusi), copiarla e incollarla (in una directory 'src / test') ed eseguirla . Il suo metodo main () illustra i diversi usi.


Nota: (modifica alla fine del 2009)

L'articolo Final Thoughts: Java Puzzler: Splitting Hairs fa un buon lavoro spiegando il comportamento bizzarro String.split().
Josh Bloch ha anche commentato in risposta a quell'articolo:

Sì, questo è un dolore. FWIW, è stato fatto per un'ottima ragione: compatibilità con Perl.
Il ragazzo che lo ha fatto è Mike "Madbot" McCloskey, che ora lavora con noi a Google. Mike si assicurò che le espressioni regolari di Java superassero praticamente tutti i test di espressione regolare Perl da 30K (e funzionassero più velocemente).

La libreria comune di Google Guava contiene anche uno Splitter che è:

  • più semplice da usare
  • gestito da Google (e non da te)

Quindi potrebbe valere la pena di essere verificato. Dalla loro documentazione iniziale approssimativa (pdf) :

JDK ha questo:

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

Va bene usarlo se vuoi esattamente quello che fa: - espressione regolare - risultato come un array - il suo modo di gestire pezzi vuoti

Mini-puzzle: ", a ,, b,". Split (",") restituisce ...

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

Risposta: (e) Nessuna delle precedenti.

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

Solo i vuoti finali vengono saltati! (Chi conosce la soluzione alternativa per evitare il salto? È divertente ...)

In ogni caso, il nostro Splitter è semplicemente più flessibile: il comportamento predefinito è semplicistico:

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

Se vuoi funzionalità extra, chiedile!

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

I metodi dell'ordine di configurazione non contano: durante la divisione, il taglio avviene prima di verificare la presenza di vuoti.



6

Passa il 3 ° documento come "vero". Restituirà anche i delimitatori.

StringTokenizer(String str, String delimiters, true);

4

Ecco una semplice implementazione pulita che è coerente Pattern#splite funziona con modelli di lunghezza variabile, che non possono essere supportati da dietro, ed è più facile da usare. È simile alla soluzione fornita da @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]);
}

Non faccio controlli nulli qui, Pattern#splitno, perché dovrei I. ifAlla fine non mi piace ma è necessario per coerenza con Pattern#split. Altrimenti aggiungerei incondizionatamente, risultando in una stringa vuota come ultimo elemento del risultato se la stringa di input termina con il modello.

Mi converto in String [] per coerenza con Pattern#split, io uso new String[0]piuttosto che new String[result.size()], vedi qui per perché.

Ecco i miei test:

@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

Pubblicherò anche le mie versioni funzionanti (la prima è molto simile a 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()]);
}

Ed ecco la seconda soluzione e il suo giro è il 50% più veloce della prima:

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

Un'altra soluzione candidata che utilizza una regex. Mantiene l'ordine dei token, abbina correttamente più token dello stesso tipo in una riga. Il rovescio della medaglia è che la regex è un po 'brutta.

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

Uscita campione:

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

1

Non conosco una funzione esistente nell'API Java che lo fa (il che non vuol dire che non esiste), ma ecco la mia implementazione (uno o più delimitatori verranno restituiti come token singolo; se vuoi ogni delimitatore deve essere restituito come token separato, avrà bisogno di un po 'di adattamento):

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

Suggerisco di usare Pattern and Matcher, che raggiungerà quasi sicuramente quello che vuoi. La tua espressione regolare dovrà essere un po 'più complicata di quella che stai usando in String.split.


+1, questo è il modo giusto. StringTokenizer genererà delimitatori se li inserirai in gruppi di acquisizione, ma è essenzialmente obsoleto. L'uso di lookahead con split () è confuso per ragioni che sono delineate nei commenti della risposta accettata, principalmente che diventa un casino quando c'è più di un delimitatore. Ma puoi avere un vero tokenizer in poche righe con Pattern e Matcher.
johncip

1

Non credo sia possibile con String#split, ma puoi usare a StringTokenizer, anche se ciò non ti consentirà di definire il delimitatore come regex, ma solo come una classe di caratteri a una cifra:

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

Lì non riesco a definire una regex per specificare i miei delimitatori.
Daniel Rikowski,

1
StringTokenizer consente solo i delimitatori a carattere singolo.
Michael Borgwardt,

1

Se puoi permetterti, usa il metodo di sostituzione Java (destinazione CharSequence, sostituzione CharSequence) e compila un altro delimitatore con cui dividere. Esempio: voglio dividere la stringa "boo: and: foo" e mantenere ':' alla sua stringa di destra.

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

Nota importante: funziona solo se nella stringa non sono presenti altri "newdelimiter"! Pertanto, non è una soluzione generale. Ma se conosci una CharSequence di cui puoi essere sicuro che non comparirà mai nella String, questa è una soluzione molto semplice.



0

Risposta rapida: usa i limiti non fisici come \ b per dividere. Proverò e sperimenterò per vedere se funziona (usato in PHP e JS).

È possibile e tipo di lavoro, ma potrebbe dividere troppo. In realtà, dipende dalla stringa che vuoi dividere e dal risultato che ti serve. Fornisci maggiori dettagli, ti aiuteremo meglio.

Un altro modo è fare la tua divisione, catturando il delimitatore (supponendo che sia variabile) e aggiungendolo successivamente al risultato.

Il mio test rapido:

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

Risultato:

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

Un po 'troppo ... :-)



0

Tweaked Pattern.split () per includere il modello corrispondente nell'elenco

aggiunto

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

Fonte completa

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

Ecco una versione groovy basata su alcuni dei codici sopra, nel caso in cui aiuti. È breve, comunque. Include in modo condizionale la testa e la coda (se non sono vuote). L'ultima parte è un caso demo / test.

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

Una soluzione estremamente ingenua e inefficiente che funziona comunque. Usa la divisione due volte sulla stringa e quindi concatena le due matrici

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

Con regexp questo sarà:Scanner scanner = new Scanner("((A+B)*C-D)*E"); scanner.useDelimiter("((?<=[\\+\\*\\-\\/\\(\\)])|(?=[\\+\\*\\-\\/\\(\\)]))"); while (scanner.hasNext()) { System.out.print(" " + scanner.next()); }
Tsolak Barseghyan l'

0

Una delle sottigliezze di questa domanda riguarda la domanda "delimitatore principale": se si desidera disporre di un array combinato di token e delimitatori, è necessario sapere se inizia con un token o un delimitatore. Ovviamente si potrebbe semplicemente supporre che un delim di testa debba essere scartato, ma questo sembra un presupposto ingiustificato. Potresti anche voler sapere se hai un delimente finale o meno. Questo imposta di conseguenza due flag booleani.

Scritto in Groovy ma una versione Java dovrebbe essere abbastanza ovvia:

            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

Non conosco troppo bene Java, ma se non riesci a trovare un metodo Split che lo faccia, ti suggerisco di crearne uno tuo.

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

Non è troppo elegante, ma lo farà.


ma cosa succede se si hanno più delimitatori di fila?
Kip

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.