Cadena dividida en subcadenas de igual longitud en Java

Cómo dividir la cadena "Thequickbrownfoxjumps"en subcadenas de igual tamaño en Java. P.ej. "Thequickbrownfoxjumps"de 4 de igual tamaño debe dar la salida.

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

Pregunta similar:

Dividir cadena en subcadenas de igual longitud en Scala

Answer

Aquí está la versión de una sola línea de expresiones regulares:

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

\Ges una aserción de ancho cero que coincide con la posición donde terminó la coincidencia anterior. Si no hubo una coincidencia anterior, coincide con el comienzo de la entrada, al igual que \A. El lookbehind adjunto coincide con la posición que está a cuatro caracteres del final de la última coincidencia.

Ambos miran atrás y \Gson características avanzadas de expresiones regulares, no compatibles con todos los sabores. Además, \Gno se implementa de manera consistente en todos los sabores que lo admiten. Este truco funcionará (por ejemplo) en Java , Perl, .NET y JGSoft, pero no en PHP (PCRE), Ruby 1.9+ o TextMate (ambos Oniguruma). JavaScript /y(bandera adhesiva) no es tan flexible como \G, y no podría usarse de esta manera, incluso si JS admitiera mirar atrás.

Debo mencionar que no recomiendo necesariamente esta solución si tiene otras opciones. Las soluciones que no son expresiones regulares en las otras respuestas pueden ser más largas, pero también se autodocumentan; este es casi lo opuesto a eso. ;)

Además, esto no funciona en Android, que no admite el uso de \Glookbehinds.

Bueno, es bastante fácil hacer esto con operaciones aritméticas y de cadenas simples:

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

Nota: esto supone un mapeo 1:1 de la unidad de código UTF-16 ( char, efectivamente) con "carácter". Esa suposición se rompe para los caracteres fuera del plano multilingüe básico, como los emoji y (dependiendo de cómo quieras contar las cosas) la combinación de caracteres.

No creo que realmente valga la pena usar una expresión regular para esto.

EDITAR: Mi razonamiento para no usar una expresión regular:

  • Esto no utiliza ninguna de las coincidencias de patrones reales de expresiones regulares. Solo está contando.
  • Sospecho que lo anterior será más eficiente, aunque en la mayoría de los casos no importará
  • Si necesita usar tamaños variables en diferentes lugares, tiene repetición o una función auxiliar para construir la expresión regular en sí misma en función de un parámetro, ick.
  • La expresión regular proporcionada en otra respuesta primero no se compiló (escape no válido) y luego no funcionó. Mi código funcionó a la primera. Eso es más un testimonio de la usabilidad de las expresiones regulares frente al código simple, en mi opinión.

Esto es muy fácil con Google Guayaba :

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

Producción:

Theq
uick
brow
nfox
jump
s

O si necesita el resultado como una matriz, puede usar este código:

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

Referencia:

Nota: la construcción de divisores se muestra en línea arriba, pero dado que los divisores son inmutables y reutilizables, es una buena práctica almacenarlos en constantes:

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

// more code

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

Si está utilizando las bibliotecas de uso general de guayaba de Google (y, sinceramente, cualquier proyecto Java nuevo probablemente debería serlo), esto es increíblemente trivial con la clase Splitter :

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

y eso es todo Fácil como!

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

Aquí hay una versión de una sola línea que usa Java 8 IntStream para determinar los índices de los comienzos de los segmentos:

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

Una StringBuilderversión:

public static List<String> getChunks(String s, int chunkSize)
{
 List<String> chunks = new ArrayList<>();
 StringBuilder sb = new StringBuilder(s);

while(!(sb.length() ==0)) 
{           
   chunks.add(sb.substring(0, chunkSize));
   sb.delete(0, chunkSize);

}
return chunks;

}

Puede usar substringfrom String.class(manejo de excepciones) o de Apache lang commons (maneja las excepciones por usted)

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

Póngalo dentro de un bucle y listo.

Prefiero esta solución simple:

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

utilizo la siguiente solución 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());
}

En caso de que desee dividir la cadena igualmente hacia atrás, es decir, de derecha a izquierda, por ejemplo, para dividirla 1010001111, [10, 1000, 1111]aquí está el código:

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

Aquí hay una implementación de una sola línea usando flujos de 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();

Da el siguiente resultado:

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

Solución Java 8 (como esta pero un poco más 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;
}

Use puntos de código para manejar todos los caracteres

Aquí hay una solución:

  • Funciona con los 143.859 caracteres Unicode
  • Le permite examinar o manipular cada cadena resultante, si tiene más lógica para procesar.

Para trabajar con todos los caracteres Unicode, evite el chartipo obsoleto . Y evite las charutilidades basadas en . En su lugar, utilice números enteros de punto de código .

Llame String#codePointspara obtener un IntStreamobjeto, un flujo de intvalores. En el siguiente código, recopilamos esos intvalores en una matriz. Luego hacemos un bucle en la matriz, para cada número entero agregamos el carácter asignado a ese número a nuestro StringBuilderobjeto. Cada enésimo carácter, agregamos una cadena a nuestra lista maestra y vaciamos el archivo StringBuilder.

String input = "Thequickbrownfoxjumps";

int chunkSize = 4 ;
int[] codePoints = input.codePoints().toArray();  // `String#codePoints` returns an `IntStream`. Collect the elements of that stream into an array.
int initialCapacity = ( ( codePoints.length / chunkSize ) + 1 );
List < String > strings = new ArrayList <>( initialCapacity );

StringBuilder sb = new StringBuilder();
for ( int i = 0 ; i < codePoints.length ; i++ )
{
    sb.appendCodePoint( codePoints[ i ] );
    if ( 0 == ( ( i + 1 ) % chunkSize ) ) // Every nth code point.
    {
        strings.add( sb.toString() ); // Remember this iteration's value.
        sb.setLength( 0 ); // Clear the contents of the `StringBuilder` object.
    }
}
if ( sb.length() > 0 ) // If partial string leftover, save it too. Or not… just delete this `if` block.
{
    strings.add( sb.toString() ); // Remember last iteration's value.
}

System.out.println( "strings = " + strings );

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

Esto funciona con caracteres no latinos. Aquí reemplazamos qcon CARA CON MASCARILLA MÉDICA .

String text = "The😷uickbrownfoxjumps"

strings = [The😷, uick, brow, nfox, jump, s]

Aquí está mi versión basada en flujos RegEx y Java 8. Vale la pena mencionar que el Matcher.results()método está disponible desde Java 9.

Prueba incluida.

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

La solución más simple es:

  /**
   * Slices string by passed - in slice length.
   * If passed - in string is null or slice length less then 0 throws IllegalArgumentException.
   * @param toSlice string to slice
   * @param sliceLength slice length
   * @return List of slices
   */
  public static List<String> stringSlicer(String toSlice, int sliceLength) {
    if (toSlice == null) {
      throw new IllegalArgumentException("Passed - in string is null");
    }
    if (sliceLength < 0) {
      throw new IllegalArgumentException("Slice length can not be less then 0");
    }
    if (toSlice.isEmpty() || toSlice.length() <= sliceLength) {
      return List.of(toSlice);
    }
    
   return Arrays.stream(toSlice.split(String.format("(?s)(?<=\\G.{%d})", sliceLength))).collect(Collectors.toList());
  }

Le pregunté a @Alan Moore en un comentario a la solución aceptada cómo se podrían manejar las cadenas con saltos de línea. Sugirió usar DOTALL.

Usando su sugerencia, creé una pequeña muestra de cómo funciona:

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] - [[email protected]
     */


    //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] - [[email protected]
    [Idx: 1, length: 4] - [[email protected]
    [Idx: 2, length: 4] - [[email protected]
    [Idx: 3, length: 4] - [[email protected]
    [Idx: 4, length: 4] - [[email protected]
    [Idx: 5, length: 4] - [[email protected]
    [Idx: 6, length: 2] - [[email protected]
     */

}

Pero también me gusta la solución de @Jon Skeets en https://stackoverflow.com/a/3760193/1237974 . Para la mantenibilidad en proyectos más grandes donde no todos tienen la misma experiencia en expresiones regulares, probablemente usaría la solución Jons.

Otra solución de fuerza bruta podría ser,

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

Donde el código simplemente recorre la cadena con subcadenas

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

Resultado

 Enter String
 Thequickbrownfoxjumps
 Enter length Of Sub-string
 4

 ["Theq","uick","brow","nfox","jump","s"]
@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);
}
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;
}
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;
    }