Dividere una stringa in pezzi di una certa dimensione


218

Supponiamo di avere una stringa:

string str = "1111222233334444"; 

Come posso spezzare questa stringa in pezzi di qualche dimensione?

ad esempio, suddividerlo in dimensioni di 4 restituirebbe stringhe:

"1111"
"2222"
"3333"
"4444"

18
Perché usare LINQ o regexes quando le funzioni standard di manipolazione delle stringhe di C # possono farlo con meno sforzo e più velocità? Inoltre, cosa succede se la stringa è un numero dispari di caratteri di lunghezza?
Ian Kemp,

7
"Vorrei evitare i loop" - perché?
Mitch Wheat,

12
L'uso di un semplice loop è sicuramente ciò che offre le migliori prestazioni.
Guffa,

4
nichesoftware.co.nz/blog/200909/linq-vs-loop-performance è un ottimo confronto tra linq e il looping effettivo su un array. Dubito che troverai mai linq più veloce del codice scritto manualmente perché continua a chiamare i delegati di runtime che sono difficili da ottimizzare. Linq è più divertente però :)
Blindy,

2
Se stai usando LINQ o regex, il loop è ancora lì.
Anton Tykhyy

Risposte:


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

Si noti che potrebbe essere necessario un codice aggiuntivo per gestire con grazia i casi limite ( nullo svuotare la stringa di chunkSize == 0input, la lunghezza della stringa di input non divisibile per chunkSize, ecc.). La domanda originale non specifica alcun requisito per questi casi limite e nella vita reale i requisiti potrebbero variare, quindi non rientrano nell'ambito di questa risposta.


3
@Harry Buona cattura! Questo può essere risolto con un'espressione ternaria drop-in sul parametro count della sottostringa. Qualcosa come: (i * chunkSize + chunkSize <= str.Length) ? chunkSize : str.Length - i * chunkSize. Un ulteriore problema è che questa funzione non tiene conto del fatto che str è nullo. Questo può essere risolto avvolgendo l'intera istruzione return in un'altra espressione ternaria: (str != null) ? ... : Enumerable.Empty<String>();.
Drew Spickes il

7
Questo era vicino, ma a differenza dei precedenti 30 utenti, ho dovuto cambiare il limite di conteggio del loop di Range da str.Length / chunkSizeadouble length = str.Length; double size = chunkSize; int count = (int)Math.Ceiling(length/size); return Enumerable.Range(0, count)...
gap

4
@KonstantinSpirin Sono d'accordo se il codice ha funzionato. Gestisce solo il caso in cui una stringa è un multiplo di chunkSize, il resto della stringa viene perso. Per favore, ammend. Inoltre, tieni presente che LINQ ed è magico non è così facile da capire per qualcuno che vuole solo cercare una soluzione a questo problema. Una persona deve ora capire cosa fanno le funzioni Enumerable.Range () e .Select (). Non sosterrò che dovresti avere una comprensione di ciò per scrivere codice NET C # /. NET poiché queste funzioni sono presenti nel BCL da molti anni ormai.
CodeMonkeyKing

6
L'antipasto di argomento ha detto nei commenti che StringLength % 4 will always be 0. Se Linqnon è così facile da capire, allora ci sono altre risposte che usano loop e rese. Chiunque è libero di scegliere la soluzione che le piace di più. Puoi pubblicare il tuo codice come risposta e la gente voterà felicemente per questo.
Konstantin Spirin,

3
Enumerable.Range (0, (str.Length + chunkSize - 1) / chunkSize) .Seleziona (i => str.Substring (i * chunkSize, Math.Min (str.Length - i * chunkSize, chunkSize)))
Sten Petrov,

135

In una combinazione di colomba + le risposte di Konstatin ...

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

Questo funzionerà per tutte le stringhe che possono essere suddivise in un numero intero di blocchi e genererà un'eccezione in caso contrario.

Se si desidera supportare stringhe di qualsiasi lunghezza, è possibile utilizzare il seguente codice:

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

Tuttavia, il PO ha esplicitamente dichiarato di non averne bisogno; è un po 'più lungo e più difficile da leggere, leggermente più lento. Nello spirito di KISS e YAGNI, sceglierei la prima opzione: è probabilmente l'implementazione più efficiente possibile ed è molto breve, leggibile e, soprattutto, genera un'eccezione per l'input non conforme.


4
+1 vale un cenno del capo. un po 'colpisce chiodo sulla testa. sta cercando una sintassi sintetica e stai anche dando le prestazioni (probabilmente) migliori.
Colomba,

7
E se lo rendi "statico ... Chunk (questa stringa str, int chunkSize) {" hai anche un'altra "nuova" funzione C # in esso. Quindi puoi scrivere "1111222233334444" .Chunk (4).
MartinStettner,

1
@MartinStettner: Questa è sicuramente un'idea decente se si tratta di un'operazione comune.
Eamon Nerbonne,

Dovresti includere solo quest'ultimo codice. Il primo richiede di comprendere e verificare che la stringa sia un multiplo di dimensioni del blocco prima dell'uso o di capire che non restituirà il resto della stringa.
CodeMonkeyKing

La domanda del PO non chiarisce se ha bisogno di quella funzionalità. La prima soluzione è più semplice, più veloce e affidabile non riesce con un'eccezione se la stringa non può essere suddivisa uniformemente nella dimensione del blocco specificata. Concordo sul fatto che restituire risultati "sbagliati" sarebbe negativo, ma non è quello che fa - genera solo un'eccezione, quindi sarei d'accordo con l'uso se riesci a convivere con la limitazione.
Eamon Nerbonne,

56

Perché non i loop? Ecco qualcosa che lo farebbe abbastanza bene:

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

Non so come gestiresti il ​​caso in cui la stringa non è un fattore 4, ma non dici che la tua idea non è possibile, ti stai solo chiedendo la motivazione per cui un semplice ciclo per lo fa molto bene? Ovviamente quanto sopra potrebbe essere pulito e persino inserito come metodo di estensione.

O come menzionato nei commenti, sai che è / 4 allora

str = "1111222233334444";
for (int i = 0; i < stringLength; i += chunkSize) 
  {Console.WriteLine(str.Substring(i, chunkSize));} 

1
Puoi tirare int chunkSize = 4fuori dal ciclo. Sarà modificato solo nel passaggio finale.
John Feminella,

+1 per una soluzione semplice ed efficace: ecco come l'avrei fatto, anche se avrei usato i += chunkSizeinvece.
Ian Kemp,

Probabilmente un piccolo cavillo, ma probabilmente dovresti anche estrarre str.Lengthil ciclo e passare a una variabile locale. L'ottimizzatore C # potrebbe essere in grado di incorporare la lunghezza dell'array, ma penso che il codice scritto farà una chiamata di metodo su ogni ciclo, il che non è efficiente, poiché la dimensione di strnon cambia mai.
Daniel Pryden,

@Daniel, metti la tua idea lì dentro. anche se non sono sicuro che questo non sia calcolato in fase di esecuzione, ma questa è un'altra domanda;)
dove

@Daniel tornando a questo, abbastanza sicuro che questa ottimizzazione sarebbe stata estratta dal compilatore.
Colomba,

41

Utilizzando espressioni regolari e Linq :

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

Trovo che questo sia più leggibile, ma è solo un'opinione personale. Può anche essere un one-liner:).


7
Cambia il modello in @ "\ d {1,4}" e funziona per qualsiasi lunghezza di stringa. :)
Guffa,

3
+1 Anche se questo è più lento rispetto alle altre soluzioni, è sicuramente molto leggibile. Non mi è chiaro se l'OP richiede cifre o caratteri arbitrari; probabilmente sarebbe saggio sostituire la \dclasse di caratteri con un .e specificare RegexOptions.Singleline.
Eamon Nerbonne,

2
o solo Regex.Matches (s, @ "\ d {1,4}"). Seleziona (m => m.Value) .ToList (); Non ho mai avuto il punto di questa sintassi alternativa che serve solo a offuscare che stiamo usando metodi di estensione.
The Dag

38

Questo si basa sulla soluzione @dove ma implementato come metodo di estensione.

Benefici:

  • Metodo di estensione
  • Copre custodie angolari
  • Divide la stringa con qualsiasi carattere: numeri, lettere, altri simboli

Codice

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

Test unitari rimossi per brevità (vedi revisione precedente )


Soluzione interessante, ma per evitare controlli oltre null sull'input, sembra più logico consentire a una stringa vuota di restituire solo una singola parte di stringa vuota:if (str.Length == 0) yield return String.Empty; else { for... }
Nyerguds

Voglio dire, è così che String.Split normale gestisce le stringhe vuote; restituisce una voce di stringa vuota.
Nyerguds,

Nota a margine: il tuo esempio di utilizzo è sbagliato. Non puoi semplicemente IEnumerableeseguire il cast in array, soprattutto non implicitamente.
Nyerguds,

Personalmente mi piace chiamare quel metodo Chunkify.. Non è mio, non ricordo dove ho visto quel nome, ma mi è sembrato molto carino
quetzalcoatl

20

Come va per un one-liner?

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

Con questa regex non importa se l'ultimo pezzo è inferiore a quattro personaggi, perché guarda sempre e solo i personaggi dietro di esso.

Sono sicuro che questa non è la soluzione più efficiente, ma ho dovuto buttarla là fuori.


in target.Lenght % ChunckSize == 0tal caso restituisce una riga vuota aggiuntiva, ad es.List<string> result = new List<string>(Regex.Split("fooo", @"(?<=\G.{4})", RegexOptions.Singleline));
fubo

9

Non è carino e non è veloce, ma funziona, è un one-liner ed è 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();

È garantito che GroupBy mantenga l'ordine degli elementi?
Konstantin Spirin,

ToCharArraynon è necessario poiché lo stringè IEnumerable<char>.
giovedì

8

Di recente ho dovuto scrivere qualcosa per realizzare questo lavoro, quindi ho pensato di pubblicare la mia soluzione a questo problema. Come bonus aggiuntivo, la funzionalità di questa soluzione fornisce un modo per dividere la stringa nella direzione opposta e gestisce correttamente i caratteri unicode come precedentemente menzionato da Marvin Pinto. Quindi, eccolo qui:

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

Inoltre, ecco un link immagine ai risultati dell'esecuzione di questo codice: http://i.imgur.com/16Iih.png


1
Ho notato un problema con questo codice. Hai {str.ToString()}alla fine della tua prima dichiarazione IF. Sei sicuro di non volere dire str.String? Ho avuto un problema con il codice sopra, ho apportato quella modifica e tutto ha funzionato.
gunr2171,

@ gunr2171 Sembra che se str == null, quella riga darà anche una NullReferenceException.
John Zabroski,

5

Questo dovrebbe essere molto più veloce ed efficiente rispetto all'utilizzo di LINQ o altri approcci qui utilizzati.

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

Questo sembra come fa la verifica presto, ma non è così. Non viene visualizzato un errore fino a quando non si inizia a enumerare l'enumerabile. È necessario suddividere la funzione in due parti, in cui la prima parte verifica l'argomento e quindi restituisce i risultati della seconda parte privata che esegue l'enumerazione.
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;
}

4

Puoi usare morelinq di Jon Skeet. Usa il batch come:

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

Ciò restituirà 4 blocchi per la stringa "1111222233334444". Se la lunghezza della stringa è inferiore o uguale alla dimensione del blocco Batch, la stringa verrà restituita come unico elemento diIEnumerable<string>

Per uscita:

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

e darà:

1111
2222
3333
4444

Tra gli autori di MoreLINQ vedo Jonathan Skeet , ma non Jon Skeet . Quindi volevi dire l' Jon Skeet, o cosa? ;-)
Sнаđошƒаӽ il

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 un altro approccio:

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

3

Sei anni dopo o_O

Solo perché

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

Le custodie perimetrali AFAIK sono gestite

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

Che dire del limite "input is an empty string"? Mi aspetto che, proprio come con Split, restituisca un IEnumerable con una singola stringa vuota contenente una voce.
Nyerguds,

3

Semplice e breve:

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

Perché non usare .?
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;
}

Gestisce correttamente la lunghezza della stringa di input non divisibile per chunkSize.

Si noti che potrebbe essere necessario un codice aggiuntivo per gestire con grazia i casi limite (stringa di input nulla o vuota, chunkSize == 0).


2

Un suggerimento importante se la stringa che viene tagliata deve supportare tutti i caratteri Unicode.

Se la stringa deve supportare caratteri internazionali come 𠀋, quindi suddividere la stringa utilizzando la classe System.Globalization.StringInfo. Usando StringInfo, puoi dividere la stringa in base al numero di elementi di testo.

string internationalString = '𠀋';

La stringa sopra ha una lunghezza di 2, poiché la String.Lengthproprietà restituisce il numero di oggetti Char in questa istanza, non il numero di caratteri Unicode.


2

Risposta migliore, più semplice e generica :).

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

Il calcolo della lunghezza nell'ultima riga è ridondante, è sufficiente utilizzare il Substringsovraccarico che non richiede il parametro length originalString.Substring(i). Inoltre puoi usare >invece che >=nel tuo assegno.
Racil Hilan,

@RacilHilan Proverò le modifiche al codice con il tuo suggerimento e aggiornerò la risposta. Sono contento che qualcuno con una tale reputazione abbia avuto il tempo di rivedere il mio codice. :) Grazie, Sandeep
Sandeep Kushwah,

2

Personalmente preferisco la mia soluzione :-)

Gestisce:

  • Lunghezze di stringa che sono un multiplo della dimensione del blocco.
  • Lunghezze di stringa che NON sono un multiplo della dimensione del blocco.
  • Lunghezze delle stringhe inferiori alle dimensioni del blocco.
  • NULL e stringhe vuote (genera un'eccezione).
  • Dimensioni del blocco inferiori a 1 (genera un'eccezione).

È implementato come metodo di estensione e calcola in anticipo il numero di blocchi che genererà. Controlla l'ultimo blocco perché nel caso in cui la lunghezza del testo non sia un multiplo, deve essere più breve. Pulito, breve, facile da capire ... e funziona!

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

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

1
Mi piace molto questa risposta, ma forse dovresti usare if ((i + 1) * chunk> = input.Length) invece di provare / catturare poiché le eccezioni sono per casi eccezionali.
nelsontruran,

2

Penso che questa sia una risposta semplice:

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 copre casi limite.


2

So che la domanda ha anni, ma ecco un'implementazione di Rx. Gestisce il length % chunkSize != 0problema immediatamente:

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

1

Ho leggermente sviluppato la soluzione di João. Quello che ho fatto diversamente è nel mio metodo che puoi effettivamente specificare se vuoi restituire l'array con i caratteri rimanenti o se vuoi troncarli se i caratteri finali non corrispondono alla lunghezza del blocco richiesta, penso che sia piuttosto flessibile e il il codice è abbastanza semplice:

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

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

Hai dimenticato il parametro MaxLength.
Nyerguds,

1

Modificato leggermente per restituire parti di dimensioni non uguali 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;
    }

Non sono sicuro Vedo l'uso di back-fusione che Lista IEnumerable; tutto ciò che fa è nascondere le funzioni specifiche dell'elenco che potresti voler usare. Non c'è alcun aspetto negativo a restituire il file List.
Nyerguds,

1

Non ricordo chi mi ha dato questo, ma funziona benissimo. Ho testato in diversi modi diversi i gruppi enumerabili. L'utilizzo sarebbe proprio così ...

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

Il codice di estensione sarebbe simile a questo ...

#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

1
class StringHelper
{
    static void Main(string[] args)
    {
        string str = "Hi my name is vikas bansal and my email id is bansal.vks@gmail.com";
        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;
    }
}

Puoi migliorare leggermente il tuo codice: sposta l'espressione dell'incremento i += offSetnella tua forespressione.
JimiLoe,

1

Modificato (ora accetta qualsiasi non nulli stringe qualsiasi positiva chunkSize) Konstantin Spirin soluzione '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));
}

test:

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

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

dimostrazione


questo mantiene il resto della stringa (post split) anche se è più corto di "chunkLenght", grazie
Jason Loki Smith

0

Basato sulle risposte di altri poster, insieme ad alcuni esempi di utilizzo:

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

0

Utilizzo delle estensioni buffer dalla libreria IX

    static IEnumerable<string> Split( this string str, int chunkSize )
    {
        return str.Buffer(chunkSize).Select(l => String.Concat(l));
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.