¿Cómo dividir una cadena, pero también mantener los delimitadores?

243

Tengo una cadena multilínea que está delimitada por un conjunto de delimitadores diferentes:

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

Puedo dividir esta cadena en sus partes, usando String.split, pero parece que no puedo obtener la cadena real, que coincide con la expresión regular del delimitador.

En otras palabras, esto es lo que obtengo:

  • Text1
  • Text2
  • Text3
  • Text4

Esto es lo que quiero

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

¿Hay alguna forma JDK de dividir la cadena usando una expresión regular del delimitador pero también mantener los delimitadores?

Daniel Rikowski
fuente
Ahora que lo pienso, ¿dónde quieres guardar los delimitadores? ¿Junto con palabras o por separado? En el primer caso, ¿los adjuntaría a la palabra anterior o siguiente? En el segundo caso, mi respuesta es lo que necesita ...
PhiLho
Acabo de implementar una clase que debería ayudarte a lograr lo que estás buscando. Ver abajo
VonC

Respuestas:

366

Puede usar Lookahead y Lookbehind. Me gusta esto:

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

Y obtendrás:

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

El último es lo que quieres.

((?<=;)|(?=;))es igual a seleccionar un caracter vacío antes ;o después ;.

Espero que esto ayude.

EDITAR Los comentarios de Fabian Steeg sobre Legibilidad son válidos. La legibilidad es siempre el problema para RegEx. Una cosa que hago para ayudar a aliviar esto es crear una variable cuyo nombre represente lo que hace la expresión regular y usar el formato de Java String para ayudarlo. Me gusta esto:

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

Esto ayuda un poco. :-RE

NawaMan
fuente
2
¡Muy agradable! ¡Aquí podemos ver nuevamente el poder de las expresiones regulares!
George
1
Es bueno ver que hay una manera de hacer esto con String # split, aunque desearía que hubiera una manera de incluir los delimitadores como lo había para StringTokenizer: split(";", true)sería mucho más legible que split("((?<=;)|(?=;))").
Fabian Steeg
3
Eso debería ser: String.format(WITH_DELIMITER, ";");ya que el formato es un método estático.
john16384
8
Una complicación que acabo de encontrar son los delimitadores de longitud variable (digamos [\\s,]+) que desea que coincidan por completo. Las expresiones regulares requeridas se hacen aún más largas, ya que necesita una mirada negativa adicional {adelante, atrás} s para evitar que coincidan en el medio, por ejemplo. (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
Michał Politowski,
3
¿Qué pasa si quiero dividirme en dos delimitadores? digamos ';' o '.'
miracle-doh
78

Desea usar lookarounds y dividir en coincidencias de ancho cero. Aquí hay unos ejemplos:

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

Y sí, esa es una afirmación triplemente anidada en el último patrón.

Preguntas relacionadas

Ver también

poligenelubricantes
fuente
1
Tenga en cuenta que esto solo funcionará para expresiones relativamente simples; Obtuve un "Grupo de observación no tiene una longitud máxima obvia" tratando de usar esto con una expresión regular que representa todos los números reales.
daveagp
2
FYI: Fusionada de stackoverflow.com/questions/275768/…
Shog9
30

Una solución muy ingenua, que no involucra expresiones regulares sería realizar un reemplazo de cadena en su delimitador a lo largo de las líneas de (suponiendo una coma para delimitador):

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

Donde puede reemplazar tilda (~) con un delimitador único apropiado.

Luego, si hace una división en su nuevo delimitador, creo que obtendrá el resultado deseado.

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

Realmente no me gusta la otra manera, donde obtienes un elemento vacío al frente y atrás. Por lo general, un delimitador no está al principio o al final de la cadena, por lo que a menudo terminas desperdiciando dos ranuras de matriz buenas.

Editar: casos límite fijos. La fuente comentada con casos de prueba se puede encontrar aquí: http://snippets.dzone.com/posts/show/6453

Markus Jarderot
fuente
Wahoo ... ¡Gracias por participar! Enfoque interesante No estoy seguro de que pueda ser una ayuda constante (con eso, a veces hay un delimitador, a veces no), pero +1 por el esfuerzo. Sin embargo, aún necesita abordar adecuadamente los casos límite (valores vacíos o nulos)
VonC
Los invito a reforzar adecuadamente esta clase, documentarla a fondo, hacer un pase con findbugs y checkstyle, y luego publicarla en un sitio web de fragmentos (para evitar saturar esta página con toneladas de código)
VonC
¡Has ganado el desafío! Errr ... felicidades! Como saben, desde el hilo de desafío de código, no habría puntos o insignias especiales para eso ... (suspiro): stackoverflow.com/questions/172184 . Pero gracias por esta contribución.
VonC
@VonC La mayoría de las veces, lanzar NPE en el nullargumento es la forma correcta de hacerlo. Su manejo silencioso conduce a errores que aparecen más tarde.
maaartinus
@maaartinus Estoy de acuerdo, pero seguramente hay instancias en las que desea lanzar un mensaje más fácil de usar que solo NPE, ¿verdad?
VonC
11

Llegué tarde, pero volviendo a la pregunta original, ¿por qué no solo usar lookarounds?

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

salida:

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

EDITAR: Lo que ves arriba es lo que aparece en la línea de comando cuando ejecuto ese código, pero ahora veo que es un poco confuso. Es difícil hacer un seguimiento de qué comas son parte del resultado y cuáles fueron agregadas por Arrays.toString(). El resaltado de sintaxis de SO tampoco ayuda. Con la esperanza de conseguir el resalte al trabajo con mí en vez de contra mí, así es como esas matrices se vería que les estaban declarando en el código fuente:

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

Espero que sea más fácil de leer. Gracias por el aviso, @finnw.

Alan Moore
fuente
Sé que se ve mal, me pareció mal cuando volví a hacerlo justo ahora, un año después del hecho. La entrada de muestra fue mal elegida; Editaré la publicación e intentaré aclarar las cosas.
Alan Moore
FYI: Fusionada de stackoverflow.com/questions/275768/…
Shog9
10

Sé que esta es una pregunta muy antigua y la respuesta también ha sido aceptada. Pero aún así me gustaría presentar una respuesta muy simple a la pregunta original. Considera este código:

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

SALIDA:

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"

Solo estoy usando el límite de palabras \bpara delimitar las palabras, excepto cuando es el comienzo del texto.

anubhava
fuente
1
+1 La mejor respuesta para mí. pero no funciona para delimitadores alfanuméricos en una cadena alfanumérica
Casimir et Hippolyte
@CasimiretHippolyte: Gracias por tu voto a favor. ¿Puede proporcionar una entrada de muestra donde no funcionó?
anubhava
2
por ejemplo, esto no funciona para abcdefcon decomo delimitador, pero se puede resolver el problema con(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
Casimir et Hippolyte
1
Tenga en cuenta la primera afirmación para evitar una cadena vacía en el resultado cuando la cadena termina con el delimitador, es decir(?!^|$)
Casimir et Hippolyte
1
FYI: Fusionada de stackoverflow.com/questions/275768/…
Shog9
9

Eché un vistazo a las respuestas anteriores y, sinceramente, ninguna de ellas me parece satisfactoria. Lo que quiere hacer es esencialmente imitar la funcionalidad de división de Perl. Por qué Java no permite esto y tiene un método join () en algún lugar que está más allá de mí, pero estoy divagando. Ni siquiera necesitas una clase para esto realmente. Es solo una función. Ejecute este programa de muestra:

Algunas de las respuestas anteriores tienen una comprobación nula excesiva, que recientemente escribí una respuesta a una pregunta aquí:

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

De todos modos, el código:

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
fuente
Estoy confundido: Java tiene un método split (), que está modelado sobre el de Perl, pero mucho menos potente. El problema aquí es que el split () de Java no proporciona una forma de devolver los delimitadores, lo que puede lograr en Perl encerrando la expresión regular en la captura de paréntesis.
Alan Moore
FYI: Fusionada de stackoverflow.com/questions/275768/…
Shog9
7

Me gusta la idea de StringTokenizer porque es Enumerable.
Pero también es obsoleto y se reemplaza por String.split, que devuelve un String aburrido [] (y no incluye los delimitadores).

Así que implementé un StringTokenizerEx que es un Iterable y que requiere una expresión regular verdadera para dividir una cadena.

Una expresión regular verdadera significa que no es una 'secuencia de caracteres' repetida para formar el delimitador:
'o' solo coincidirá con 'o', y dividirá 'ooo' en tres delimitadores, con dos cadenas vacías dentro:

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

Pero la expresión regular o + devolverá el resultado esperado al dividir "aooob"

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

Para usar este 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();
}

El código de esta clase está disponible en DZone Snippets .

Como es habitual para una respuesta de desafío de código (una clase autónoma con casos de prueba incluidos), cópiela y péguela (en un directorio 'src / test') y ejecútela . Su método main () ilustra los diferentes usos.


Nota: (edición de finales de 2009)

El artículo Reflexiones finales: Java Puzzler: Splitting Hairs hace un buen trabajo explicando el extraño comportamiento en String.split().
Josh Bloch incluso comentó en respuesta a ese artículo:

Sí, esto es un dolor. FWIW, se hizo por una muy buena razón: compatibilidad con Perl.
El tipo que lo hizo es Mike "madbot" McCloskey, quien ahora trabaja con nosotros en Google. Mike se aseguró de que las expresiones regulares de Java pasaran virtualmente cada una de las pruebas de expresión regular de 30K Perl (y corrían más rápido).

La guayaba de la biblioteca común de Google también contiene un divisor que es:

  • más simple de usar
  • mantenido por Google (y no por usted)

Por lo tanto, puede valer la pena echarle un vistazo. De su documentación preliminar inicial (pdf) :

JDK tiene esto:

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

Está bien usar esto si quieres exactamente lo que hace: - expresión regular - resultado como una matriz - su forma de manejar piezas vacías

Mini-rompecabezas: ", a ,, b,". Split (",") devuelve ...

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

Respuesta: (e) Ninguna de las anteriores.

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

¡Solo se saltan los vacíos finales! (¿Quién conoce la solución para evitar el salto? Es divertido ...)

En cualquier caso, nuestro Splitter es simplemente más flexible: el comportamiento predeterminado es simplista:

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

Si desea funciones adicionales, ¡pídalas!

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

El orden de los métodos de configuración no importa: durante la división, el recorte ocurre antes de verificar si hay vacíos.

VonC
fuente
FYI: Fusionada de stackoverflow.com/questions/275768/…
Shog9
6

Pase el tercer aurgument como "verdadero". También devolverá delimitadores.

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

Aquí hay una implementación simple y limpia que es consistente Pattern#splity funciona con patrones de longitud variable, que mirar hacia atrás no puede soportar, y es más fácil de usar. Es similar a la solución proporcionada por @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]);
}

No hago verificaciones nulas aquí, Pattern#splitno lo hace, ¿por qué debería hacerlo? No me gusta ifel final, pero es necesario para mantener la coherencia con el Pattern#split. De lo contrario, agregaría incondicionalmente, dando como resultado una cadena vacía como el último elemento del resultado si la cadena de entrada termina con el patrón.

Convierto a String [] por coherencia Pattern#split, uso en new String[0]lugar de new String[result.size()], vea aquí por qué.

Aquí están mis pruebas:

@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);
}
Julian
fuente
2

Publicaré también mis versiones de trabajo (la primera es muy similar 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()]);
}

Y aquí hay una segunda solución y es un 50% más rápida que la primera:

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
fuente
2

Otra solución candidata usando una expresión regular. Conserva el orden de los tokens, coincide correctamente con varios tokens del mismo tipo en una fila. La desventaja es que la expresión regular es un poco desagradable.

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

Salida de muestra:

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

No conozco una función existente en la API de Java que haga esto (lo que no quiere decir que no exista), pero aquí está mi propia implementación (uno o más delimitadores se devolverán como un token único; si lo desea cada delimitador se devolverá como un token separado, necesitará un poco de adaptación):

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
fuente
FYI: Fusionada de stackoverflow.com/questions/275768/…
Shog9
1

Sugiero usar Pattern and Matcher, que casi seguramente logrará lo que desea. Su expresión regular deberá ser algo más complicada que la que está utilizando en String.split.

Steve McLeod
fuente
+1, esta es la forma correcta. StringTokenizer generará delimitadores si los coloca en grupos de captura, pero es esencialmente obsoleto. Usar lookahead con split () es hacky por razones que se describen en los comentarios de la respuesta aceptada, principalmente porque se convierte en un desastre cuando hay más de un delimitador. Pero puedes tener un tokenizador real en unas pocas líneas con Pattern y Matcher.
johncip
1

No creo que sea posible con String#split, pero puede usar un StringTokenizer, aunque eso no le permitirá definir su delimitador como una expresión regular, sino solo como una clase de caracteres de un solo dígito:

new StringTokenizer("Hello, world. Hi!", ",.!", true); // true for returnDelims
Fabian Steeg
fuente
Allí no puedo definir una expresión regular para especificar mis delimitadores.
Daniel Rikowski
1
Sin embargo, StringTokenizer solo permite delimitadores de un solo carácter.
Michael Borgwardt
1

Si puede pagarlo, use el método de reemplazo de Java (destino de CharSequence, reemplazo de CharSequence) y complete otro delimitador para dividirlo. Ejemplo: quiero dividir la cadena "boo: and: foo" y mantener ':' en su cadena a la derecha.

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

Nota importante: ¡Esto solo funciona si no tienes más "newdelimiter" en tu String! Por lo tanto, no es una solución general. Pero si conoce un CharSequence del cual puede estar seguro de que nunca aparecerá en la Cadena, esta es una solución muy simple.

Stephan
fuente
FYI: Fusionada de stackoverflow.com/questions/275768/…
Shog9
0

Respuesta rápida: use límites no físicos como \ b para dividir. Intentaré experimentar para ver si funciona (lo usé en PHP y JS).

Es posible y un tipo de trabajo, pero podría dividirse demasiado. En realidad, depende de la cadena que desea dividir y del resultado que necesita. Da más detalles, te ayudaremos mejor.

Otra forma es hacer su propia división, capturando el delimitador (suponiendo que sea variable) y luego agregándolo al resultado.

Mi prueba rápida:

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

Resultado:

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

Un poco demasiado... :-)

PhiLho
fuente
FYI: Fusionada de stackoverflow.com/questions/275768/…
Shog9
0

Tweaked Pattern.split () para incluir patrones coincidentes en la lista

Adicional

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

Fuente 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);
}
Prashant Bhate
fuente
FYI: Fusionada de stackoverflow.com/questions/275768/…
Shog9
0

Aquí hay una versión maravillosa basada en algunos de los códigos anteriores, en caso de que ayude. Es corto, de todos modos. Incluye condicionalmente la cabeza y la cola (si no están vacías). La última parte es un caso de demostración / prueba.

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)
}
millas zarathustra
fuente
FYI: Fusionada de stackoverflow.com/questions/275768/…
Shog9
0

Sin embargo, es una solución extremadamente ingenua e ineficiente que funciona dividida dos veces en la cadena y luego concatena las dos matrices.

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
fuente
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
fuente
Con regexp esto será:Scanner scanner = new Scanner("((A+B)*C-D)*E"); scanner.useDelimiter("((?<=[\\+\\*\\-\\/\\(\\)])|(?=[\\+\\*\\-\\/\\(\\)]))"); while (scanner.hasNext()) { System.out.print(" " + scanner.next()); }
Tsolak Barseghyan
0

Una de las sutilezas de esta pregunta implica la pregunta del "delimitador principal": si va a tener una matriz combinada de tokens y delimitadores, debe saber si comienza con un token o un delimitador. Por supuesto, podría suponer que se debe descartar una delimitación principal, pero esto parece una suposición injustificada. También es posible que desee saber si tiene un delimitador final o no. Esto establece dos banderas booleanas en consecuencia.

Escrito en Groovy pero una versión de Java debería ser bastante obvia:

            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 roedor
fuente
-2

No conozco demasiado bien Java, pero si no puede encontrar un método Split que lo haga, le sugiero que haga el suyo.

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

No es demasiado elegante, pero lo hará.

Alon L
fuente
pero ¿qué pasa si tienes varios delimitadores seguidos?
Kip
FYI: Fusionada de stackoverflow.com/questions/275768/…
Shog9