Dividindo uma corda em pedaços de um determinado tamanho

218

Suponha que eu tivesse uma string:

string str = "1111222233334444"; 

Como posso quebrar essa string em pedaços de algum tamanho?

por exemplo, dividir isso em tamanhos de 4 retornaria seqüências de caracteres:

"1111"
"2222"
"3333"
"4444"
Jeff Mercado
fonte
18
Por que usar LINQ ou regexes quando as funções de manipulação de seqüência de caracteres padrão do C # podem fazer isso com menos esforço e mais velocidade? Além disso, o que acontece se a sequência tiver um número ímpar de caracteres?
22615 Ian Kemp
7
"Eu gostaria de evitar loops" - por quê?
30610 Mitch Wheat
12
Usar um loop simples é definitivamente o que oferece o melhor desempenho.
Guffa 20/09/09
4
nichesoftware.co.nz/blog/200909/linq-vs-loop-performance é uma comparação muito boa entre linq e loop real sobre uma matriz. Duvido que você encontre o linq mais rápido que o código escrito manualmente, porque ele continua chamando delegados em tempo de execução que são difíceis de otimizar. Linq é mais divertido embora :)
Blindy
2
Esteja você usando LINQ ou expressões regulares, o loop ainda está lá.
Anton Tykhyy 21/09/09

Respostas:

247
static IEnumerable<string> Split(string str, int chunkSize)
{
    return Enumerable.Range(0, str.Length / chunkSize)
        .Select(i => str.Substring(i * chunkSize, chunkSize));
}

Observe que pode ser necessário código adicional para lidar com casos de borda normalmente ( nullou sequência de entrada vazia chunkSize == 0, comprimento da sequência de entrada não divisível por chunkSizeetc.). A pergunta original não especifica nenhum requisito para esses casos extremos e, na vida real, os requisitos podem variar, ficando fora do escopo desta resposta.

Konstantin Spirin
fonte
3
@ Harry Boa captura! Isso pode ser remediado com uma expressão ternária drop-in no parâmetro count de substring. Algo como: (i * chunkSize + chunkSize <= str.Length) ? chunkSize : str.Length - i * chunkSize. Um problema adicional é que essa função não considera str sendo nulo. Isso pode ser corrigido por envolvimento toda a instrução de retorno em outra expressão ternária: (str != null) ? ... : Enumerable.Empty<String>();.
Tirou Spickes
7
Isso estava próximo, mas, diferentemente dos 30 upvoters anteriores, tive que alterar o limite de contagem de loops de Range de str.Length / chunkSizeparadouble length = str.Length; double size = chunkSize; int count = (int)Math.Ceiling(length/size); return Enumerable.Range(0, count)...
gap
4
@KonstantinSpirin Concordo se o código funcionou. Ele lida apenas com o caso em que uma string é um múltiplo de chunkSize, o restante da string é perdido. Por favor, emendar. Lembre-se também de que o LINQ e sua mágica não são tão fáceis de entender para quem quer apenas encontrar uma solução para esse problema. Uma pessoa agora deve entender o que as funções Enumerable.Range () e .Select () fazem. Não vou argumentar que você deve entender isso para escrever código C # /. NET, pois essas funções estão na BCL há muitos anos.
CodeMonkeyKing
6
O autor do tópico disse nos comentários que StringLength % 4 will always be 0. Se Linqnão é tão fácil de entender, existem outras respostas que usam loops e rendimentos. Qualquer pessoa é livre para escolher a solução que mais gosta. Você pode postar seu código como resposta e as pessoas votarão com prazer.
Konstantin Spirin
3
Enumerable.Range (0, (str.Length + chunkSize - 1) / chunkSize). Selecione (i => str.Substring (i * chunkSize, Math.Min (str.Length - i * chunkSize, chunkSize)))
Sten Petrov
135

Em uma combinação de respostas de pomba + Konstatin ...

static IEnumerable<string> WholeChunks(string str, int chunkSize) {
    for (int i = 0; i < str.Length; i += chunkSize) 
        yield return str.Substring(i, chunkSize);
}

Isso funcionará para todas as seqüências de caracteres que podem ser divididas em um número inteiro de partes e gerará uma exceção caso contrário.

Se você deseja suportar seqüências de caracteres de qualquer tamanho, pode usar o seguinte código:

static IEnumerable<string> ChunksUpto(string str, int maxChunkSize) {
    for (int i = 0; i < str.Length; i += maxChunkSize) 
        yield return str.Substring(i, Math.Min(maxChunkSize, str.Length-i));
}

No entanto, o OP declarou explicitamente que não precisa disso; é um pouco mais longo e difícil de ler, um pouco mais lento. No espírito do KISS e YAGNI, eu usaria a primeira opção: provavelmente é a implementação mais eficiente possível, e é muito curta, legível e, o que é mais importante, lança uma exceção para informações não conformes.

Eamon Nerbonne
fonte
4
+1 vale um aceno de cabeça. meio que bate na cabeça. ele está procurando uma sintaxe sucinta e você também está dando o (provavelmente) melhor desempenho.
pomba
7
E se você o tornar "estático ... Chunk (esta string str, int chunkSize) {", você ainda terá mais um "novo" recurso C # nele. Em seguida, você pode escrever "1111222233334444" .Chunk (4).
22420 MartinStettner
1
@ MartinStettner: Essa é certamente uma ideia decente se essa é uma operação comum.
Eamon Nerbonne
Você deve incluir apenas o último código. O primeiro exige que você entenda e teste se a sequência é um múltiplo do tamanho de um pedaço antes de usar, ou entenda que ela não retornará o restante da sequência.
precisa saber é o seguinte
A pergunta do OP não deixa claro se ele precisa dessa funcionalidade. A primeira solução é mais simples, mais rápida e confiável falha, com uma exceção se a sequência não puder ser dividida igualmente no tamanho do bloco especificado. Concordo que retornar resultados "errados" seria ruim, mas não é o que acontece - isso apenas gera uma exceção; portanto, eu ficaria bem em usá-lo se você puder viver com a limitação.
Eamon Nerbonne
56

Por que não loops? Aqui está algo que faria muito bem:

        string str = "111122223333444455";
        int chunkSize = 4;
        int stringLength = str.Length;
        for (int i = 0; i < stringLength ; i += chunkSize)
        {
            if (i + chunkSize > stringLength) chunkSize = stringLength  - i;
            Console.WriteLine(str.Substring(i, chunkSize));

        }
        Console.ReadLine();

Eu não sei como você lida com o caso em que a string não é fator de 4, mas não está dizendo que sua ideia não é possível, apenas imaginando a motivação para isso, se um loop for simples for muito bem? Obviamente, o acima pode ser limpo e até colocado como um método de extensão.

Ou, como mencionado nos comentários, você sabe que é / 4

str = "1111222233334444";
for (int i = 0; i < stringLength; i += chunkSize) 
  {Console.WriteLine(str.Substring(i, chunkSize));} 
pomba
fonte
1
Você pode puxar para int chunkSize = 4fora do loop. Só será modificado no passe final.
John Feminella
+1 para uma solução simples e eficaz - é assim que eu o faria, embora eu o tivesse usado i += chunkSize.
22610 Ian Kemp
Provavelmente, uma queixa menor, mas você provavelmente também deve extrair str.Lengtho loop e entrar em uma variável local. O otimizador de C # pode ser capaz de alinhar o comprimento da matriz, mas acho que o código escrito fará uma chamada de método em cada loop, o que não é eficiente, pois o tamanho de strnunca muda.
Daniel Pryden
@ Daniel, coloque sua idéia lá. embora eu não tenho certeza de que isso não seria calculado em tempo de execução, mas isso é outra questão;)
pomba
@ Daniel retornando a isso, tenho certeza de que essa otimização seria extraída pelo compilador.
pomba
41

Usando expressões regulares e Linq :

List<string> groups = (from Match m in Regex.Matches(str, @"\d{4}")
                       select m.Value).ToList();

Acho que isso é mais legível, mas é apenas uma opinião pessoal. Também pode ser uma linha:).

João Silva
fonte
7
Mude o padrão para @ "\ d {1,4}" e funciona para qualquer comprimento de string. :)
Guffa 20/09/09
3
+1 Embora seja mais lento que as outras soluções, é definitivamente muito legível. Não está claro para mim se o OP exige dígitos ou caracteres arbitrários; provavelmente seria sensato substituir a \dclasse de caracteres por ae .especificar RegexOptions.Singleline.
Eamon Nerbonne
2
ou apenas Regex.Matches (s, @ "\ d {1,4}"). Selecione (m => m.Value) .ToList (); Eu nunca entendi essa sintaxe alternativa que serve apenas para ofuscar que estamos usando métodos de extensão.
The Dag
38

Isso é baseado na solução @dove mas implementado como um método de extensão.

Benefícios:

  • Método de extensão
  • Capas de canto
  • Divide a string com qualquer caractere: números, letras, outros símbolos

Código

public static class EnumerableEx
{    
    public static IEnumerable<string> SplitBy(this string str, int chunkLength)
    {
        if (String.IsNullOrEmpty(str)) throw new ArgumentException();
        if (chunkLength < 1) throw new ArgumentException();

        for (int i = 0; i < str.Length; i += chunkLength)
        {
            if (chunkLength + i > str.Length)
                chunkLength = str.Length - i;

            yield return str.Substring(i, chunkLength);
        }
    }
}

Uso

var result = "bobjoecat".SplitBy(3); // bob, joe, cat

Testes de unidade removidos por questões de brevidade (consulte a revisão anterior )

oleksii
fonte
Solução interessante, mas, a fim de evitar verificações além de nulo na entrada, parece mais lógico permitir que uma string vazia retorne apenas uma única parte de string vazia:if (str.Length == 0) yield return String.Empty; else { for... }
Nyerguds
Quero dizer, é assim que o String.Split normal lida com cadeias vazias; retorna uma entrada de sequência vazia.
Nyerguds
Nota lateral: seu exemplo de uso está errado. Você não pode simplesmente transmitir IEnumerablepara a matriz, especialmente não implicitamente.
Nyerguds
Eu pessoalmente gosto de chamar esse método Chunkify.. Não é minha, eu não me lembro onde eu vi esse nome, mas era muito bom para mim
quetzalcoatl
20

Como é isso para uma fila?

List<string> result = new List<string>(Regex.Split(target, @"(?<=\G.{4})", RegexOptions.Singleline));

Com esse regex, não importa se o último pedaço tem menos de quatro caracteres, porque apenas olha para os caracteres por trás dele.

Tenho certeza de que essa não é a solução mais eficiente, mas eu apenas tive que jogá-la lá fora.

Alan Moore
fonte
no caso de target.Lenght % ChunckSize == 0isso retornar uma linha vazia adicional, por exemplo,List<string> result = new List<string>(Regex.Split("fooo", @"(?<=\G.{4})", RegexOptions.Singleline));
fubo
9

Não é bonito e não é rápido, mas funciona, é uma linha e é LINQy:

List<string> a = text.Select((c, i) => new { Char = c, Index = i }).GroupBy(o => o.Index / 4).Select(g => new String(g.Select(o => o.Char).ToArray())).ToList();
Guffa
fonte
É garantido que o GroupBy preserve a ordem dos elementos?
Konstantin Spirin
ToCharArrayé desnecessário, pois stringé IEnumerable<char>.
21415 Juhrr
8

Recentemente, tive que escrever algo que realizasse isso no trabalho, então pensei em publicar minha solução para esse problema. Como um bônus adicional, a funcionalidade desta solução fornece uma maneira de dividir a string na direção oposta e lida corretamente com caracteres unicode, como mencionado anteriormente por Marvin Pinto acima. Então aqui está:

using System;
using Extensions;

namespace TestCSharp
{
    class Program
    {
        static void Main(string[] args)
        {    
            string asciiStr = "This is a string.";
            string unicodeStr = "これは文字列です。";

            string[] array1 = asciiStr.Split(4);
            string[] array2 = asciiStr.Split(-4);

            string[] array3 = asciiStr.Split(7);
            string[] array4 = asciiStr.Split(-7);

            string[] array5 = unicodeStr.Split(5);
            string[] array6 = unicodeStr.Split(-5);
        }
    }
}

namespace Extensions
{
    public static class StringExtensions
    {
        /// <summary>Returns a string array that contains the substrings in this string that are seperated a given fixed length.</summary>
        /// <param name="s">This string object.</param>
        /// <param name="length">Size of each substring.
        ///     <para>CASE: length &gt; 0 , RESULT: String is split from left to right.</para>
        ///     <para>CASE: length == 0 , RESULT: String is returned as the only entry in the array.</para>
        ///     <para>CASE: length &lt; 0 , RESULT: String is split from right to left.</para>
        /// </param>
        /// <returns>String array that has been split into substrings of equal length.</returns>
        /// <example>
        ///     <code>
        ///         string s = "1234567890";
        ///         string[] a = s.Split(4); // a == { "1234", "5678", "90" }
        ///     </code>
        /// </example>            
        public static string[] Split(this string s, int length)
        {
            System.Globalization.StringInfo str = new System.Globalization.StringInfo(s);

            int lengthAbs = Math.Abs(length);

            if (str == null || str.LengthInTextElements == 0 || lengthAbs == 0 || str.LengthInTextElements <= lengthAbs)
                return new string[] { str.ToString() };

            string[] array = new string[(str.LengthInTextElements % lengthAbs == 0 ? str.LengthInTextElements / lengthAbs: (str.LengthInTextElements / lengthAbs) + 1)];

            if (length > 0)
                for (int iStr = 0, iArray = 0; iStr < str.LengthInTextElements && iArray < array.Length; iStr += lengthAbs, iArray++)
                    array[iArray] = str.SubstringByTextElements(iStr, (str.LengthInTextElements - iStr < lengthAbs ? str.LengthInTextElements - iStr : lengthAbs));
            else // if (length < 0)
                for (int iStr = str.LengthInTextElements - 1, iArray = array.Length - 1; iStr >= 0 && iArray >= 0; iStr -= lengthAbs, iArray--)
                    array[iArray] = str.SubstringByTextElements((iStr - lengthAbs < 0 ? 0 : iStr - lengthAbs + 1), (iStr - lengthAbs < 0 ? iStr + 1 : lengthAbs));

            return array;
        }
    }
}

Além disso, aqui está um link de imagem para os resultados da execução deste código: http://i.imgur.com/16Iih.png

Michael Nelson
fonte
1
Percebi um problema com este código. Você tem {str.ToString()}no final de sua primeira declaração IF. Tem certeza de que não quis dizer isso str.String? Eu tive um problema com o código acima, fiz essa alteração e tudo funcionou.
gunr2171
@ gunr2171 Parece que se str == null, essa linha também fornecerá uma NullReferenceException.
John Zabroski
5

Isso deve ser muito mais rápido e eficiente do que usar o LINQ ou outras abordagens usadas aqui.

public static IEnumerable<string> Splice(this string s, int spliceLength)
{
    if (s == null)
        throw new ArgumentNullException("s");
    if (spliceLength < 1)
        throw new ArgumentOutOfRangeException("spliceLength");

    if (s.Length == 0)
        yield break;
    var start = 0;
    for (var end = spliceLength; end < s.Length; end += spliceLength)
    {
        yield return s.Substring(start, spliceLength);
        start = end;
    }
    yield return s.Substring(start);
}
Jeff Mercado
fonte
Este olhares como ele faz a verificação cedo, mas isso não acontece. Você não recebe um erro até começar a enumerar o enumerável. Você precisa dividir sua função em duas partes, onde a primeira parte faz a verificação do argumento e, em seguida, retorna os resultados da segunda parte privada que faz a enumeração.
ErikE
4
public static IEnumerable<IEnumerable<T>> SplitEvery<T>(this IEnumerable<T> values, int n)
{
    var ls = values.Take(n);
    var rs = values.Skip(n);
    return ls.Any() ?
        Cons(ls, SplitEvery(rs, n)) : 
        Enumerable.Empty<IEnumerable<T>>();
}

public static IEnumerable<T> Cons<T>(T x, IEnumerable<T> xs)
{
    yield return x;
    foreach (var xi in xs)
        yield return xi;
}
HoloEd
fonte
4

Você pode usar o morelinq de Jon Skeet. Use o lote como:

string str = "1111222233334444";
int chunkSize = 4;
var chunks = str.Batch(chunkSize).Select(r => new String(r.ToArray()));

Isso retornará 4 pedaços para a sequência "1111222233334444". Se o comprimento da string for menor ou igual ao tamanho do pedaçoBatch , retornará a string como o único elemento deIEnumerable<string>

Para saída:

foreach (var chunk in chunks)
{
    Console.WriteLine(chunk);
}

e vai dar:

1111
2222
3333
4444
Habib
fonte
Entre os autores do MoreLINQ, vejo Jonathan Skeet , mas não Jon Skeet . Assim que você quis dizer o Jon Skeet, ou o quê? ;-)
Sнаđошƒаӽ
3
static IEnumerable<string> Split(string str, double chunkSize)
{
    return Enumerable.Range(0, (int) Math.Ceiling(str.Length/chunkSize))
       .Select(i => new string(str
           .Skip(i * (int)chunkSize)
           .Take((int)chunkSize)
           .ToArray()));
}

e outra abordagem:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {

        var x = "Hello World";
        foreach(var i in x.ChunkString(2)) Console.WriteLine(i);
    }
}

public static class Ext{
    public static IEnumerable<string> ChunkString(this string val, int chunkSize){
        return val.Select((x,i) => new {Index = i, Value = x})
                  .GroupBy(x => x.Index/chunkSize, x => x.Value)
                  .Select(x => string.Join("",x));
    }
}
Chris Hayes
fonte
3

Seis anos depois o_O

Só porque

    public static IEnumerable<string> Split(this string str, int chunkSize, bool remainingInFront)
    {
        var count = (int) Math.Ceiling(str.Length/(double) chunkSize);
        Func<int, int> start = index => remainingInFront ? str.Length - (count - index)*chunkSize : index*chunkSize;
        Func<int, int> end = index => Math.Min(str.Length - Math.Max(start(index), 0), Math.Min(start(index) + chunkSize - Math.Max(start(index), 0), chunkSize));
        return Enumerable.Range(0, count).Select(i => str.Substring(Math.Max(start(i), 0),end(i)));
    }

ou

    private static Func<bool, int, int, int, int, int> start = (remainingInFront, length, count, index, size) =>
        remainingInFront ? length - (count - index) * size : index * size;

    private static Func<bool, int, int, int, int, int, int> end = (remainingInFront, length, count, index, size, start) =>
        Math.Min(length - Math.Max(start, 0), Math.Min(start + size - Math.Max(start, 0), size));

    public static IEnumerable<string> Split(this string str, int chunkSize, bool remainingInFront)
    {
        var count = (int)Math.Ceiling(str.Length / (double)chunkSize);
        return Enumerable.Range(0, count).Select(i => str.Substring(
            Math.Max(start(remainingInFront, str.Length, count, i, chunkSize), 0),
            end(remainingInFront, str.Length, count, i, chunkSize, start(remainingInFront, str.Length, count, i, chunkSize))
        ));
    }

AFAIK todos os casos extremos são tratados.

Console.WriteLine(string.Join(" ", "abc".Split(2, false))); // ab c
Console.WriteLine(string.Join(" ", "abc".Split(2, true))); // a bc
Console.WriteLine(string.Join(" ", "a".Split(2, true))); // a
Console.WriteLine(string.Join(" ", "a".Split(2, false))); // a
Gerben Limburg
fonte
E o caso da borda "entrada é uma string vazia"? Eu esperaria que, assim como no Split, retornasse um IEnumerable com uma única string contendo a entrada.
Nyerguds
3

Simples e curto:

// this means match a space or not a space (anything) up to 4 characters
var lines = Regex.Matches(str, @"[\s\S]{0,4}").Cast<Match>().Select(x => x.Value);
Tono Nam
fonte
Por que não usar .?
marsze 21/09
3
static IEnumerable<string> Split(string str, int chunkSize)
{
   IEnumerable<string> retVal = Enumerable.Range(0, str.Length / chunkSize)
        .Select(i => str.Substring(i * chunkSize, chunkSize))

   if (str.Length % chunkSize > 0)
        retVal = retVal.Append(str.Substring(str.Length / chunkSize * chunkSize, str.Length % chunkSize));

   return retVal;
}

Ele manipula corretamente o comprimento da string de entrada não divisível por chunkSize.

Observe que pode ser necessário código adicional para lidar com casos extremos (seqüência de entrada nula ou vazia, chunkSize == 0).

Troncho
fonte
2

Uma dica importante se a sequência que está sendo dividida em partes precisa suportar todos os caracteres Unicode.

Se a string for compatível com caracteres internacionais como 𠀋, divida-a usando a classe System.Globalization.StringInfo. Usando StringInfo, você pode dividir a sequência com base no número de elementos de texto.

string internationalString = '𠀋';

A sequência acima tem um Comprimento 2, porque a String.Lengthpropriedade retorna o número de objetos Char nessa instância, não o número de caracteres Unicode.

Seth
fonte
2

Resposta melhor, mais fácil e genérica :).

    string originalString = "1111222233334444";
    List<string> test = new List<string>();
    int chunkSize = 4; // change 4 with the size of strings you want.
    for (int i = 0; i < originalString.Length; i = i + chunkSize)
    {
        if (originalString.Length - i >= chunkSize)
            test.Add(originalString.Substring(i, chunkSize));
        else
            test.Add(originalString.Substring(i,((originalString.Length - i))));
    }
Sandeep Kushwah
fonte
O cálculo do comprimento na última linha é redundante, basta usar a Substringsobrecarga que não requer o parâmetro length originalString.Substring(i). Além disso, você pode usar em >vez de >=no seu cheque.
Racil Hilan
@RacilHilan Vou testar as alterações de código com sua sugestão e atualizar a resposta. Fico feliz que alguém com essa reputação tenha tido tempo para revisar meu código. :) Obrigado, Sandeep
Sandeep Kushwah
2

Pessoalmente, prefiro minha solução :-)

Ele lida com:

  • Comprimentos de cadeia que são múltiplos do tamanho da parte.
  • Comprimentos de string que NÃO são múltiplos do tamanho do pedaço.
  • Comprimentos de string menores que o tamanho do pedaço.
  • Cadeias nulas e vazias (lança uma exceção).
  • Tamanhos de pedaços menores que 1 (lança uma exceção).

Ele é implementado como um método de extensão e calcula o número de pedaços que serão gerados previamente. Ele verifica o último pedaço porque, caso o tamanho do texto não seja múltiplo, ele precisa ser menor. Limpo, curto, fácil de entender ... e funciona!

    public static string[] Split(this string value, int chunkSize)
    {
        if (string.IsNullOrEmpty(value)) throw new ArgumentException("The string cannot be null.");
        if (chunkSize < 1) throw new ArgumentException("The chunk size should be equal or greater than one.");

        int remainder;
        int divResult = Math.DivRem(value.Length, chunkSize, out remainder);

        int numberOfChunks = remainder > 0 ? divResult + 1 : divResult;
        var result = new string[numberOfChunks];

        int i = 0;
        while (i < numberOfChunks - 1)
        {
            result[i] = value.Substring(i * chunkSize, chunkSize);
            i++;
        }

        int lastChunkSize = remainder > 0 ? remainder : chunkSize;
        result[i] = value.Substring(i * chunkSize, lastChunkSize);

        return result;
    }
Ibai
fonte
2
List<string> SplitString(int chunk, string input)
{
    List<string> list = new List<string>();
    int cycles = input.Length / chunk;

    if (input.Length % chunk != 0)
        cycles++;

    for (int i = 0; i < cycles; i++)
    {
        try
        {
            list.Add(input.Substring(i * chunk, chunk));
        }
        catch
        {
            list.Add(input.Substring(i * chunk));
        }
    }
    return list;
}
Gabriel
fonte
1
Eu gosto muito dessa resposta, mas talvez você deva usar if ((i + 1) * chunk> = input.Length) em vez de try / catch, pois as exceções são para casos excepcionais.
Nelsontruran
2

Eu acho que esta é uma resposta direta:

public static IEnumerable<string> Split(this string str, int chunkSize)
    {
        if(string.IsNullOrEmpty(str) || chunkSize<1)
            throw new ArgumentException("String can not be null or empty and chunk size should be greater than zero.");
        var chunkCount = str.Length / chunkSize + (str.Length % chunkSize != 0 ? 1 : 0);
        for (var i = 0; i < chunkCount; i++)
        {
            var startIndex = i * chunkSize;
            if (startIndex + chunkSize >= str.Length)
                yield return str.Substring(startIndex);
            else
                yield return str.Substring(startIndex, chunkSize);
        }
    }

E abrange casos extremos.

Milad
fonte
2

Eu sei que a pergunta tem anos, mas aqui está uma implementação do Rx. Ele lida com o length % chunkSize != 0problema imediatamente:

   public static IEnumerable<string> Chunkify(this string input, int size)
        {
            if(size < 1)
                throw new ArgumentException("size must be greater than 0");

            return input.ToCharArray()
                .ToObservable()
                .Buffer(size)            
                .Select(x => new string(x.ToArray()))
                .ToEnumerable();
        }
Krzysztof Skowronek
fonte
1

Eu desenvolvi um pouco a solução do João. O que fiz de maneira diferente é que, no meu método, você pode especificar se deseja retornar a matriz com os caracteres restantes ou se deseja truncá-los se os caracteres finais não corresponderem ao tamanho do pedaço necessário, acho que é bastante flexível e o código é bastante direto:

using System;
using System.Linq;
using System.Text.RegularExpressions;

namespace SplitFunction
{
    class Program
    {
        static void Main(string[] args)
        {
            string text = "hello, how are you doing today?";
            string[] chunks = SplitIntoChunks(text, 3,false);
            if (chunks != null)
            {
                chunks.ToList().ForEach(e => Console.WriteLine(e));
            }

            Console.ReadKey();
        }

        private static string[] SplitIntoChunks(string text, int chunkSize, bool truncateRemaining)
        {
            string chunk = chunkSize.ToString(); 
            string pattern = truncateRemaining ? ".{" + chunk + "}" : ".{1," + chunk + "}";

            string[] chunks = null;
            if (chunkSize > 0 && !String.IsNullOrEmpty(text))
                chunks = (from Match m in Regex.Matches(text,pattern)select m.Value).ToArray(); 

            return chunks;
        }     
    }
}
Denys Wessels
fonte
1
    public static List<string> SplitByMaxLength(this string str)
    {
        List<string> splitString = new List<string>();

        for (int index = 0; index < str.Length; index += MaxLength)
        {
            splitString.Add(str.Substring(index, Math.Min(MaxLength, str.Length - index)));
        }

        return splitString;
    }
Rikin Patel
fonte
Você esqueceu o parâmetro MaxLength.
Nyerguds
1

Alterado ligeiramente para retornar peças cujo tamanho não seja igual a chunkSize

public static IEnumerable<string> Split(this string str, int chunkSize)
    {
        var splits = new List<string>();
        if (str.Length < chunkSize) { chunkSize = str.Length; }
        splits.AddRange(Enumerable.Range(0, str.Length / chunkSize).Select(i => str.Substring(i * chunkSize, chunkSize)));
        splits.Add(str.Length % chunkSize > 0 ? str.Substring((str.Length / chunkSize) * chunkSize, str.Length - ((str.Length / chunkSize) * chunkSize)) : string.Empty);
        return (IEnumerable<string>)splits;
    }
Abhishek Shrestha
fonte
Não tenho certeza se vejo o uso de back-casting Listpara isso IEnumerable; tudo o que faz é ocultar funções específicas da lista que você pode querer usar. Não há nenhuma desvantagem em apenas retornar o arquivo List.
Nyerguds
1

Não me lembro de quem me deu isso, mas funciona muito bem. Eu testei várias maneiras de dividir tipos numerosos em grupos. O uso seria assim ...

List<string> Divided = Source3.Chunk(24).Select(Piece => string.Concat<char>(Piece)).ToList();

O código de extensão ficaria assim ...

#region Chunk Logic
private class ChunkedEnumerable<T> : IEnumerable<T>
{
    class ChildEnumerator : IEnumerator<T>
    {
        ChunkedEnumerable<T> parent;
        int position;
        bool done = false;
        T current;


        public ChildEnumerator(ChunkedEnumerable<T> parent)
        {
            this.parent = parent;
            position = -1;
            parent.wrapper.AddRef();
        }

        public T Current
        {
            get
            {
                if (position == -1 || done)
                {
                    throw new InvalidOperationException();
                }
                return current;

            }
        }

        public void Dispose()
        {
            if (!done)
            {
                done = true;
                parent.wrapper.RemoveRef();
            }
        }

        object System.Collections.IEnumerator.Current
        {
            get { return Current; }
        }

        public bool MoveNext()
        {
            position++;

            if (position + 1 > parent.chunkSize)
            {
                done = true;
            }

            if (!done)
            {
                done = !parent.wrapper.Get(position + parent.start, out current);
            }

            return !done;

        }

        public void Reset()
        {
            // per http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
            throw new NotSupportedException();
        }
    }

    EnumeratorWrapper<T> wrapper;
    int chunkSize;
    int start;

    public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
    {
        this.wrapper = wrapper;
        this.chunkSize = chunkSize;
        this.start = start;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new ChildEnumerator(this);
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

}
private class EnumeratorWrapper<T>
{
    public EnumeratorWrapper(IEnumerable<T> source)
    {
        SourceEumerable = source;
    }
    IEnumerable<T> SourceEumerable { get; set; }

    Enumeration currentEnumeration;

    class Enumeration
    {
        public IEnumerator<T> Source { get; set; }
        public int Position { get; set; }
        public bool AtEnd { get; set; }
    }

    public bool Get(int pos, out T item)
    {

        if (currentEnumeration != null && currentEnumeration.Position > pos)
        {
            currentEnumeration.Source.Dispose();
            currentEnumeration = null;
        }

        if (currentEnumeration == null)
        {
            currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
        }

        item = default(T);
        if (currentEnumeration.AtEnd)
        {
            return false;
        }

        while (currentEnumeration.Position < pos)
        {
            currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
            currentEnumeration.Position++;

            if (currentEnumeration.AtEnd)
            {
                return false;
            }

        }

        item = currentEnumeration.Source.Current;

        return true;
    }

    int refs = 0;

    // needed for dispose semantics 
    public void AddRef()
    {
        refs++;
    }

    public void RemoveRef()
    {
        refs--;
        if (refs == 0 && currentEnumeration != null)
        {
            var copy = currentEnumeration;
            currentEnumeration = null;
            copy.Source.Dispose();
        }
    }
}
/// <summary>Speed Checked.  Works Great!</summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
    if (chunksize < 1) throw new InvalidOperationException();

    var wrapper = new EnumeratorWrapper<T>(source);

    int currentPos = 0;
    T ignore;
    try
    {
        wrapper.AddRef();
        while (wrapper.Get(currentPos, out ignore))
        {
            yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
            currentPos += chunksize;
        }
    }
    finally
    {
        wrapper.RemoveRef();
    }
}
#endregion
Jacob Raines
fonte
1
class StringHelper
{
    static void Main(string[] args)
    {
        string str = "Hi my name is vikas bansal and my email id is [email protected]";
        int offSet = 10;

        List<string> chunks = chunkMyStr(str, offSet);

        Console.Read();
    }

    static List<string> chunkMyStr(string str, int offSet)
    {


        List<string> resultChunks = new List<string>();

        for (int i = 0; i < str.Length; i += offSet)
        {
            string temp = str.Substring(i, (str.Length - i) > offSet ? offSet : (str.Length - i));
            Console.WriteLine(temp);
            resultChunks.Add(temp);


        }

        return resultChunks;
    }
}
Vikas Bansal
fonte
Você pode melhorar um pouco o seu código: altere a expressão de incremento i += offSetpara sua forexpressão.
precisa saber é o seguinte
1

Modificado (agora aceita qualquer não nulo stringe qualquer positiva chunkSize) Konstantin Spirin solução 's:

public static IEnumerable<String> Split(String value, int chunkSize) {
  if (null == value)
    throw new ArgumentNullException("value");
  else if (chunkSize <= 0)
    throw new ArgumentOutOfRangeException("chunkSize", "Chunk size should be positive");

  return Enumerable
    .Range(0, value.Length / chunkSize + ((value.Length % chunkSize) == 0 ? 0 : 1))
    .Select(index => (index + 1) * chunkSize < value.Length 
      ? value.Substring(index * chunkSize, chunkSize)
      : value.Substring(index * chunkSize));
}

Testes:

  String source = @"ABCDEF";

  // "ABCD,EF"
  String test1 = String.Join(",", Split(source, 4));
  // "AB,CD,EF"
  String test2 = String.Join(",", Split(source, 2));
  // "ABCDEF"
  String test3 = String.Join(",", Split(source, 123));
Dmitry Bychenko
fonte
1
static List<string> GetChunks(string value, int chunkLength)
{
    var res = new List<string>();
    int count = (value.Length / chunkLength) + (value.Length % chunkLength > 0 ? 1 : 0);
    Enumerable.Range(0, count).ToList().ForEach(f => res.Add(value.Skip(f * chunkLength).Take(chunkLength).Select(z => z.ToString()).Aggregate((a,b) => a+b)));
    return res;
}

demonstração

ren
fonte
este mantém o restante da string (após a divisão), mesmo que seja menor que "chunkLenght", obrigado
Jason Loki Smith
0

Com base em outras respostas de pôsteres, além de algumas amostras de uso:

public static string FormatSortCode(string sortCode)
{
    return ChunkString(sortCode, 2, "-");
}
public static string FormatIBAN(string iban)
{
    return ChunkString(iban, 4, "&nbsp;&nbsp;");
}

private static string ChunkString(string str, int chunkSize, string separator)
{
    var b = new StringBuilder();
    var stringLength = str.Length;
    for (var i = 0; i < stringLength; i += chunkSize)
    {
        if (i + chunkSize > stringLength) chunkSize = stringLength - i;
        b.Append(str.Substring(i, chunkSize));
        if (i+chunkSize != stringLength)
            b.Append(separator);
    }
    return b.ToString();
}
Tom Gullen
fonte
0

Usando as extensões de buffer da biblioteca IX

    static IEnumerable<string> Split( this string str, int chunkSize )
    {
        return str.Buffer(chunkSize).Select(l => String.Concat(l));
    }
bradgonesurfing
fonte