Como dividir uma string, mas também manter os delimitadores?

243

Eu tenho uma seqüência de caracteres multilinha que é delimitada por um conjunto de diferentes delimitadores:

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

Posso dividir essa sequência em partes, usando String.split, mas parece que não consigo obter a sequência real, que corresponde ao regex delimitador.

Em outras palavras, é isso que recebo:

  • Text1
  • Text2
  • Text3
  • Text4

É isso que eu quero

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

Existe alguma maneira do JDK de dividir a cadeia usando um regex delimitador, mas também manter os delimitadores?

Daniel Rikowski
fonte
Venha para pensar sobre onde, onde você deseja manter os delimitadores? Junto com palavras ou separado? No primeiro caso, você os anexaria à palavra anterior ou posterior? No segundo caso, a minha resposta é que você precisa ...
PhiLho
Acabei de implementar uma classe que deve ajudá-lo a alcançar o que está procurando. Veja abaixo
VonC

Respostas:

366

Você pode usar Lookahead e Lookbehind. Como isso:

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 você receberá:

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

O último é o que você quer.

((?<=;)|(?=;))é igual a para selecionar um caractere vazio antes ;ou depois ;.

Espero que isto ajude.

Os comentários do EDIT Fabian Steeg sobre a legibilidade são válidos. A legibilidade é sempre o problema do RegEx. Uma coisa que faço para ajudar a facilitar isso é criar uma variável cujo nome represente o que a regex faz e use o formato Java String para ajudar nisso. Como isso:

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

Isso ajuda um pouco. :-D

NawaMan
fonte
2
Muito agradável! Aqui podemos ver novamente o poder das expressões regulares !!
George
1
É bom ver que existe uma maneira de fazer isso com a divisão de String #, embora eu desejasse que houvesse uma maneira de incluir os delimitadores como havia para o StringTokenizer - split(";", true)seria muito mais legível do que split("((?<=;)|(?=;))").
Fabian Steeg
3
Isso deve ser: String.format(WITH_DELIMITER, ";");o formato é um método estático.
precisa saber é o seguinte
8
Uma complicação que acabei de encontrar são delimitadores de comprimento variável (digamos [\\s,]+) que você deseja corresponder completamente. As regexes necessárias ficam ainda mais longas, pois você precisa de uma visão negativa adicional {à frente, atrás} s para evitar combiná-las no meio, por exemplo. (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
Michał Politowski
3
e se eu quiser dividir por dois delimitadores? Digamos ';' ou '.'
milagre-doh
78

Você deseja usar lookarounds e dividir em correspondências com largura zero. aqui estão alguns exemplos:

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 sim, essa é uma afirmação triplamente aninhada no último padrão.

Perguntas relacionadas

Veja também

poligenelubricants
fonte
1
Observe que isso funcionará apenas para expressões relativamente simples; Eu recebi um "grupo Look-behind não tem um comprimento máximo óbvio" tentando usar isso com um regex que representa todos os números reais.
Daveagp 28/05
2
FYI: mesclado de stackoverflow.com/questions/275768/…
Shog9
30

Uma solução muito ingênua, que não envolva regex, seria executar uma substituição de string no seu delimitador ao longo das linhas de (assumindo vírgula para delimitador):

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

Onde você pode substituir o tilda (~) por um delimitador exclusivo apropriado.

Então, se você fizer uma divisão no seu novo delimitador, acredito que obterá o resultado desejado.

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

Eu realmente não gosto do outro lado, onde você recebe um elemento vazio na frente e atrás. Um delimitador geralmente não está no início ou no final da string, portanto, na maioria das vezes, você acaba desperdiçando dois bons slots de array.

Edit: casos de limite fixo. A fonte comentada com casos de teste pode ser encontrada aqui: http://snippets.dzone.com/posts/show/6453

Markus Jarderot
fonte
Wahoo ... Obrigado por participar! Abordagem interessante. Não tenho certeza de que possa ajudar de forma consistente (com isso, às vezes há um delimitador, às vezes não), mas +1 para o esforço. No entanto, você ainda precisa resolver adequadamente os casos limites (esvaziar ou valores nulos)
VonC
Convido-vos a reforçar adequadamente esta classe, completamente documentá-lo, fazer um passe com findbugs e checkstyle, e depois publicá-lo em um site trechos (para evitar congestionar desta página com toneladas de código)
VonC
Você venceu o desafio! Errr ... parabéns! Como você sabe, no segmento de desafio de código, não haveria pontos ou emblemas especiais para isso ... (suspiro): stackoverflow.com/questions/172184 . Mas obrigado por esta contribuição.
VonC 10/11/2008
@VonC Na maioria das vezes, jogar NPE no nullargumento é o caminho correto a seguir. A manipulação silenciosa leva a erros que aparecem mais tarde.
Maaartinus 15/05
@maaartinus Eu concordo, mas certamente há casos em que você deseja enviar uma mensagem mais amigável do que apenas a NPE, certo?
VonC
11

Cheguei tarde, mas, voltando à pergunta original, por que não usar apenas 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")));

resultado:

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

Edição: O que você vê acima é o que aparece na linha de comando quando executo esse código, mas agora vejo que é um pouco confuso. É difícil acompanhar quais vírgulas fazem parte do resultado e quais foram adicionadas por Arrays.toString(). O destaque da sintaxe do SO também não está ajudando. Na esperança de obter o realce para o trabalho com me em vez de contra mim, aqui está como aquelas matrizes iria procurá-lo eu estávamos declarando-os em código fonte:

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

Espero que seja mais fácil de ler. Obrigado pelo aviso, @finnw.

Alan Moore
fonte
Eu sei que parece errado - parecia errado para mim quando voltei agora, um ano depois do fato. A entrada da amostra foi mal escolhida; Vou editar a postagem e tentar esclarecer as coisas.
277 Alan Moore Alan
10

Sei que essa é uma pergunta muito antiga e a resposta também foi aceita. Ainda assim, gostaria de enviar uma resposta muito simples à pergunta original. Considere 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] + '"');
}

RESULTADO:

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"

Estou apenas usando o limite da palavra \bpara delimitar as palavras, exceto quando é o início do texto.

anubhava
fonte
1
+1 A melhor resposta para mim. mas ele não funciona para delimitadores alfanuméricos em uma seqüência alfanumérica
Casimir et Hippolyte
@CasimiretHippolyte: Obrigado pelo seu voto. Você pode fornecer uma amostra de entrada onde não funcionou.
Anubhava
2
por exemplo, isso não funciona para abcdefcom decomo delimitador, mas você pode resolver o problema usando(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
Casimir et Hippolyte
1
Note-se a primeira afirmação de evitar uma cadeia vazia no resultado quando as extremidades de cadeia com o delimitador, ou seja(?!^|$)
Casimir et Hippolyte
1
FYI: mesclado de stackoverflow.com/questions/275768/…
Shog9
9

Dei uma olhada nas respostas acima e, honestamente, nenhuma delas me parece satisfatória. O que você quer fazer é imitar a funcionalidade de divisão do Perl. Por que o Java não permite isso e tem um método join () em algum lugar está além de mim, mas discordo. Você nem precisa de uma aula para isso realmente. É apenas uma função. Execute este programa de amostra:

Algumas das respostas anteriores têm verificação nula excessiva, que eu escrevi recentemente uma resposta para uma pergunta aqui:

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

Enfim, o 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"
    }
}
cleto
fonte
Estou confuso: Java tem um método split (), que é modelado no Perl, mas muito menos poderoso. O problema aqui é que o split () do Java não oferece como retornar os delimitadores, o que você pode obter no Perl colocando o regex na captura de parênteses.
1175 Alan Alan Moore
7

Eu gosto da idéia do StringTokenizer porque é enumerável.
Mas também é obsoleto e substitui por String.split, que retorna um String [] chato (e não inclui os delimitadores).

Então, eu implementei um StringTokenizerEx que é um Iterable e que requer um verdadeiro regexp para dividir uma string.

Um regexp verdadeiro significa que não é uma 'Sequência de caracteres' repetida para formar o delimitador:
'o' corresponderá apenas a 'o' e dividirá 'ooo' em três delimitadores, com duas cadeias vazias dentro:

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

Mas o regexp o + retornará o resultado esperado ao 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();
}

O código desta classe está disponível nos DZone Snippets .

Como de costume, para uma resposta de desafio de código (uma classe independente com casos de teste incluídos), copie e cole-a (em um diretório 'src / test') e execute-a . Seu método main () ilustra os diferentes usos.


Nota: (edição de final de 2009)

O artigo Considerações finais: Java Puzzler: Splitting Hairs faz um bom trabalho explicando o comportamento bizarro de String.split().
Josh Bloch até comentou em resposta a esse artigo:

Sim, isso é uma dor. FWIW, isso foi feito por uma boa razão: compatibilidade com Perl.
O cara que fez isso é Mike "madbot" McCloskey, que agora trabalha conosco no Google. Mike garantiu que as expressões regulares do Java passassem virtualmente em todos os testes de expressão regular do 30K Perl (e corriam mais rápido).

A biblioteca comum do Google Guava também contém um divisor que é:

  • mais simples de usar
  • mantido pelo Google (e não por você)

Portanto, pode valer a pena conferir. A partir da documentação inicial inicial (pdf) :

O JDK tem isso:

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

É bom usar isso se você quiser exatamente o que ele faz: - expressão regular - resultado como uma matriz - sua maneira de lidar com peças vazias

Mini-quebra-cabeças: ", a ,, b,". Split (",") retorna ...

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

Resposta: (e) Nenhuma das opções acima.

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

Apenas vazios finais são ignorados! (Quem sabe a solução alternativa para evitar o salto? É divertido ...)

De qualquer forma, nosso Splitter é simplesmente mais flexível: o comportamento padrão é simplista:

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

Se você quiser recursos extras, peça-os!

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

A ordem dos métodos de configuração não importa - durante a divisão, o corte ocorre antes de verificar se há vazios.

VonC
fonte
6

Passe o terceiro aurgument como "true". Ele retornará delimitadores também.

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

Aqui está uma implementação simples e limpa, que é consistente Pattern#splite trabalha com padrões de comprimento variável, os quais, atrás, não podem suportar, e é mais fácil de usar. É semelhante à solução fornecida pelo @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]);
}

Eu não faço verificações nulas aqui, Pattern#splitnão, por que eu deveria. Eu não gosto ifdo final, mas é necessário para consistência com o Pattern#split. Caso contrário, eu acrescentaria incondicionalmente, resultando em uma sequência vazia como o último elemento do resultado se a sequência de entrada terminar com o padrão.

Eu converter para String [] para consistência com Pattern#split, eu uso em new String[0]vez de new String[result.size()], veja aqui porquê.

Aqui estão os meus testes:

@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
fonte
2

Vou postar minhas versões de trabalho também (primeiro é realmente semelhante ao 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()]);
}

E aqui está a segunda solução e sua rodada 50% mais rápida que a primeira:

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

Outra solução candidata usando uma regex. Mantém a ordem do token, corresponde corretamente a vários tokens do mesmo tipo em uma linha. A desvantagem é que o regex é meio desagradável.

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

Saída de amostra:

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

Não conheço uma função existente na API Java que faça isso (o que não quer dizer que não exista), mas aqui está minha própria implementação (um ou mais delimitadores serão retornados como um único token; se você quiser cada delimitador a ser retornado como um token separado, será necessário um pouco de adaptação):

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
fonte
1

Sugiro usar Pattern and Matcher, que quase certamente alcançará o que você deseja. Sua expressão regular precisará ser um pouco mais complicada do que o que você está usando no String.split.

Steve McLeod
fonte
+1, este é o caminho certo. O StringTokenizer produzirá delimitadores se você os colocar em grupos de captura, mas é essencialmente obsoleto. O uso de lookahead com split () é hacky por motivos descritos nos comentários da resposta aceita - principalmente porque se torna uma bagunça quando há mais de um delimitador. Mas você pode ter um tokenizer real em algumas linhas com Pattern e Matcher.
johncip
1

Eu não acho que é possível com String#split, mas você pode usar a StringTokenizer, embora isso não permita que você defina seu delimitador como uma expressão regular, mas apenas como uma classe de caracteres de um dígito:

new StringTokenizer("Hello, world. Hi!", ",.!", true); // true for returnDelims
Fabian Steeg
fonte
Não posso definir um regex para especificar meus delimitadores.
Daniel Rikowski
1
O StringTokenizer permite apenas delimitadores de um caractere.
Michael Borgwardt
1

Se você puder pagar, use o método replace (destino de CharSequence, substituição de CharSequence) do Java e preencha outro delimitador para dividir. Exemplo: eu quero dividir a string "boo: and: foo" e manter ':' na string direita.

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

Nota importante: Isso só funciona se você não tiver mais "novo delimitador" na sua String! Portanto, não é uma solução geral. Mas se você conhece um CharSequence do qual pode ter certeza de que ele nunca aparecerá na String, esta é uma solução muito simples.

Stephan
fonte
0

Resposta rápida: use limites não físicos como \ b para dividir. Vou tentar experimentar para ver se funciona (usado no PHP e JS).

É possível, e tipo de trabalho, mas pode dividir demais. Na verdade, isso depende da string que você deseja dividir e do resultado que você precisa. Dê mais detalhes, nós o ajudaremos melhor.

Outra maneira é fazer sua própria divisão, capturando o delimitador (supondo que seja variável) e adicionando-o posteriormente ao resultado.

Meu teste rápido:

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|'|

Um pouco demais ... :-)

PhiLho
fonte
0

Tweaked Pattern.split () para incluir o padrão correspondente na lista

Adicionado

// 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);
}
Prashant Bhate
fonte
0

Aqui está uma versão interessante com base em alguns dos códigos acima, caso isso ajude. É curto, pelo menos. Inclui condicionalmente a cabeça e a cauda (se não estiverem vazias). A última parte é um caso de demonstração / teste.

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)
}
milhas zarathustra
fonte
0

Uma solução extremamente ingênua e ineficiente que funciona, no entanto. Use dividir duas vezes na string e concatenar as duas matrizes

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

Uma das sutilezas desta pergunta envolve a pergunta "delimitador principal": se você deseja ter uma matriz combinada de tokens e delimitadores, precisa saber se ele começa com um token ou um delimitador. Obviamente, você pode simplesmente assumir que um delim principal deve ser descartado, mas isso parece uma suposição injustificada. Você também pode querer saber se possui ou não um delim à direita. Isso define dois sinalizadores booleanos de acordo.

Escrito em Groovy, mas uma versão Java deve ser bastante óbvia:

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

            }
microfone roedor
fonte
-2

Eu não conheço Java muito bem, mas se você não conseguir encontrar um método Split que faça isso, sugiro que você faça o seu próprio.

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

Não é muito elegante, mas serve.

Alon L
fonte
mas e se você tiver vários delimitadores seguidos?
Kip