Dividi la stringa in sottostringhe di uguale lunghezza in Java


125

Come dividere la stringa "Thequickbrownfoxjumps"in sottostringhe di uguale dimensione in Java. Per esempio. "Thequickbrownfoxjumps"di 4 dimensioni uguali dovrebbe dare l'output.

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

Domanda simile:

Dividi la stringa in sottostringhe di uguale lunghezza in Scala


4
Cosa hai provato Perché non ha funzionato?
Thilo,

2
Devi usare una regex per questo?
Sto

Il link @Thilo che ha pubblicato è per Scala, lo sta chiedendo a proposito di Java
Jaydeep Patel,

@Thilo: stavo chiedendo cosa fare a java, come la risposta data per scala.
Emil,

Risposte:


226

Ecco la versione regex one-liner:

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

\Gè un'asserzione di larghezza zero che corrisponde alla posizione in cui è terminata la corrispondenza precedente. Se non vi era alcuna corrispondenza precedente, corrisponde all'inizio dell'input, lo stesso di \A. Il look racchiuso corrisponde alla posizione di quattro caratteri dalla fine dell'ultima partita.

Sia lookbehind sia \Gfunzionalità regex avanzate, non supportate da tutti i gusti. Inoltre, \Gnon viene implementato in modo coerente in tutti i sapori che lo supportano. Questo trucco funzionerà (ad esempio) in Java , Perl, .NET e JGSoft, ma non in PHP (PCRE), Ruby 1.9+ o TextMate (entrambi Oniguruma). JavaScript /y(bandiera adesiva) non è flessibile come \G, e non potrebbe essere utilizzato in questo modo anche se JS supportava il lookbehind.

Devo dire che non consiglio necessariamente questa soluzione se hai altre opzioni. Le soluzioni non regex nelle altre risposte potrebbero essere più lunghe, ma sono anche auto-documentanti; questo è quasi l' opposto di quello. ;)

Inoltre, questo non funziona in Android, che non supporta l'uso di \Glookbehinds.


2
In PHP 5.2.4 funziona il seguente codice: return preg_split ('/ (? <= \ G. {'. $ Len. '}) / U', $ str, -1, PREG_SPLIT_NO_EMPTY);
Igor,

5
Per la cronaca, usando al String.substring()posto di un regex, pur richiedendo alcune righe di codice in più, verrà eseguito da qualche parte nell'ordine di 5 volte più veloce ...
Drew Moore,

2
In Java questo non funziona per una stringa con newline. Verificherà solo fino alla prima riga nuova e, se quella riga si trova prima della dimensione divisa, la stringa non verrà divisa. O ho perso qualcosa?
joensson,

5
Per ragioni di completezza: il testo scissione sopra multilinee ha bisogno di un prefisso (?s)nel regex: (?s)(?<=\\G.{4}).
Bob

1
Java ne parla completamente al momento della compilazione:java.util.regex.PatternSyntaxException: Look-behind pattern matches must have a bounded maximum length
Jeffrey Blattman

132

Bene, è abbastanza facile farlo con semplici operazioni aritmetiche e di stringa:

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

Non penso che valga davvero la pena usare una regex per questo.

EDIT: il mio ragionamento per non usare un regex:

  • Questo non usa nessuna delle corrispondenze reali dei regex. Sta solo contando.
  • Ho il sospetto che quanto sopra sarà più efficiente, anche se nella maggior parte dei casi non importa
  • Se hai bisogno di usare dimensioni variabili in luoghi diversi, hai la ripetizione o una funzione di aiuto per costruire la regex stessa sulla base di un parametro - ick.
  • Il regex fornito in un'altra risposta inizialmente non è stato compilato (escape non valido), quindi non ha funzionato. Il mio codice ha funzionato la prima volta. È più una testimonianza dell'usabilità delle regex rispetto al codice semplice, IMO.

8
@Emil: In realtà, non hai chiesto una regex. È nei tag, ma nulla nella domanda stessa richiede una regex. Metti questo metodo in un posto e poi puoi dividere la stringa in una sola istruzione molto leggibile in qualsiasi punto del tuo codice.
Jon Skeet,

3
Emil, questo non è ciò che serve per una regex. Periodo.
Chris,

3
@Emil: Se vuoi un liner per dividere la stringa, consiglierei Guava Splitter.fixedLength(4)come suggerito da Seanizer.
ColinD,

2
@Jay: dai non devi essere così sarcastico. Sono sicuro che si può fare usando regex in una sola riga. Anche una sottostringa a lunghezza fissa è uno schema. Cosa dici di questa risposta. stackoverflow.com/questions/3760152/… .
Emil,

4
@Emil: non intendevo che fosse scortese, solo stravagante. La parte seria del mio punto era che mentre sì, sono sicuro che potresti inventare un Regex per farlo - vedo che Alan Moore ne ha uno che sostiene funziona - è criptico e quindi difficile per un programmatore successivo capire e mantenere. Una soluzione di sottostringa può essere intuitiva e leggibile. Vedi il quarto proiettile di Jon Skeet: sono d'accordo con quel 100%.
Jay,

71

Questo è molto semplice con Google Guava :

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

Produzione:

Theq
uick
brow
nfox
jump
s

O se hai bisogno del risultato come un array, puoi usare questo codice:

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

Riferimento:

Nota: la costruzione di Splitter è mostrata in linea sopra, ma poiché gli Splitter sono immutabili e riutilizzabili, è buona norma conservarli in costanti:

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

// more code

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

Grazie per il post (per avermi fatto conoscere il metodo della libreria guava). Ma dovrò accettare la risposta regex stackoverflow.com/questions/3760152/… poiché non richiede alcuna libreria di terze parti e una riga.
Emil,

1
Includere centinaia di KB di codice di libreria solo per eseguire questa semplice operazione non è quasi certamente la cosa giusta.
Jeffrey Blattman,

2
@JeffreyBlattman incluso Guava solo per questo è probabilmente eccessivo, vero. Ma lo uso comunque come libreria per tutti gli usi in tutto il mio codice Java, quindi perché non usare questa funzionalità aggiuntiva
Sean Patrick Floyd,

un modo per unirti di nuovo con un separatore?
Aquarius Power

1
@AquariusPowerString.join(separator, arrayOrCollection)
Holger

14

Se stai usando le librerie di guava generiche di Google (e, onestamente, probabilmente dovrebbe esserlo qualsiasi nuovo progetto Java ), questo è follemente banale con la classe Splitter :

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

e basta . Facile come!


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

Dato src.length()che lensono entrambi ints, la tua chiamata ceiling non sta realizzando quello che vuoi - controlla come alcune delle altre risposte lo stanno facendo: (src.length () + len - 1) / len
Michael Brewer-Davis

@Michael: buon punto. Non l'ho provato con stringhe di lunghezze non multiple. Ora è riparato.
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;
}

6
Per interesse, hai qualcosa contro i forloop?
Jon Skeet,

Un forloop è davvero un uso più 'naturale' per questo :-) Grazie per averlo sottolineato.
Grodriguez,

3

Puoi usare substringda String.class(gestire le eccezioni) o dai comuni di Apache (gestisce le eccezioni per te)

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

Mettilo in un loop e sei a posto.


1
Cosa c'è di sbagliato con il substringmetodo nella Stringclasse standard ?
Grodriguez,

La versione commons evita le eccezioni (fuori limite e simili)
Thilo

7
Vedo; Direi che preferisco "evitare le eccezioni" controllando invece i parametri nel codice chiamante.
Grodriguez,

2

Preferirei questa semplice soluzione:

String content = "Thequickbrownfoxjumps";
while(content.length() > 4) {
    System.out.println(content.substring(0, 4));
    content = content.substring(4);
}
System.out.println(content);

Non farlo! String è immutabile, quindi il tuo codice deve copiare l'intera stringa rimanente ogni 4 caratteri. Il tuo frammento pertanto richiede un tempo quadratico anziché lineare nella dimensione della stringa.
Tobias,

@Tobias: anche se String era mutabile, questo frammento esegue la copia ridondante menzionata, tranne per il fatto che esistono complessi processi di compilazione che lo riguardano. L'unico motivo per utilizzare questo frammento è la semplicità del codice.
Cheetah Coder,

Hai cambiato il tuo codice da quando l'hai pubblicato per la prima volta? L'ultima versione in realtà non crea copie - substring () funziona in modo efficiente (tempo costante, almeno su vecchie versioni di Java); mantiene un riferimento all'intero carattere della stringa [] (almeno sulle vecchie versioni di Java), ma va bene in questo caso poiché stai mantenendo tutti i caratteri. Quindi l'ultimo codice che hai qui è effettivamente a posto (modulo che il tuo codice stampa una riga vuota se il contenuto inizia come stringa vuota, che potrebbe non essere ciò che si intende).
Tobias,

@Tobias: non ricordo alcun cambiamento.
Cheetah Coder,

@Tobias l' substringimplementazione è cambiata con Java 7, aggiornamento 6 a metà del 2012, quando i campi offsete countsono stati rimossi dalla Stringclasse. Quindi la complessità di substringdiventare lineare molto prima che questa risposta fosse fatta. Ma per una stringa piccola come nell'esempio, funziona ancora abbastanza velocemente e per stringhe più lunghe ... beh, questo compito si verifica raramente in pratica.
Holger,

2

Ecco un'implementazione one liner che utilizza i flussi 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();

Fornisce il seguente output:

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

1
Questa è una soluzione orribile, combattere l'intenzione dell'API, utilizzare funzioni stateful ed essere significativamente più complicata di un normale ciclo, per non parlare del sovraccarico di boxe e di concatenazione di stringhe. Se vuoi una soluzione Stream, usa qualcosa comeString[] 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

Ecco una versione one-liner che utilizza Java 8 IntStream per determinare gli indici degli inizi della sezione:

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

1

Nel caso in cui si desideri dividere la stringa equamente all'indietro, cioè da destra a sinistra, ad esempio, per dividere 1010001111in [10, 1000, 1111], ecco il codice:

/**
 * @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;
}

1

io uso la seguente soluzione java 8:

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

0

Soluzione Java 8 (come questa, ma un po 'più semplice):

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

-1

Ho chiesto a @Alan Moore in un commento alla soluzione accettata come gestire le stringhe con newline. Ha suggerito di usare DOTALL.

Usando il suo suggerimento ho creato un piccolo esempio di come funziona:

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

}

Ma mi piace anche la soluzione @Jon Skeets in https://stackoverflow.com/a/3760193/1237974 . Per la manutenibilità in progetti più grandi in cui non tutti hanno ugualmente esperienza nelle espressioni regolari, probabilmente utilizzerei la soluzione Jons.


-1

Un'altra soluzione di forza bruta potrebbe essere,

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

Dove il codice attraversa semplicemente la stringa con sottostringhe


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

Risultato

 Enter String
 Thequickbrownfoxjumps
 Enter length Of Sub-string
 4

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

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

-1

Ecco la mia versione basata su stream RegEx e Java 8. Vale la pena ricordare che il Matcher.results()metodo è disponibile da Java 9.

Test incluso.

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

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

-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;
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.