Comment diviser une chaîne, mais aussi conserver les délimiteurs?

243

J'ai une chaîne multiligne qui est délimitée par un ensemble de délimiteurs différents:

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

Je peux diviser cette chaîne en ses parties en utilisant String.split, mais il semble que je ne puisse pas obtenir la chaîne réelle, qui correspondait à l'expression rationnelle du délimiteur.

En d'autres termes, voici ce que j'obtiens:

  • Text1
  • Text2
  • Text3
  • Text4

C'est ce que je veux

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

Existe-t-il un moyen JDK de diviser la chaîne à l'aide d'une expression régulière de délimiteur, mais également de conserver les délimiteurs?

Daniel Rikowski
la source
À bien y penser, où voulez-vous garder les délimiteurs? Avec des mots ou séparés? Dans le premier cas, les rattacheriez-vous au mot précédent ou suivant? Dans le deuxième cas, ma réponse est ce dont vous avez besoin ...
PhiLho
Je viens de mettre en place une classe qui devrait vous aider à réaliser ce que vous recherchez. Voir ci
VonC

Réponses:

366

Vous pouvez utiliser Lookahead et Lookbehind. Comme ça:

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

Et vous obtiendrez:

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

Le dernier est ce que vous voulez.

((?<=;)|(?=;))équivaut à sélectionner un caractère vide avant ;ou après ;.

J'espère que cela t'aides.

EDIT Les commentaires de Fabian Steeg sur la lisibilité sont valides. La lisibilité est toujours le problème pour RegEx. Une chose que je fais pour faciliter cela est de créer une variable dont le nom représente ce que fait l'expression régulière et utilise le format Java String pour aider cela. Comme ça:

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

Cela aide un peu. :-RÉ

NawaMan
la source
2
Très agréable! Ici, nous pouvons voir à nouveau la puissance des expressions régulières !!
George
1
C'est bien de voir qu'il existe un moyen de le faire avec String # split, bien que j'aimerais qu'il y ait un moyen d'inclure les délimiteurs comme il y en avait pour StringTokenizer - split(";", true)serait tellement plus lisible que split("((?<=;)|(?=;))").
Fabian Steeg
3
Cela devrait être: String.format(WITH_DELIMITER, ";");car le format est une méthode statique.
john16384
8
Une complication que je viens de rencontrer est les délimiteurs de longueur variable (disons [\\s,]+) que vous voulez faire correspondre complètement. Les expressions rationnelles requises deviennent encore plus longues, car vous avez besoin d'un regard négatif supplémentaire {devant, derrière} pour éviter de les faire correspondre au milieu, par exemple. (?<=[\\s,]+)(?![\\s,])|(?<![\\s,])(?=[\\s,]+).
Michał Politowski
3
que faire si je veux être divisé par deux délimiteurs? Disons ';' ou '.'
miracle-doh
78

Vous souhaitez utiliser des contournements et fractionner sur des correspondances de largeur nulle. Voici quelques exemples:

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

Et oui, c'est là une affirmation triplement imbriquée dans le dernier schéma.

Questions connexes

Voir également

polygénelubrifiants
la source
1
Notez que cela ne fonctionnera que pour des expressions relativement simples; J'ai obtenu un "groupe de recherche qui n'a pas de longueur maximale évidente" en essayant de l'utiliser avec une expression régulière représentant tous les nombres réels.
daveagp
2
FYI: Fusionné de stackoverflow.com/questions/275768/…
Shog9
30

Une solution très naïve, qui n'implique pas l'expression régulière, serait d'effectuer un remplacement de chaîne sur votre délimiteur dans le sens (en supposant une virgule pour le délimiteur):

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

Où vous pouvez remplacer tilda (~) par un délimiteur unique approprié.

Ensuite, si vous divisez votre nouveau délimiteur, je pense que vous obtiendrez le résultat souhaité.

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

Je n'aime pas vraiment l'autre façon, où vous obtenez un élément vide devant et derrière. Un délimiteur n'est généralement pas au début ou à la fin de la chaîne, donc vous finissez le plus souvent par perdre deux bons emplacements de tableau.

Modifier: cas limites fixes. La source commentée avec des cas de test peut être trouvée ici: http://snippets.dzone.com/posts/show/6453

Markus Jarderot
la source
Wahoo ... Merci d'avoir participé! Approche intéressante. Je ne suis pas sûr que cela puisse être une aide cohérente (avec cela, parfois il y a un délimiteur, parfois il n'y en a pas), mais +1 pour l'effort. Cependant, vous devez toujours traiter correctement les cas limites (valeurs vides ou nulles)
VonC
Je vous invite à renforcer correctement cette classe, à la documenter de manière approfondie, à effectuer une passe avec findbugs et checkstyle, puis à la publier sur un site Web d'extraits de code (pour éviter d'encombrer cette page avec des tonnes de code)
VonC
Vous avez remporté le défi! Euh ... félicitations! Comme vous le savez, à partir du fil de défi de code, il n'y aurait pas de points spéciaux ou de badges pour cela ... (soupir): stackoverflow.com/questions/172184 . Mais merci pour cette contribution.
VonC
@VonC La plupart du temps, lancer NPE sur un nullargument est la bonne façon de procéder. Une manipulation silencieuse entraîne des erreurs apparaissant plus tard.
maaartinus
@maaartinus Je suis d'accord, mais il y a sûrement des cas où vous voulez lancer un message plus convivial que juste NPE, non?
VonC
11

Je suis arrivé en retard, mais pour revenir à la question d'origine, pourquoi ne pas simplement utiliser des contournements?

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

production:

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

EDIT: Ce que vous voyez ci-dessus est ce qui apparaît sur la ligne de commande lorsque j'exécute ce code, mais je vois maintenant que c'est un peu déroutant. Il est difficile de savoir quelles virgules font partie du résultat et lesquelles ont été ajoutées par Arrays.toString(). La mise en évidence de la syntaxe de SO n'aide pas non plus. Dans l'espoir que la mise en évidence fonctionne avec moi plutôt que contre moi, voici à quoi ressembleraient ces tableaux, je les déclarais dans le code source:

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

J'espère que c'est plus facile à lire. Merci pour l'avertissement, @finnw.

Alan Moore
la source
Je sais que ça ne va pas - ça me semblait mal quand j'y reviens tout à l'heure, un an après les faits. L'échantillon d'entrée a été mal choisi; Je vais éditer le message et essayer de clarifier les choses.
Alan Moore
FYI: Fusionné de stackoverflow.com/questions/275768/…
Shog9
10

Je sais que c'est une question très très ancienne et la réponse a également été acceptée. Mais je voudrais quand même soumettre une réponse très simple à la question d'origine. Considérez ce code:

String str = "Hello-World:How\nAre You&doing";
inputs = str.split("(?!^)\\b");
for (int i=0; i<inputs.length; i++) {
   System.out.println("a[" + i + "] = \"" + inputs[i] + '"');
}

PRODUCTION:

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"

J'utilise juste la limite de mot \bpour délimiter les mots sauf quand c'est le début du texte.

anubhava
la source
1
+1 La meilleure réponse pour moi. mais cela ne fonctionne pas pour les délimiteurs alphanumériques dans une chaîne alphanumérique
Casimir et Hippolyte
@CasimiretHippolyte: Merci pour votre vote positif. Pouvez-vous s'il vous plaît fournir un exemple d'entrée où cela n'a pas fonctionné.
anubhava
2
par exemple, cela ne fonctionne pas abcdefavec decomme délimiteur, mais vous pouvez résoudre le problème en utilisant(?!^|$)(?:(?<=de)(?!de)|(?<!de)(?=de))
Casimir et Hippolyte
1
Notez la première assertion pour éviter une chaîne vide dans le résultat lorsque la chaîne se termine par le délimiteur, c'est(?!^|$)
Casimir et Hippolyte
1
FYI: Fusionné de stackoverflow.com/questions/275768/…
Shog9
9

J'ai jeté un œil aux réponses ci-dessus et honnêtement, je ne trouve aucune réponse satisfaisante. Ce que vous voulez faire, c'est essentiellement imiter la fonctionnalité de partage Perl. Pourquoi Java ne permet pas cela et a une méthode join () quelque part me dépasse mais je m'éloigne du sujet. Vous n'avez même pas vraiment besoin d'un cours pour ça. C'est juste une fonction. Exécutez cet exemple de programme:

Certaines des réponses précédentes ont un contrôle nul excessif, auquel j'ai récemment écrit une réponse à une question ici:

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

Quoi qu'il en soit, le code:

public class Split {
    public static List<String> split(String s, String pattern) {
        assert s != null;
        assert pattern != null;
        return split(s, Pattern.compile(pattern));
    }

    public static List<String> split(String s, Pattern pattern) {
        assert s != null;
        assert pattern != null;
        Matcher m = pattern.matcher(s);
        List<String> ret = new ArrayList<String>();
        int start = 0;
        while (m.find()) {
            ret.add(s.substring(start, m.start()));
            ret.add(m.group());
            start = m.end();
        }
        ret.add(start >= s.length() ? "" : s.substring(start));
        return ret;
    }

    private static void testSplit(String s, String pattern) {
        System.out.printf("Splitting '%s' with pattern '%s'%n", s, pattern);
        List<String> tokens = split(s, pattern);
        System.out.printf("Found %d matches%n", tokens.size());
        int i = 0;
        for (String token : tokens) {
            System.out.printf("  %d/%d: '%s'%n", ++i, tokens.size(), token);
        }
        System.out.println();
    }

    public static void main(String args[]) {
        testSplit("abcdefghij", "z"); // "abcdefghij"
        testSplit("abcdefghij", "f"); // "abcde", "f", "ghi"
        testSplit("abcdefghij", "j"); // "abcdefghi", "j", ""
        testSplit("abcdefghij", "a"); // "", "a", "bcdefghij"
        testSplit("abcdefghij", "[bdfh]"); // "a", "b", "c", "d", "e", "f", "g", "h", "ij"
    }
}
cletus
la source
Je suis confus: Java a une méthode split (), qui est modelée sur Perl, mais beaucoup moins puissante. Le problème ici est que le split () de Java ne fournit aucun moyen de renvoyer les délimiteurs, ce que vous pouvez obtenir en Perl en enfermant l'expression régulière dans la capture des parenthèses.
Alan Moore
FYI: Fusionné de stackoverflow.com/questions/275768/…
Shog9
7

J'aime l'idée de StringTokenizer car elle est énumérable.
Mais il est également obsolète et remplacé par String.split qui retourne un String [] ennuyeux (et n'inclut pas les délimiteurs).

J'ai donc implémenté un StringTokenizerEx qui est un Iterable, et qui prend une vraie expression rationnelle pour diviser une chaîne.

Une expression rationnelle vraie signifie qu'il ne s'agit pas d'une 'séquence de caractères' répétée pour former le délimiteur:
'o' ne correspondra qu'à 'o' et divisera 'ooo' en trois délimiteurs, avec deux chaînes vides à l'intérieur:

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

Mais l'expression régulière o + renverra le résultat attendu lors de la division de "aooob"

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

Pour utiliser ce 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();
}

Le code de cette classe est disponible sur DZone Snippets .

Comme d'habitude pour une réponse de défi de code (une classe autonome avec cas de test inclus), copiez-collez-la (dans un répertoire 'src / test') et exécutez-la . Sa méthode main () illustre les différents usages.


Remarque: (édition fin 2009)

L'article Réflexions finales: Java Puzzler: Splitting Hairs explique bien le comportement bizarre de String.split().
Josh Bloch a même commenté en réponse à cet article:

Oui, c'est pénible. FWIW, cela a été fait pour une très bonne raison: la compatibilité avec Perl.
Le gars qui l'a fait est Mike "madbot" McCloskey, qui travaille maintenant avec nous chez Google. Mike s'est assuré que les expressions régulières de Java passaient pratiquement tous les tests d'expression régulière de 30K Perl (et s'exécutaient plus rapidement).

La bibliothèque commune Google Guava contient également un Splitter qui est:

  • plus simple à utiliser
  • maintenu par Google (et non par vous)

Il peut donc être utile de vérifier. D'après leur documentation initiale (pdf) :

JDK a ceci:

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

C'est bien de l'utiliser si vous voulez exactement ce qu'il fait: - expression régulière - résultat sous forme de tableau - sa façon de gérer les pièces vides

Mini-puzzler: ", a ,, b,". Split (",") renvoie ...

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

Réponse: (e) Aucune des réponses ci-dessus.

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

Seuls les vides de fin sont ignorés! (Qui connaît la solution pour éviter le saut? C'est amusant ...)

Dans tous les cas, notre Splitter est simplement plus flexible: le comportement par défaut est simpliste:

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

Si vous voulez des fonctionnalités supplémentaires, demandez-les!

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

L'ordre des méthodes de configuration n'a pas d'importance - pendant le fractionnement, le découpage a lieu avant de rechercher les vides.

VonC
la source
FYI: Fusionné de stackoverflow.com/questions/275768/…
Shog9
6

Passez le 3ème aurgument comme "vrai". Il renverra également les délimiteurs.

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

Voici une implémentation simple et propre qui est cohérente Pattern#splitet fonctionne avec des modèles de longueur variable, qui ne peuvent pas être pris en charge par l'arrière, et elle est plus facile à utiliser. Elle est similaire à la solution fournie par @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]);
}

Je ne fais pas de vérifications nulles ici, Pattern#splitn'est-ce pas, pourquoi devrais-je le faire. Je n'aime pas le ifà la fin mais c'est nécessaire pour la cohérence avec le Pattern#split. Sinon, je voudrais ajouter inconditionnellement, ce qui entraîne une chaîne vide comme dernier élément du résultat si la chaîne d'entrée se termine par le modèle.

Je convertis en String [] pour plus de cohérence avec Pattern#split, j'utilise new String[0]plutôt que new String[result.size()], voir ici pourquoi.

Voici mes tests:

@Test
public void splitsVariableLengthPattern() {
    String[] result = Split.split("/foo/$bar/bas", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/", "$bar", "/bas" }, result);
}

@Test
public void splitsEndingWithPattern() {
    String[] result = Split.split("/foo/$bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/", "$bar" }, result);
}

@Test
public void splitsStartingWithPattern() {
    String[] result = Split.split("$foo/bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "", "$foo", "/bar" }, result);
}

@Test
public void splitsNoMatchesPattern() {
    String[] result = Split.split("/foo/bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/bar" }, result);
}
julien
la source
2

Je publierai également mes versions de travail (la première est vraiment similaire à 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()]);
}

Et voici la deuxième solution et sa ronde 50% plus rapide que la première:

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
la source
2

Une autre solution candidate utilisant une expression régulière. Conserve l'ordre des jetons, correspond correctement à plusieurs jetons du même type d'affilée. L'inconvénient est que le regex est un peu méchant.

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

Exemple de sortie:

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

Je ne connais pas de fonction existante dans l'API Java qui fait cela (ce qui ne veut pas dire qu'elle n'existe pas), mais voici ma propre implémentation (un ou plusieurs délimiteurs seront retournés comme un seul jeton; si vous voulez chaque délimiteur doit être retourné en tant que jeton séparé, il faudra un peu d'adaptation):

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
la source
FYI: Fusionné de stackoverflow.com/questions/275768/…
Shog9
1

Je suggère d'utiliser Pattern and Matcher, qui permettra presque certainement d'atteindre ce que vous voulez. Votre expression régulière devra être un peu plus compliquée que celle que vous utilisez dans String.split.

Steve McLeod
la source
+1, c'est la bonne façon. StringTokenizer affichera des délimiteurs si vous les placez dans des groupes de capture, mais il est essentiellement obsolète. L'utilisation de l'anticipation avec split () est difficile à comprendre pour les raisons décrites dans les commentaires de la réponse acceptée - principalement que cela devient un gâchis lorsqu'il y a plus d'un délimiteur. Mais vous pouvez avoir un véritable tokenizer en quelques lignes avec Pattern et Matcher.
johncip
1

Je ne pense pas que ce soit possible avec String#split, mais vous pouvez utiliser un StringTokenizer, bien que cela ne vous permette pas de définir votre délimiteur comme une expression régulière, mais uniquement comme une classe de caractères à un chiffre:

new StringTokenizer("Hello, world. Hi!", ",.!", true); // true for returnDelims
Fabian Steeg
la source
Là, je ne peux pas définir une expression régulière pour spécifier mes délimiteurs.
Daniel Rikowski
1
StringTokenizer n'autorise cependant que les délimiteurs à un seul caractère.
Michael Borgwardt
1

Si vous pouvez vous le permettre, utilisez la méthode Java replace (CharSequence target, CharSequence replacement) et remplissez un autre délimiteur pour vous séparer. Exemple: je veux diviser la chaîne "boo: and: foo" et garder ':' à sa chaîne de droite.

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

Remarque importante: cela ne fonctionne que si vous n'avez plus de "nouveau délimiteur" dans votre chaîne! Ce n'est donc pas une solution générale. Mais si vous connaissez une CharSequence dont vous pouvez être sûr qu'elle n'apparaîtra jamais dans la chaîne, c'est une solution très simple.

Stephan
la source
FYI: Fusionné de stackoverflow.com/questions/275768/…
Shog9
0

Réponse rapide: utilisez des limites non physiques comme \ b pour diviser. Je vais essayer et expérimenter pour voir si ça marche (utilisé ça en PHP et JS).

C'est possible, et genre de travail, mais ça risque de se diviser trop. En fait, cela dépend de la chaîne que vous souhaitez diviser et du résultat dont vous avez besoin. Donnez plus de détails, nous vous aiderons mieux.

Une autre façon est de faire votre propre fractionnement, de capturer le délimiteur (en supposant qu'il soit variable) et de l'ajouter ensuite au résultat.

Mon test rapide:

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

Résultat:

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

Un peu trop... :-)

PhiLho
la source
FYI: Fusionné de stackoverflow.com/questions/275768/…
Shog9
0

Tweaked Pattern.split () pour inclure le motif correspondant à la liste

Ajoutée

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

Source complète

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
la source
FYI: Fusionné de stackoverflow.com/questions/275768/…
Shog9
0

Voici une version groovy basée sur une partie du code ci-dessus, au cas où cela aiderait. C'est court, en tout cas. Comprend conditionnellement la tête et la queue (si elles ne sont pas vides). La dernière partie est une démonstration / 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)
}
miles zarathustra
la source
FYI: Fusionné de stackoverflow.com/questions/275768/…
Shog9
0

Une solution extrêmement naïve et inefficace qui fonctionne quand même: utilisez le split deux fois sur la chaîne puis concaténez les deux tableaux

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

L'une des subtilités de cette question implique la question "délimiteur principal": si vous allez avoir un tableau combiné de jetons et de délimiteurs, vous devez savoir s'il commence par un jeton ou un délimiteur. Vous pouvez bien sûr simplement supposer qu'un délimage de tête doit être jeté, mais cela semble une hypothèse injustifiée. Vous voudrez peut-être également savoir si vous avez un délimiteur de fin ou non. Cela définit deux drapeaux booléens en conséquence.

Écrit en Groovy mais une version Java devrait être assez évidente:

            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 rongeur
la source
-2

Je ne connais pas trop bien Java, mais si vous ne trouvez pas une méthode Split qui fait ça, je vous suggère de faire la vôtre.

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

Ce n'est pas trop élégant, mais ça ira.

Alon L
la source
mais que faire si vous avez plusieurs délimiteurs de suite?
Kip
FYI: Fusionné de stackoverflow.com/questions/275768/…
Shog9