Dividir string para substrings de comprimento igual em Java

125

Como dividir a string "Thequickbrownfoxjumps"em substrings de tamanho igual em Java. Por exemplo. "Thequickbrownfoxjumps"de 4 tamanho igual deve dar a saída.

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

Pergunta semelhante:

Dividir string em substrings de igual comprimento no Scala

Emil
fonte
4
O que você tentou? Por que isso não funcionou?
Thilo
2
Você precisa usar um regex para isso? Só perguntando por causa da tag regex ...
Tim Pietzcker
@Thilo ligação ele postou é para Scala, ele está perguntando sobre mesmo em Java
Jaydeep Patel
@ Thilo: Eu estava perguntando o que fazer em java, como a resposta dada pelo scala.
Emil

Respostas:

226

Aqui está a versão de uma linha do regex:

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

\Gé uma asserção de largura zero que corresponde à posição em que a correspondência anterior terminou. Se não foi páreo anterior, que corresponde ao início da entrada, o mesmo que \A. O lookbehind em anexo corresponde à posição de quatro caracteres desde o final da última partida.

Ambos olham para trás e \Gsão recursos avançados de regex, não suportados por todos os tipos. Além disso, \Gnão é implementado de forma consistente nos sabores que o suportam. Esse truque funcionará (por exemplo) em Java , Perl, .NET e JGSoft, mas não em PHP (PCRE), Ruby 1.9+ ou TextMate (ambos Oniguruma). O JavaScript /y(sinalizador fixo) não é tão flexível quanto \G, e não poderia ser usado dessa maneira, mesmo se o JS suportasse o lookback.

Devo mencionar que não recomendo necessariamente esta solução se você tiver outras opções. As soluções não regex nas outras respostas podem ser mais longas, mas também são auto-documentadas; este é exatamente o oposto disso. ;)

Além disso, isso não funciona no Android, que não suporta o uso de \Glookbehinds.

Alan Moore
fonte
2
No PHP 5.2.4, funciona o seguinte código: return preg_split ('/ (? <= \ G. {'. $ Len. '}) / U', $ str, -1, PREG_SPLIT_NO_EMPTY);
Igor
5
Para o registro, usando String.substring()em vez de um regex, mas exige algumas linhas extras de código, será executado em algum lugar na ordem de 5x mais rápido ...
chamou moore
2
Em Java, isso não funciona para uma sequência com novas linhas. Ele verificará apenas até a primeira nova linha e, se essa nova linha ocorrer antes do tamanho da divisão, a sequência não será dividida. Ou eu perdi alguma coisa?
Jönsson
5
Por uma questão de exaustividade: texto de divisão ao longo multilines precisa de um prefixo (?s)na expressão regular: (?s)(?<=\\G.{4}).
bobbel
1
Java barfs neste completamente em tempo de compilação:java.util.regex.PatternSyntaxException: Look-behind pattern matches must have a bounded maximum length
Jeffrey Blattman
132

Bem, é bastante fácil fazer isso com operações aritméticas e de cordas 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;
}

Não acho que valha a pena usar um regex para isso.

EDIT: Meu raciocínio para não usar uma regex:

  • Isso não usa nenhuma correspondência de padrão real de regexes. Está apenas contando.
  • Eu suspeito que o acima será mais eficiente, embora na maioria dos casos não importe
  • Se você precisar usar tamanhos variáveis ​​em lugares diferentes, terá repetição ou uma função auxiliar para criar o próprio regex com base em um parâmetro - ick.
  • O regex fornecido em outra resposta primeiro não foi compilado (escape inválido) e, em seguida, não funcionou. Meu código funcionou pela primeira vez. Isso é mais um testemunho da usabilidade de expressões regulares versus código simples, IMO.
Jon Skeet
fonte
8
@Emil: Na verdade, você não pediu uma regex. Está nas tags, mas nada na pergunta em si pede uma regex. Você coloca esse método em um só lugar e pode dividir a seqüência de caracteres em apenas uma instrução muito legível em qualquer lugar do seu código.
quer
3
Emil, não é para isso que serve uma regex. Período.
Chris
3
@Emil: Se você quiser uma linha para dividir a corda, eu recomendo o Goiaba, Splitter.fixedLength(4)conforme sugerido pelo seanizer.
ColinD
2
@ Jay: vamos lá, você não precisa ser tão sarcástico. Tenho certeza que isso pode ser feito usando regex em apenas uma linha. Uma sub-string de comprimento fixo também é um padrão. O que você diz sobre esta resposta. stackoverflow.com/questions/3760152/… .
Emil
4
@Emil: Eu não pretendia que isso fosse rude, apenas extravagante. A parte mais séria do meu argumento foi que, embora sim, tenho certeza de que você poderia criar um Regex para fazer isso - vejo Alan Moore como um que ele afirma funcionar - é enigmático e, portanto, difícil para um programador posterior entender e manter. Uma solução de substring pode ser intuitiva e legível. Veja a quarta bala de Jon Skeet: eu concordo com isso 100%.
Jay
71

Isso é muito fácil com o Google Guava :

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

Resultado:

Theq
uick
brow
nfox
jump
s

Ou, se você precisar do resultado como uma matriz, poderá usar este código:

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

Referência:

Nota: A construção do divisor é mostrada em linha acima, mas como os divisores são imutáveis ​​e reutilizáveis, é uma boa prática armazená-los em 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
fonte
Obrigado pela postagem (por me informar sobre o método da biblioteca de goiaba) .Mas eu terei que aceitar a resposta regex stackoverflow.com/questions/3760152/…, pois ela não requer nenhuma biblioteca de terceiros e uma linha única.
Emil
1
Incluir centenas de KB de código de biblioteca apenas para executar esta tarefa simples quase certamente não é a coisa certa.
Jeffrey Blattman
2
@JeffreyBlattman, incluindo o Goiaba, provavelmente é um exagero, é verdade. Mas eu usá-lo como uma biblioteca de uso geral em todo o código Java a minha de qualquer maneira, então porque não usar esta peça adicional de funcionalidade
Sean Patrick Floyd
alguma maneira de se juntar a um separador?
Aquarius Power
1
@AquariusPowerString.join(separator, arrayOrCollection)
Holger
14

Se você estiver usando as bibliotecas de uso geral de goiaba do Google (e, sinceramente, qualquer novo projeto Java provavelmente deveria ser), isso é incrivelmente trivial para a classe Splitter :

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

e é isso . Fácil como!

Cowan
fonte
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
fonte
Como src.length()e lensão ambos ints, sua ligação ceiling não está cumprindo o que você deseja - confira como algumas das outras respostas estão fazendo: (src.length () + len - 1) / len
Michael Brewer-Davis
@ Michael: Bom ponto. Eu não testei com strings de comprimentos não múltiplos. Está consertado agora.
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
fonte
6
Por interesse, você tem algo contra forloops?
quer
Um forloop é realmente uma opção mais 'natural' para isso :-) Obrigado por apontar isso.
Grodriguez 21/09/10
3

Você pode usar substringfrom String.class(manipulação de exceções) ou Apache lang commons (ele lida com exceções para você)

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

Coloque-o dentro de um loop e você estará pronto.

pakore
fonte
1
O que há de errado com o substringmétodo na Stringclasse padrão ?
Grodriguez 21/09/10
A versão comum evita exceções (fora dos limites e outras)
Thilo
7
Entendo; Eu diria que prefiro 'evitar exceções' controlando os parâmetros no código de chamada.
Grodriguez 21/09/10
2

Prefiro esta solução simples:

String content = "Thequickbrownfoxjumps";
while(content.length() > 4) {
    System.out.println(content.substring(0, 4));
    content = content.substring(4);
}
System.out.println(content);
Cheetah Coder
fonte
Não faça isso! A sequência é imutável, portanto, seu código precisa copiar toda a sequência restante a cada 4 caracteres. Seu snippet, portanto, leva um tempo quadrático em vez de linear no tamanho da String.
Tobias
@ Tobias: Mesmo que String seja mutável, esse trecho faz a cópia redundante mencionada, exceto que há processos de compilação complexos a respeito. O único motivo para usar esse snippet é a simplicidade do código.
Cheetah Coder
Você mudou seu código desde que o publicou pela primeira vez? A versão mais recente, na verdade, não faz cópias - a substring () é executada com eficiência (tempo constante, pelo menos nas versões antigas do Java); mantém uma referência ao caractere inteiro da string [] (pelo menos nas versões antigas do Java), mas tudo bem nesse caso, pois você mantém todos os caracteres. Portanto, o código mais recente que você tem aqui é realmente bom (módulo que seu código imprime uma linha vazia se o conteúdo iniciar como uma sequência vazia, o que pode não ser o que se pretende).
Tobias
@ Tobias: Não me lembro de nenhuma alteração.
Cheetah Coder
@Tobias a substringimplementação mudou com o Java 7, atualização 6, em meados de 2012, quando os campos offsete countforam removidos da Stringclasse. Portanto, a complexidade substringtornou-se linear muito antes de essa resposta ser feita. Mas para uma sequência pequena como o exemplo, ela ainda é executada com rapidez suficiente e para sequências mais longas ... bem, essa tarefa raramente ocorre na prática.
Holger
2

Aqui está uma implementação de um liner usando fluxos 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();

Ele fornece a seguinte saída:

[Theq, uick, brow, nfox, jump, s]
Pankaj Singhal
fonte
1
Essa é uma solução horrível, combater a intenção da API, usar funções com estado e ser significativamente mais complicado que um loop comum, para não falar da sobrecarga de concatenação de boxe e string. Se você quer uma solução Stream, usar 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

Aqui está uma versão de uma linha que usa o Java 8 IntStream para determinar os índices do início da fatia:

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

Caso você queira dividir a string igualmente para trás, ou seja, da direita para a esquerda, por exemplo, para dividir 1010001111em [10, 1000, 1111], aqui está o 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
fonte
1

eu uso a seguinte solução 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
fonte
0

Solução Java 8 (como essa, mas um pouco mais simples):

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

Perguntei a Alan Moore em um comentário para a solução aceita como lidar com seqüências de caracteres com novas linhas. Ele sugeriu o uso de DOTALL.

Usando sua sugestão, criei uma pequena amostra de como isso 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
     */

}

Mas também gosto da solução @Jon Skeets em https://stackoverflow.com/a/3760193/1237974 . Para manutenção em projetos maiores, onde nem todos são igualmente experientes em expressões regulares, eu provavelmente usaria a solução Jons.

joensson
fonte
-1

Outra solução de força bruta poderia 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]);
    }

Onde o código apenas percorre a string com substrings

Hubbly
fonte
-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
fonte
-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
fonte
-1

Aqui está minha versão baseada nos fluxos RegEx e Java 8. Vale ressaltar que o Matcher.results()método está disponível desde o Java 9.

Teste incluído.

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
fonte
-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;
}
Usuário8461
fonte
-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
fonte