Diviser la chaîne en sous-chaînes de longueur égale en Java

125

Comment diviser la chaîne "Thequickbrownfoxjumps" en sous-chaînes de taille égale en Java. Par exemple. "Thequickbrownfoxjumps"de 4 taille égale devrait donner la sortie.

["Theq","uick","brow","nfox","jump","s"]

Question similaire:

Diviser la chaîne en sous-chaînes de longueur égale dans Scala

Emil
la source
4
Qu'as-tu essayé? Pourquoi cela n'a-t-il pas fonctionné?
Thilo
2
Avez-vous besoin d'utiliser une regex pour cela? Juste demander à cause du tag regex ...
Tim Pietzcker
Le lien @Thilo qu'il a posté est pour Scala, il pose la même question à Java
Jaydeep Patel
@Thilo: Je demandais comment faire en java, comme la réponse donnée pour scala.
Emil

Réponses:

226

Voici la version regex one-liner:

System.out.println(Arrays.toString(
    "Thequickbrownfoxjumps".split("(?<=\\G.{4})")
));

\Gest une assertion de largeur nulle qui correspond à la position où la correspondance précédente s'est terminée. S'il n'y avait pas de correspondance précédente, il correspond au début de l'entrée, le même que \A. Le lookbehind englobant correspond à la position de quatre caractères à partir de la fin de la dernière correspondance.

Les deux \Gsont des fonctionnalités regex avancées, non prises en charge par toutes les versions. En outre,\G n'est pas implémenté de manière cohérente dans les versions qui le prennent en charge. Cette astuce fonctionnera (par exemple) en Java , Perl, .NET et JGSoft, mais pas en PHP (PCRE), Ruby 1.9+ ou TextMate (les deux Oniguruma). JavaScript /y(indicateur collant) n'est pas aussi flexible que \G, et ne pourrait pas être utilisé de cette façon même si JS prenait en charge lookbehind.

Je dois mentionner que je ne recommande pas nécessairement cette solution si vous avez d'autres options. Les solutions non-regex dans les autres réponses peuvent être plus longues, mais elles sont également auto-documentées; celui-ci est à peu près le contraire de cela. ;)

De plus, cela ne fonctionne pas sous Android, qui ne prend pas en charge l'utilisation de \Glookbehinds.

Alan Moore
la source
2
En PHP 5.2.4 fonctionne le code suivant: return preg_split ('/ (? <= \ G. {'. $ Len. '}) / U', $ str, -1, PREG_SPLIT_NO_EMPTY);
Igor
5
Pour mémoire, utiliser à la String.substring()place d'une expression régulière, tout en nécessitant quelques lignes de code supplémentaires, fonctionnera quelque part de l'ordre de 5 fois plus vite ...
dessiné moore le
2
En Java, cela ne fonctionne pas pour une chaîne avec des retours à la ligne. Il ne vérifiera que jusqu'à la première nouvelle ligne, et si cette nouvelle ligne se trouve avant la taille de fractionnement, la chaîne ne sera pas fractionnée. Ou ai-je manqué quelque chose?
joensson
5
Par souci d'exhaustivité: le texte sur le fractionnement multilignes a besoin d' un préfixés (?s)dans le regex: (?s)(?<=\\G.{4}).
bobbel
1
Java s'en occupe complètement au moment de la compilation:java.util.regex.PatternSyntaxException: Look-behind pattern matches must have a bounded maximum length
Jeffrey Blattman
132

Eh bien, c'est assez facile de le faire avec de simples opérations arithmétiques et de chaîne:

public static List<String> splitEqually(String text, int size) {
    // Give the list the right capacity to start with. You could use an array
    // instead if you wanted.
    List<String> ret = new ArrayList<String>((text.length() + size - 1) / size);

    for (int start = 0; start < text.length(); start += size) {
        ret.add(text.substring(start, Math.min(text.length(), start + size)));
    }
    return ret;
}

Je ne pense pas que cela vaille vraiment la peine d'utiliser une regex pour cela.

EDIT: Mon raisonnement pour ne pas utiliser une regex:

  • Cela n'utilise aucune des correspondances de modèles réelles des expressions rationnelles. C'est juste compter.
  • Je soupçonne que ce qui précède sera plus efficace, même si dans la plupart des cas cela n'aura pas d'importance
  • Si vous avez besoin d'utiliser des tailles variables à différents endroits, vous avez soit une répétition, soit une fonction d'assistance pour construire l'expression régulière elle-même en fonction d'un paramètre - ick.
  • L'expression régulière fournie dans une autre réponse n'a d'abord pas été compilée (échappement non valide), puis n'a pas fonctionné. Mon code a fonctionné la première fois. C'est plus un témoignage de la convivialité des expressions régulières par rapport au code simple, IMO.
Jon Skeet
la source
8
@Emil: En fait, vous n'avez pas demandé de regex. C'est dans les balises, mais rien dans la question elle-même ne demande une expression régulière. Vous placez cette méthode au même endroit, puis vous pouvez diviser la chaîne en une seule instruction très lisible n'importe où dans votre code.
Jon Skeet
3
Emil, ce n'est pas à ça que sert une regex. Période.
Chris
3
@Emil: Si vous voulez un one-liner pour diviser la chaîne, je recommanderais Guava Splitter.fixedLength(4)comme suggéré par seanizer.
ColinD le
2
@Jay: allez, vous n'avez pas besoin d'être aussi sarcastique.Je suis sûr que cela peut être fait en utilisant une expression régulière en une seule ligne.Une sous-chaîne de longueur fixe est également un modèle.Que dites-vous de cette réponse. stackoverflow.com/questions/3760152/… .
Emil
4
@Emil: Je n'avais pas l'intention que ce soit impoli, juste fantaisiste. La partie sérieuse de mon argument était que même si oui, je suis sûr que vous pourriez trouver un Regex pour le faire - je vois qu'Alan Moore en a un qui, selon lui, fonctionne - c'est cryptique et donc difficile pour un programmeur ultérieur comprendre et maintenir. Une solution de sous-chaîne peut être intuitive et lisible. Voir la quatrième puce de Jon Skeet: je suis d'accord avec cela à 100%.
Jay
71

C'est très simple avec Google Guava :

for(final String token :
    Splitter
        .fixedLength(4)
        .split("Thequickbrownfoxjumps")){
    System.out.println(token);
}

Production:

Theq
uick
brow
nfox
jump
s

Ou si vous avez besoin du résultat sous forme de tableau, vous pouvez utiliser ce code:

String[] tokens =
    Iterables.toArray(
        Splitter
            .fixedLength(4)
            .split("Thequickbrownfoxjumps"),
        String.class
    );

Référence:

Remarque: la construction du séparateur est montrée en ligne ci-dessus, mais comme les séparateurs sont immuables et réutilisables, il est recommandé de les stocker dans des constantes:

private static final Splitter FOUR_LETTERS = Splitter.fixedLength(4);

// more code

for(final String token : FOUR_LETTERS.split("Thequickbrownfoxjumps")){
    System.out.println(token);
}
Sean Patrick Floyd
la source
Merci pour le message (pour m'avoir fait connaître la méthode de la bibliothèque de goyave) .Mais je vais devoir accepter la réponse regex stackoverflow.com/questions/3760152/… car elle ne nécessite aucune bibliothèque tierce ni une ligne unique.
Emil
1
Inclure des centaines de Ko de code de bibliothèque juste pour effectuer cette tâche simple n'est certainement pas la bonne chose.
Jeffrey Blattman
2
@JeffreyBlattman, y compris Guava juste pour cela, c'est probablement exagéré, c'est vrai. Mais je l'utilise quand même comme une bibliothèque à usage général dans tout mon code Java, alors pourquoi ne pas utiliser cette fonctionnalité supplémentaire
Sean Patrick Floyd
un moyen de rejoindre avec un séparateur?
Aquarius Power
1
@AquariusPowerString.join(separator, arrayOrCollection)
Holger
14

Si vous utilisez les bibliothèques à usage général de goyave de Google (et très honnêtement, tout nouveau projet Java devrait probablement l' être), c'est incroyablement trivial avec la classe Splitter :

for (String substring : Splitter.fixedLength(4).split(inputString)) {
    doSomethingWith(substring);
}

et c'est tout . C'est aussi simple que ça!

Cowan
la source
8
public static String[] split(String src, int len) {
    String[] result = new String[(int)Math.ceil((double)src.length()/(double)len)];
    for (int i=0; i<result.length; i++)
        result[i] = src.substring(i*len, Math.min(src.length(), (i+1)*len));
    return result;
}
Saul
la source
Puisque src.length()et lensont tous les deux int, votre appel ceiling n'accomplit pas ce que vous voulez - voyez comment certaines des autres réponses le font: (src.length () + len - 1) / len
Michael Brewer-Davis
@Michael: Bon point. Je ne l'ai pas testé avec des chaînes de longueurs non multiples. C'est réglé maintenant.
Saul
6
public String[] splitInParts(String s, int partLength)
{
    int len = s.length();

    // Number of parts
    int nparts = (len + partLength - 1) / partLength;
    String parts[] = new String[nparts];

    // Break into parts
    int offset= 0;
    int i = 0;
    while (i < nparts)
    {
        parts[i] = s.substring(offset, Math.min(offset + partLength, len));
        offset += partLength;
        i++;
    }

    return parts;
}
Grodriguez
la source
6
Par intérêt, avez-vous quelque chose contre les forboucles?
Jon Skeet
Une forboucle est en effet une utilisation de choix plus «naturelle» pour cela :-) Merci de l'avoir signalé.
Grodriguez
3

Vous pouvez utiliser substringfrom String.class(gestion des exceptions) ou depuis Apache lang commons (il gère les exceptions pour vous)

static String   substring(String str, int start, int end) 

Mettez-le dans une boucle et vous êtes prêt à partir.

Pakore
la source
1
Quel est le problème avec la substringméthode de la Stringclasse standard ?
Grodriguez
La version commune évite les exceptions (hors limites et autres)
Thilo
7
Je vois; Je dirais que je préfère «éviter les exceptions» en contrôlant les paramètres dans le code d'appel à la place.
Grodriguez
2

Je préfère cette solution simple:

String content = "Thequickbrownfoxjumps";
while(content.length() > 4) {
    System.out.println(content.substring(0, 4));
    content = content.substring(4);
}
System.out.println(content);
Codeur guépard
la source
Ne fais pas ça! La chaîne est immuable, votre code doit donc copier toute la chaîne restante tous les 4 caractères. Votre extrait de code prend donc un temps quadratique plutôt que linéaire dans la taille de la chaîne.
Tobias
@Tobias: Même si String était mutable, cet extrait de code effectue la copie redondante mentionnée, sauf qu'il existe des processus de compilation complexes le concernant. La seule raison d'utiliser cet extrait de code est la simplicité du code.
Cheetah Coder
Avez-vous changé votre code depuis que vous l'avez publié pour la première fois? La dernière version ne fait pas réellement de copies - substring () fonctionne efficacement (temps constant, au moins sur les anciennes versions de Java); il garde une référence à la chaîne entière char [] (au moins sur les anciennes versions de Java), mais c'est très bien dans ce cas puisque vous conservez tous les caractères. Donc, le dernier code que vous avez ici est en fait correct (modulo que votre code imprime une ligne vide si le contenu commence par la chaîne vide, ce qui peut ne pas être ce que l'on souhaite).
Tobias
@Tobias: Je ne me souviens d'aucun changement.
Cheetah Coder
@Tobias l' substringimplémentation a changé avec Java 7, mise à jour 6 au milieu de 2012, lorsque les champs offsetet countont été supprimés de la Stringclasse. La complexité de substrings'est donc transformée en linéaire bien avant que cette réponse ne soit faite. Mais pour une petite chaîne comme l'exemple, elle tourne toujours assez vite et pour des chaînes plus longues… eh bien cette tâche se produit rarement en pratique.
Holger
2

Voici une implémentation en une seule ligne utilisant des flux Java8:

String input = "Thequickbrownfoxjumps";
final AtomicInteger atomicInteger = new AtomicInteger(0);
Collection<String> result = input.chars()
                                    .mapToObj(c -> String.valueOf((char)c) )
                                    .collect(Collectors.groupingBy(c -> atomicInteger.getAndIncrement() / 4
                                                                ,Collectors.joining()))
                                    .values();

Il donne la sortie suivante:

[Theq, uick, brow, nfox, jump, s]
Pankaj Singhal
la source
1
C'est une solution horrible, combattant l'intention de l'API, utilisant des fonctions avec état et étant beaucoup plus compliquée qu'une boucle ordinaire, sans parler de la surcharge de boxe et de concaténation de chaînes. Si vous voulez une solution Stream, utilisez quelque chose commeString[] result = IntStream.range(0, (input.length()+3)/4) .mapToObj(i -> input.substring(i *= 4, Math.min(i + 4, input.length()))) .toArray(String[]::new);
Holger
2

Voici une version à une ligne qui utilise Java 8 IntStream pour déterminer les index des débuts de tranche:

String x = "Thequickbrownfoxjumps";

String[] result = IntStream
                    .iterate(0, i -> i + 4)
                    .limit((int) Math.ceil(x.length() / 4.0))
                    .mapToObj(i ->
                        x.substring(i, Math.min(i + 4, x.length())
                    )
                    .toArray(String[]::new);
Marko Previsic
la source
1

Si vous voulez diviser la chaîne également en arrière, soit de droite à gauche, par exemple, de diviser 1010001111pour [10, 1000, 1111], voici le code:

/**
 * @param s         the string to be split
 * @param subLen    length of the equal-length substrings.
 * @param backwards true if the splitting is from right to left, false otherwise
 * @return an array of equal-length substrings
 * @throws ArithmeticException: / by zero when subLen == 0
 */
public static String[] split(String s, int subLen, boolean backwards) {
    assert s != null;
    int groups = s.length() % subLen == 0 ? s.length() / subLen : s.length() / subLen + 1;
    String[] strs = new String[groups];
    if (backwards) {
        for (int i = 0; i < groups; i++) {
            int beginIndex = s.length() - subLen * (i + 1);
            int endIndex = beginIndex + subLen;
            if (beginIndex < 0)
                beginIndex = 0;
            strs[groups - i - 1] = s.substring(beginIndex, endIndex);
        }
    } else {
        for (int i = 0; i < groups; i++) {
            int beginIndex = subLen * i;
            int endIndex = beginIndex + subLen;
            if (endIndex > s.length())
                endIndex = s.length();
            strs[i] = s.substring(beginIndex, endIndex);
        }
    }
    return strs;
}
Ivan Huang
la source
1

j'utilise la solution java 8 suivante:

public static List<String> splitString(final String string, final int chunkSize) {
  final int numberOfChunks = (string.length() + chunkSize - 1) / chunkSize;
  return IntStream.range(0, numberOfChunks)
                  .mapToObj(index -> string.substring(index * chunkSize, Math.min((index + 1) * chunkSize, string.length())))
                  .collect(toList());
}
rloeffel
la source
0

Solution Java 8 (comme celle-ci mais un peu plus simple):

public static List<String> partition(String string, int partSize) {
  List<String> parts = IntStream.range(0, string.length() / partSize)
    .mapToObj(i -> string.substring(i * partSize, (i + 1) * partSize))
    .collect(toList());
  if ((string.length() % partSize) != 0)
    parts.add(string.substring(string.length() / partSize * partSize));
  return parts;
}
Timofey Gorshkov
la source
-1

J'ai demandé à @Alan Moore dans un commentaire sur la solution acceptée comment les chaînes avec des sauts de ligne pouvaient être gérées. Il a suggéré d'utiliser DOTALL.

En utilisant sa suggestion, j'ai créé un petit échantillon de la façon dont cela fonctionne:

public void regexDotAllExample() throws UnsupportedEncodingException {
    final String input = "The\nquick\nbrown\r\nfox\rjumps";
    final String regex = "(?<=\\G.{4})";

    Pattern splitByLengthPattern;
    String[] split;

    splitByLengthPattern = Pattern.compile(regex);
    split = splitByLengthPattern.split(input);
    System.out.println("---- Without DOTALL ----");
    for (int i = 0; i < split.length; i++) {
        byte[] s = split[i].getBytes("utf-8");
        System.out.println("[Idx: "+i+", length: "+s.length+"] - " + s);
    }
    /* Output is a single entry longer than the desired split size:
    ---- Without DOTALL ----
    [Idx: 0, length: 26] - [B@17cdc4a5
     */


    //DOTALL suggested in Alan Moores comment on SO: https://stackoverflow.com/a/3761521/1237974
    splitByLengthPattern = Pattern.compile(regex, Pattern.DOTALL);
    split = splitByLengthPattern.split(input);
    System.out.println("---- With DOTALL ----");
    for (int i = 0; i < split.length; i++) {
        byte[] s = split[i].getBytes("utf-8");
        System.out.println("[Idx: "+i+", length: "+s.length+"] - " + s);
    }
    /* Output is as desired 7 entries with each entry having a max length of 4:
    ---- With DOTALL ----
    [Idx: 0, length: 4] - [B@77b22abc
    [Idx: 1, length: 4] - [B@5213da08
    [Idx: 2, length: 4] - [B@154f6d51
    [Idx: 3, length: 4] - [B@1191ebc5
    [Idx: 4, length: 4] - [B@30ddb86
    [Idx: 5, length: 4] - [B@2c73bfb
    [Idx: 6, length: 2] - [B@6632dd29
     */

}

Mais j'aime aussi la solution @Jon Skeets sur https://stackoverflow.com/a/3760193/1237974 . Pour la maintenabilité dans des projets plus importants où tout le monde n'a pas la même expérience des expressions régulières, j'utiliserais probablement la solution Jons.

Joensson
la source
-1

Une autre solution de force brute pourrait être,

    String input = "thequickbrownfoxjumps";
    int n = input.length()/4;
    String[] num = new String[n];

    for(int i = 0, x=0, y=4; i<n; i++){
    num[i]  = input.substring(x,y);
    x += 4;
    y += 4;
    System.out.println(num[i]);
    }

Où le code parcourt simplement la chaîne avec des sous-chaînes

Hubbly
la source
-1
    import static java.lang.System.exit;
   import java.util.Scanner;
   import Java.util.Arrays.*;


 public class string123 {

public static void main(String[] args) {


  Scanner sc=new Scanner(System.in);
    System.out.println("Enter String");
    String r=sc.nextLine();
    String[] s=new String[10];
    int len=r.length();
       System.out.println("Enter length Of Sub-string");
    int l=sc.nextInt();
    int last;
    int f=0;
    for(int i=0;;i++){
        last=(f+l);
            if((last)>=len) last=len;
        s[i]=r.substring(f,last);
     // System.out.println(s[i]);

      if (last==len)break;
       f=(f+l);
    } 
    System.out.print(Arrays.tostring(s));
    }}

Résultat

 Enter String
 Thequickbrownfoxjumps
 Enter length Of Sub-string
 4

 ["Theq","uick","brow","nfox","jump","s"]
Ravichandra
la source
-1
@Test
public void regexSplit() {
    String source = "Thequickbrownfoxjumps";
    // define matcher, any char, min length 1, max length 4
    Matcher matcher = Pattern.compile(".{1,4}").matcher(source);
    List<String> result = new ArrayList<>();
    while (matcher.find()) {
        result.add(source.substring(matcher.start(), matcher.end()));
    }
    String[] expected = {"Theq", "uick", "brow", "nfox", "jump", "s"};
    assertArrayEquals(result.toArray(), expected);
}
Adrian-Bogdan Ionescu
la source
-1

Voici ma version basée sur les flux RegEx et Java 8. Il convient de mentionner que cette Matcher.results()méthode est disponible depuis Java 9.

Test inclus.

public static List<String> splitString(String input, int splitSize) {
    Matcher matcher = Pattern.compile("(?:(.{" + splitSize + "}))+?").matcher(input);
    return matcher.results().map(MatchResult::group).collect(Collectors.toList());
}

@Test
public void shouldSplitStringToEqualLengthParts() {
    String anyValidString = "Split me equally!";
    String[] expectedTokens2 = {"Sp", "li", "t ", "me", " e", "qu", "al", "ly"};
    String[] expectedTokens3 = {"Spl", "it ", "me ", "equ", "all"};

    Assert.assertArrayEquals(expectedTokens2, splitString(anyValidString, 2).toArray());
    Assert.assertArrayEquals(expectedTokens3, splitString(anyValidString, 3).toArray());
}
Itachi
la source
-1
public static String[] split(String input, int length) throws IllegalArgumentException {

    if(length == 0 || input == null)
        return new String[0];

    int lengthD = length * 2;

    int size = input.length();
    if(size == 0)
        return new String[0];

    int rep = (int) Math.ceil(size * 1d / length);

    ByteArrayInputStream stream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_16LE));

    String[] out = new String[rep];
    byte[]  buf = new byte[lengthD];

    int d = 0;
    for (int i = 0; i < rep; i++) {

        try {
            d = stream.read(buf);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if(d != lengthD)
        {
            out[i] = new String(buf,0,d, StandardCharsets.UTF_16LE);
            continue;
        }

        out[i] = new String(buf, StandardCharsets.UTF_16LE);
    }
    return out;
}
Utilisateur8461
la source
-1
public static List<String> getSplittedString(String stringtoSplit,
            int length) {

        List<String> returnStringList = new ArrayList<String>(
                (stringtoSplit.length() + length - 1) / length);

        for (int start = 0; start < stringtoSplit.length(); start += length) {
            returnStringList.add(stringtoSplit.substring(start,
                    Math.min(stringtoSplit.length(), start + length)));
        }

        return returnStringList;
    }
Raj Hirani
la source