División de cadenas en subcadenas de igual longitud en Java

125

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

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

Pregunta similar:

Dividir la cadena en subcadenas de igual longitud en Scala

Emil
fuente
44
¿Qué intentaste? ¿Por qué eso no funcionó?
Thilo
2
¿Necesitas usar una expresión regular para esto? Solo preguntaba por la etiqueta regex ...
Tim Pietzcker
El enlace de @Thilo que publicó es para Scala, está preguntando sobre lo mismo en Java
Jaydeep Patel
@Thilo: Estaba preguntando cómo hacerlo en Java, como la respuesta dada para Scala.
Emil

Respuestas:

226

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

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 previa, coincide con el comienzo de la entrada, igual que \A. La mirada de retrospectiva adjunta coincide con la posición de cuatro caracteres desde el final de la última coincidencia.

Ambos miran hacia 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 hacia atrás.

Debo mencionar que no necesariamente recomiendo 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 documentan por sí mismas; este es casi lo contrario de eso. ;)

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

Alan Moore
fuente
2
En PHP 5.2.4 funciona el siguiente código: return preg_split ('/ (? <= \ G. {'. $ Len. '}) / U', $ str, -1, PREG_SPLIT_NO_EMPTY);
Igor
55
Para el registro, usar en String.substring()lugar de una expresión regular, mientras se requieren algunas líneas de código adicionales, se ejecutará en algún lugar del orden de 5 veces más rápido ...
Dibujó más el
2
En Java, esto no funciona para una cadena con líneas nuevas. Solo verificará hasta la primera línea nueva, y si esa línea nueva es anterior al tamaño de división, entonces la cadena no se dividirá. ¿O me he perdido algo?
joensson
55
En aras de la exhaustividad: División del texto sobre líneas múltiples necesita un prefijo (?s)en la expresión regular: (?s)(?<=\\G.{4}).
bobbel
1
Java critica completamente esto en tiempo de compilación:java.util.regex.PatternSyntaxException: Look-behind pattern matches must have a bounded maximum length
Jeffrey Blattman
132

Bueno, es bastante fácil hacer esto con operaciones aritméticas y de cadena 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;
}

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 ninguno de los patrones reales de coincidencia de expresiones regulares. Es solo contar.
  • Yo 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 propia expresión regular en función de un parámetro - ick.
  • La expresión regular proporcionada en otra respuesta en primer lugar no se compiló (escape no válido), y luego no funcionó. Mi código funcionó por primera vez. Eso es más un testimonio de la usabilidad de expresiones regulares frente a código simple, IMO.
Jon Skeet
fuente
8
@Emil: En realidad, no pediste una expresión regular. Está en las etiquetas, pero nada en la pregunta pide una expresión regular. Pones este método en un lugar, y luego puedes dividir la cadena en una sola declaración muy legible en cualquier parte de tu código.
Jon Skeet
3
Emil, esto no es para lo que es una expresión regular. Período.
Chris
3
@Emil: si quieres una frase para dividir la cuerda, recomendaría Guava's Splitter.fixedLength(4)como lo sugiere Seanizer.
ColinD
2
@ Jay: vamos, no es necesario que seas tan sarcástico. Estoy seguro de que se puede hacer usando expresiones regulares en una sola línea. Una subcadena de longitud fija también es un patrón. ¿Qué dices sobre esta respuesta? stackoverflow.com/questions/3760152/… .
Emil
44
@Emil: no tenía la intención de que fuera grosero, simplemente caprichoso. La parte seria de mi punto fue que, si bien estoy seguro de que podrías encontrar un Regex para hacer esto (veo que Alan Moore tiene uno que dice que funciona) es críptico y, por lo tanto, difícil para un programador posterior entender y mantener Una solución de subcadena puede ser intuitiva y legible. Ver la cuarta viñeta de Jon Skeet: estoy de acuerdo con eso al 100%.
Jay
71

Esto es muy fácil con Google Guava :

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

Salida:

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 del divisor 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);
}
Sean Patrick Floyd
fuente
Gracias por la publicación (por informarme sobre el método de la biblioteca de guayaba). Pero tendré que aceptar la respuesta regex stackoverflow.com/questions/3760152/… ya que no requiere ninguna biblioteca de terceros y una sola línea.
Emil
1
La inclusión de cientos de KB de código de biblioteca solo para realizar esta tarea simple casi seguramente no es lo correcto.
Jeffrey Blattman
2
@JeffreyBlattman, incluida la guayaba, solo por esto es probablemente exagerado, cierto. Pero de todos modos lo uso como una biblioteca de uso general en todo mi código Java, así que ¿por qué no usar esta única funcionalidad adicional
Sean Patrick Floyd
¿Alguna forma de unirse con un separador?
Acuario Power
1
@AquariusPowerString.join(separator, arrayOrCollection)
Holger
14

Si está utilizando las bibliotecas de propósito general de guayaba de Google (y, sinceramente, cualquier nuevo proyecto Java 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 lo . Tan fácil como!

Cowan
fuente
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;
}
Saulo
fuente
Dado que ambos son src.length()y s, su llamada no está logrando lo que desea, vea cómo algunas de las otras respuestas lo están haciendo: (src.length () + len - 1) / lenlenintceiling
Michael Brewer-Davis
@Michael: Buen punto. No lo probé con cadenas de longitudes no múltiples. Ya está arreglado.
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
fuente
66
Por interés, ¿tienes algo contra los forbucles?
Jon Skeet
Un forbucle es, de hecho, una opción de uso más 'natural' para esto :-) Gracias por señalar esto.
Grodriguez
3

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

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

Ponlo dentro de un bucle y listo.

pakore
fuente
1
¿Qué tiene de malo el substringmétodo en la Stringclase estándar ?
Grodriguez
La versión de commons evita excepciones (fuera de límites y tal)
Thilo
77
Veo; Diría que prefiero 'evitar excepciones' controlando los parámetros en el código de llamada.
Grodriguez
2

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);
Cheetah Coder
fuente
¡No hagas esto! La cadena es inmutable, por lo que su código debe copiar toda la cadena restante cada 4 caracteres. Por lo tanto, su fragmento toma tiempo cuadrático en lugar de lineal en el tamaño de la cadena.
Tobias
@Tobias: Incluso si String era mutable, este fragmento hace la copia redundante mencionada, excepto que haya procesos de compilación complejos al respecto. La única razón para usar este fragmento es la simplicidad del código.
Cheetah Coder
¿Cambiaste tu código desde que lo publicaste por primera vez? La última versión en realidad no hace copias: substring () se ejecuta de manera eficiente (tiempo constante, al menos en versiones antiguas de Java); mantiene una referencia al char [] de la cadena completa (al menos en versiones antiguas de Java), pero eso está bien en este caso ya que conserva todos los caracteres. Entonces, el último código que tiene aquí está realmente bien (módulo de que su código imprime una línea vacía si el contenido comienza como una cadena vacía, lo que puede no ser lo que se pretende).
Tobias
@Tobias: No recuerdo ningún cambio.
Cheetah Coder
@Tobias la substringimplementación cambió con Java 7, actualización 6 a mediados de 2012, cuando los campos offsety countse eliminaron de la Stringclase. Por lo tanto, la complejidad se substringvolvió lineal mucho antes de que se hiciera esta respuesta. Pero para una cadena pequeña como el ejemplo, todavía se ejecuta lo suficientemente rápido y para cadenas más largas ... bueno, esta tarea rara vez ocurre en la práctica.
Holger
2

Aquí hay una implementación de una línea utilizando flujos 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]
Pankaj Singhal
fuente
1
Esa es una solución horrible, luchar contra la intención de la API, usar funciones con estado y ser significativamente más complicado que un bucle ordinario, por no hablar de la sobrecarga de concatenación de cadenas y boxeo. Si desea una solución Stream, use algo comoString[] 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

Aquí hay una versión de una línea que utiliza Java 8 IntStream para determinar los índices de los inicios de la porción:

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

En caso de que quiera dividir la cadena igualmente hacia atrás, es decir, de derecha a izquierda, por ejemplo, para dividir 1010001111a [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;
}
Ivan Huang
fuente
1

Yo uso la siguiente solución de 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());
}
rloeffel
fuente
0

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;
}
Timofey Gorshkov
fuente
-1

Le pregunté a @Alan Moore en un comentario a la solución aceptada cómo se podían manejar las cadenas con líneas nuevas. 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] - [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
     */

}

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

joensson
fuente
-1

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 solo atraviesa la cadena con subcadenas

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

Resultado

 Enter String
 Thequickbrownfoxjumps
 Enter length Of Sub-string
 4

 ["Theq","uick","brow","nfox","jump","s"]
Ravichandra
fuente
-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
fuente
-1

Aquí está mi versión basada en transmisiones 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());
}
itachi
fuente
-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;
}
Usuario8461
fuente
-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
fuente