Dividir una cuerda en trozos de cierto tamaño

218

Supongamos que tengo una cadena:

string str = "1111222233334444"; 

¿Cómo puedo romper esta cadena en trozos de algún tamaño?

por ejemplo, dividir esto en tamaños de 4 devolvería cadenas:

"1111"
"2222"
"3333"
"4444"
Jeff Mercado
fuente
18
¿Por qué usar LINQ o expresiones regulares cuando las funciones de manipulación de cadenas estándar de C # pueden hacer esto con menos esfuerzo y más velocidad? Además, ¿qué sucede si la cadena tiene un número impar de caracteres de longitud?
Ian Kemp
77
"Me gustaría evitar bucles", ¿por qué?
Mitch Wheat
12
Usar un bucle simple es definitivamente lo que brinda el mejor rendimiento.
Guffa
44
nichesoftware.co.nz/blog/200909/linq-vs-loop-performance es una comparación bastante buena entre linq y el bucle real sobre una matriz. Dudo que alguna vez encuentre linq más rápido que el código escrito manualmente porque sigue llamando a delegados en tiempo de ejecución que son difíciles de optimizar. Sin embargo, Linq es más divertido :)
Blindy
2
Ya sea que esté utilizando LINQ o expresiones regulares, el bucle sigue ahí.
Anton Tykhyy

Respuestas:

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

Tenga en cuenta que es posible que se requiera código adicional para manejar con gracia los casos extremos ( nullo cadena de entrada vacía chunkSize == 0, longitud de cadena de entrada no divisible por chunkSize, etc.). La pregunta original no especifica ningún requisito para estos casos extremos y en la vida real los requisitos pueden variar, por lo que están fuera del alcance de esta respuesta.

Konstantin Spirin
fuente
3
@ Harry Buena captura! Esto puede remediarse con una expresión ternaria directa en el parámetro de recuento de la subcadena. Algo así como: (i * chunkSize + chunkSize <= str.Length) ? chunkSize : str.Length - i * chunkSize. Un problema adicional es que esta función no tiene en cuenta que str sea nulo. Esto puede remediarse envolviendo toda la instrucción de retorno en otra expresión ternaria: (str != null) ? ... : Enumerable.Empty<String>();.
Drew Spickes
77
Esto estuvo cerca, pero a diferencia de los 30 votos anteriores, tuve que cambiar el límite de conteo de bucle de Rango de str.Length / chunkSizeadouble length = str.Length; double size = chunkSize; int count = (int)Math.Ceiling(length/size); return Enumerable.Range(0, count)...
gap
44
@ KonstantinSpirin Estoy de acuerdo si el código funcionó. Solo maneja el caso donde una cadena es un múltiplo de chunkSize, el resto de la cadena se pierde. Por favor, enmiende. También tenga en cuenta que LINQ y su magia no es tan fácil de entender para alguien que solo quiere buscar una solución a este problema. Una persona ahora debe comprender lo que hacen las funciones Enumerable.Range () y .Select (). No voy a argumentar que debe comprenderlo para escribir código C # / .NET ya que estas funciones han estado en el BCL durante muchos años.
CodeMonkeyKing
66
El iniciador del tema dijo en comentarios que StringLength % 4 will always be 0. Si Linqno es tan fácil de entender, existen otras respuestas que usan bucles y rendimientos. Cualquiera es libre de elegir la solución que más le guste. Puede publicar su código como respuesta y la gente felizmente votará por él.
Konstantin Spirin
3
Enumerable.Range (0, (str.Length + chunkSize - 1) / chunkSize) .Select (i => str.Substring (i * chunkSize, Math.Min (str.Length - i * chunkSize, chunkSize)))
Sten Petrov
135

En una combinación de las respuestas de dove + Konstatin ...

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

Esto funcionará para todas las cadenas que se pueden dividir en un número entero de fragmentos, y de lo contrario generará una excepción.

Si desea admitir cadenas de cualquier longitud, puede usar el siguiente 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));
}

Sin embargo, el OP declaró explícitamente que no necesita esto; es algo más largo y más difícil de leer, un poco más lento. En el espíritu de KISS y YAGNI, elegiría la primera opción: probablemente sea la implementación más eficiente posible, y es muy corta, legible y, lo que es más importante, arroja una excepción para la entrada no conforme.

Eamon Nerbonne
fuente
44
+1 vale la pena asentir. un poco golpea el clavo sobre la cabeza. está buscando una sintaxis sucinta y también le está dando (probablemente) un mejor rendimiento.
paloma
77
Y si lo hace "estático ... Chunk (esta cadena str, int chunkSize) {" incluso tiene uno más "nuevo" C # -Feature en él. Luego puede escribir "1111222233334444" .Chunk (4).
MartinStettner
1
@ MartininStettner: Esa es ciertamente una idea decente si esta es una operación común.
Eamon Nerbonne
Solo debe incluir el último código. El primero requiere que comprenda y pruebe que la cadena sea un múltiplo del tamaño de fragmento antes de usarla, o que comprenda que no devolverá el resto de la cadena.
CodeMonkeyKing
La pregunta del OP no deja en claro si necesita esa funcionalidad. La primera solución es más simple, más rápida y confiable, con una excepción si la cadena no se puede dividir uniformemente en el tamaño de fragmento especificado. Estoy de acuerdo en que devolver resultados "incorrectos" sería malo, pero eso no es lo que hace, solo arroja una excepción, por lo que estaría bien usarlo si puede vivir con la limitación.
Eamon Nerbonne
56

¿Por qué no bucles? Aquí hay algo que lo haría bastante bien:

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

No sé cómo lidiarías con el caso en el que la cadena no es un factor de 4, pero no digo que tu idea no sea posible, solo me pregunto la motivación si un bucle simple lo hace muy bien. Obviamente, lo anterior podría limpiarse e incluso ponerse como un método de extensión.

O como se menciona en los comentarios, ya sabes que es / 4

str = "1111222233334444";
for (int i = 0; i < stringLength; i += chunkSize) 
  {Console.WriteLine(str.Substring(i, chunkSize));} 
paloma
fuente
1
Puede tirar int chunkSize = 4fuera del bucle. Solo se modificará en el pase final.
John Feminella
+1 para una solución simple y efectiva: así es como lo habría hecho, aunque en su lugar lo hubiera usado i += chunkSize.
Ian Kemp
Probablemente una objeción menor, pero probablemente también deberías sacarla str.Lengthdel bucle y ponerla en una variable local. El optimizador de C # puede alinear la longitud de la matriz, pero creo que el código tal como está escrito hará una llamada al método en cada bucle, lo que no es eficiente, ya que el tamaño de strnunca cambia.
Daniel Pryden
@Daniel, pon tu idea ahí. aunque no estoy seguro de que esto no se calcule en tiempo de ejecución, pero esa es otra pregunta;)
zambulló el
@Daniel volviendo a esto, bastante seguro de que esta optimización sería extraída por el compilador.
paloma
41

Usando expresiones regulares y Linq :

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

Creo que esto es más legible, pero es solo una opinión personal. También puede ser de una sola línea:).

João Silva
fuente
77
Cambie el patrón a @ "\ d {1,4}" y funciona para cualquier longitud de cadena. :)
Guffa
3
+1 Aunque esto es más lento que las otras soluciones, definitivamente es muy legible. No me queda claro si el OP requiere dígitos o caracteres arbitrarios; probablemente sería prudente reemplazar la \dclase de caracteres con a .y especificar RegexOptions.Singleline.
Eamon Nerbonne
2
o simplemente Regex.Matches (s, @ "\ d {1,4}"). Seleccione (m => m.Value) .ToList (); Nunca entendí el objetivo de esta sintaxis alternativa que solo sirve para ofuscar que estamos usando métodos de extensión.
El Dag
38

Esto se basa en la solución @dove pero se implementa como un método de extensión.

Beneficios:

  • Método de extensión
  • Cubre cajas de esquina
  • Divide la cadena con cualquier carácter: números, letras, otros 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

Pruebas unitarias eliminadas por brevedad (ver revisión anterior )

oleksii
fuente
Solución interesante, pero en aras de evitar verificaciones más allá de nulo en la entrada, parece más lógico permitir que una cadena vacía solo devuelva una sola parte de cadena vacía:if (str.Length == 0) yield return String.Empty; else { for... }
Nyerguds
Quiero decir, así es como el String.Split normal maneja cadenas vacías; devuelve una entrada de cadena vacía.
Nyerguds
Nota al margen: su ejemplo de uso es incorrecto. No puede simplemente convertir IEnumerablea matriz, especialmente no implícitamente.
Nyerguds
Personalmente me gusta llamar a ese método Chunkify... No es mío, no recuerdo dónde he visto ese nombre, pero me pareció muy agradable
Quetzalcoatl
20

¿Cómo es esto para una sola línea?

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

Con esta expresión regular no importa si el último fragmento tiene menos de cuatro caracteres, ya que solo mira a los personajes detrás de él.

Estoy seguro de que esta no es la solución más eficiente, pero solo tuve que tirarla.

Alan Moore
fuente
en caso de target.Lenght % ChunckSize == 0que devuelva una fila vacía adicional, por ejemploList<string> result = new List<string>(Regex.Split("fooo", @"(?<=\G.{4})", RegexOptions.Singleline));
fubo
9

No es bonito ni rápido, pero funciona, es de una sola línea y es 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
fuente
¿Se garantiza que GroupBy conserva el orden de los elementos?
Konstantin Spirin
ToCharArrayEs innecesario ya que stringes IEnumerable<char>.
juharr
8

Recientemente tuve que escribir algo que logre esto en el trabajo, así que pensé en publicar mi solución a este problema. Como una ventaja adicional, la funcionalidad de esta solución proporciona una manera de dividir la cadena en la dirección opuesta y maneja correctamente los caracteres unicode como se mencionó anteriormente por Marvin Pinto anteriormente. Asi que aqui esta:

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

Además, aquí hay un enlace de imagen a los resultados de ejecutar este código: http://i.imgur.com/16Iih.png

Michael Nelson
fuente
1
Noté un problema con este código. Usted tiene {str.ToString()}al final de su primer estado de cuenta IF. ¿Estás seguro de que no quisiste decir str.String? Tuve un problema con el código anterior, hice ese cambio y todo funcionó.
gunr2171
@ gunr2171 Parece que si str == null, esa línea también dará una NullReferenceException.
John Zabroski
5

Esto debería ser mucho más rápido y más eficiente que usar LINQ u otros enfoques utilizados aquí.

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
fuente
Este aspecto , como lo hace la comprobación temprana, pero no es así. No recibirá un error hasta que comience a enumerar lo enumerable. Debe dividir su función en dos partes, donde la primera parte verifica los argumentos y luego devuelve los resultados de la segunda parte privada que realiza la enumeración.
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
fuente
4

Puedes usar morelinq de Jon Skeet. Usar lote como:

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

Esto devolverá 4 trozos para la cadena "1111222233334444". Si la longitud de la cadena es menor o igual que el tamaño del fragmentoBatch , devolverá la cadena como el único elemento deIEnumerable<string>

Para salida:

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

y dará:

1111
2222
3333
4444
Habib
fuente
Entre los autores de MoreLINQ veo a Jonathan Skeet , pero no Jon Skeet . Entonces, ¿te refieres al Jon Skeet, 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()));
}

y otro enfoque:

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

Seis años después o_O

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

o

    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 se manejan todos los casos de borde.

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
fuente
¿Qué pasa con el caso de borde "input is a empty string"? Esperaría que, al igual que con Split, devuelva un IEnumerable con una sola entrada que contenga una cadena vacía.
Nyerguds
3

Simple y corto:

// 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
fuente
¿Por qué no usar .?
marsze
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;
}

Maneja correctamente la longitud de la cadena de entrada no divisible por chunkSize.

Tenga en cuenta que es posible que se requiera código adicional para manejar correctamente los casos extremos (cadena de entrada nula o vacía, chunkSize == 0).

Troncho
fuente
2

Un consejo importante si la cadena que se está fragmentando debe admitir todos los caracteres Unicode.

Si la cadena es compatible con caracteres internacionales como 𠀋, entonces divídala usando la clase System.Globalization.StringInfo. Con StringInfo, puede dividir la cadena en función del número de elementos de texto.

string internationalString = '𠀋';

La cadena anterior tiene una longitud de 2, porque la String.Lengthpropiedad devuelve el número de objetos Char en este caso, no el número de caracteres Unicode.

Seth
fuente
2

La mejor, más fácil y genérica respuesta :).

    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
fuente
Calcular la longitud en la última línea es redundante, simplemente use la Substringsobrecarga que no requiere el parámetro de longitud originalString.Substring(i). También puede usar en >lugar de >=en su cheque.
Racil Hilan
@RacilHilan Probaré los cambios de código con su sugerencia y actualizaré la respuesta. Me alegra que alguien con tan buena reputación haya tenido tiempo de revisar mi código. :) Gracias, Sandeep
Sandeep Kushwah
2

Personalmente prefiero mi solución :-)

Lo maneja:

  • Longitudes de cadena que son múltiplos del tamaño del fragmento.
  • Longitudes de cadena que NO son múltiplos del tamaño del fragmento.
  • Longitudes de cadena que son más pequeñas que el tamaño del fragmento.
  • NULL y cadenas vacías (produce una excepción).
  • Tamaños de trozos más pequeños que 1 (produce una excepción).

Se implementa como un método de extensión y calcula de antemano el número de fragmentos que se generarán. Comprueba el último fragmento porque, en caso de que la longitud del texto no sea múltiple, debe ser más corta. Limpio, corto, fácil de entender ... ¡y 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
fuente
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
fuente
1
Me gusta mucho esta respuesta, pero tal vez debería usar if ((i + 1) * chunk> = input.Length) en lugar de try / catch, ya que las excepciones son para casos excepcionales.
nelsontruran
2

Creo que esta es una respuesta directa:

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

Y cubre casos extremos.

Milad
fuente
2

Sé que la pregunta tiene años, pero aquí hay una implementación de Rx. Maneja el length % chunkSize != 0problema fuera de la caja:

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

He acumulado un poco en la solución de João. Lo que he hecho de manera diferente es que, en mi método, puede especificar si desea devolver la matriz con los caracteres restantes o si desea truncarlos si los caracteres finales no coinciden con la longitud de fragmento requerida, creo que es bastante flexible y el el código es bastante sencillo:

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
fuente
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
fuente
Usted, eh, olvidó el parámetro MaxLength.
Nyerguds
1

Se modificó ligeramente para devolver partes cuyo tamaño no es igual al fragmento

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
fuente
No estoy seguro de que veo el uso de back-casting que Lista IEnumerable; todo lo que hace es ocultar funciones específicas de la Lista que tal vez quieras usar. No hay ningún inconveniente en solo devolver el List.
Nyerguds
1

No recuerdo quién me dio esto, pero funciona muy bien. Probé varias formas de dividir Enumerable tipos en grupos. El uso sería así ...

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

El código de extensión se vería así ...

#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
fuente
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
fuente
Puede mejorar ligeramente su código: cambie la expresión de incremento i += offSeta su forexpresión.
JimiLoe
1

Modificado (ahora se acepta cualquier nula no stringy cualquier positivo chunkSize) Konstantin Spirin solución '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));
}

Pruebas:

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

manifestación

ren
fuente
este mantiene el resto de la cadena (división posterior) incluso si es más corta que "chunkLenght", gracias
Jason Loki Smith
0

Basado en otras respuestas de carteles, junto con algunas muestras 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
fuente
0

Usando las extensiones de Buffer de la biblioteca IX

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