Fractionner une chaîne en morceaux d'une certaine taille

218

Supposons que j'avais une chaîne:

string str = "1111222233334444"; 

Comment puis-je diviser cette chaîne en morceaux d'une certaine taille?

par exemple, le diviser en tailles de 4 retournerait des chaînes:

"1111"
"2222"
"3333"
"4444"
Jeff Mercado
la source
18
Pourquoi utiliser LINQ ou des expressions régulières alors que les fonctions standard de manipulation de chaînes de C # peuvent le faire avec moins d'effort et plus de vitesse? De plus, que se passe-t-il si la chaîne a un nombre impair de caractères?
Ian Kemp
7
"Je voudrais éviter les boucles" - pourquoi?
Mitch Wheat
12
L'utilisation d'une boucle simple est certainement ce qui donne les meilleures performances.
Guffa
4
nichesoftware.co.nz/blog/200909/linq-vs-loop-performance est une assez bonne comparaison entre linq et le bouclage réel sur un tableau. Je doute que vous trouviez jamais linq plus rapidement que du code écrit manuellement, car il continue d'appeler des délégués d'exécution difficiles à optimiser. Linq est plus amusant cependant :)
Blindy
2
Que vous utilisiez LINQ ou des expressions rationnelles, la boucle est toujours là.
Anton Tykhyy

Réponses:

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

Veuillez noter qu'un code supplémentaire peut être requis pour gérer avec élégance les cas marginaux ( nullou chaîne d'entrée vide chunkSize == 0,, longueur de chaîne d'entrée non divisible par chunkSize, etc.). La question d'origine ne spécifie aucune exigence pour ces cas marginaux et dans la vie réelle, les exigences peuvent varier et sont donc hors de portée de cette réponse.

Konstantin Spirin
la source
3
@Harry Bonne prise! Cela peut être résolu avec une expression ternaire de dépôt sur le paramètre count de la sous-chaîne. Quelque chose comme: (i * chunkSize + chunkSize <= str.Length) ? chunkSize : str.Length - i * chunkSize. Un problème supplémentaire est que cette fonction ne tient pas compte du fait que str est nul. Cela peut être corrigé en enveloppant toute la déclaration de retour dans une autre expression ternaire: (str != null) ? ... : Enumerable.Empty<String>();.
Drew Spickes
7
C'était proche, mais contrairement aux 30 votants précédents, j'ai dû changer la limite de comptage de boucles de Range du str.Length / chunkSizeaudouble length = str.Length; double size = chunkSize; int count = (int)Math.Ceiling(length/size); return Enumerable.Range(0, count)...
gap
4
@KonstantinSpirin Je suis d'accord si le code a fonctionné. Il ne gère que le cas où une chaîne est un multiple de chunkSize, le reste de la chaîne est perdu. Veuillez corriger. Gardez également à l'esprit que LINQ et sa magie ne sont pas aussi faciles à comprendre pour quelqu'un qui veut simplement chercher une solution à ce problème. Une personne doit maintenant comprendre ce que font les fonctions Enumerable.Range () et .Select (). Je ne dirai pas que vous devriez avoir une compréhension de cela pour écrire du code C # /. NET car ces fonctions sont dans la BCL depuis de nombreuses années maintenant.
CodeMonkeyKing
6
Le sujet a déclaré dans les commentaires que StringLength % 4 will always be 0. Si ce Linqn'est pas aussi facile à comprendre, il existe d'autres réponses qui utilisent des boucles et des rendements. Tout le monde est libre de choisir la solution qu'elle préfère. Vous pouvez poster votre code comme réponse et les gens voteront volontiers pour lui.
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

Dans une combinaison de colombe + réponses de Konstatin ...

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

Cela fonctionnera pour toutes les chaînes qui peuvent être divisées en un nombre entier de morceaux et lèvera une exception dans le cas contraire.

Si vous souhaitez prendre en charge des chaînes de n'importe quelle longueur, vous pouvez utiliser le code suivant:

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

Cependant, le PO a déclaré explicitement qu'il n'en avait pas besoin; c'est un peu plus long et plus difficile à lire, légèrement plus lent. Dans l'esprit de KISS et YAGNI, j'irais avec la première option: c'est probablement l'implémentation la plus efficace possible, et elle est très courte, lisible et, surtout, lève une exception pour les entrées non conformes.

Eamon Nerbonne
la source
4
+1 vaut un signe de tête. frappe un peu clou sur la tête. il cherche un sytnax succinct et vous donnez également (probablement) de meilleures performances.
plongé le
7
Et si vous le rendez "statique ... Chunk (cette chaîne de caractères, int chunkSize) {" vous avez même un "nouveau" C # -Feature en plus. Ensuite, vous pouvez écrire "1111222233334444". Chunk (4).
MartinStettner
1
@MartinStettner: C'est certainement une idée décente s'il s'agit d'une opération courante.
Eamon Nerbonne
Vous ne devez inclure que ce dernier code. Le premier nécessite que vous compreniez et testiez que la chaîne est un multiple de la taille du bloc avant de l'utiliser, ou comprenez qu'elle ne renverra pas le reste de la chaîne.
CodeMonkeyKing
La question du PO ne précise pas s'il a besoin de cette fonctionnalité. La première solution est plus simple, plus rapide et échoue de manière fiable à une exception si la chaîne ne peut pas être divisée uniformément en la taille de bloc spécifiée. Je suis d'accord que renvoyer de «mauvais» résultats serait mauvais, mais ce n'est pas ce qu'il fait - cela lève juste une exception, donc je serais d'accord pour l'utiliser si vous pouvez vivre avec la limitation.
Eamon Nerbonne
56

Pourquoi pas des boucles? Voici quelque chose qui le ferait très 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();

Je ne sais pas comment vous traiteriez le cas où la chaîne n'est pas un facteur de 4, mais ne dites pas que votre idée n'est pas possible, vous vous demandez simplement la motivation si une simple boucle for le fait très bien? Évidemment, ce qui précède pourrait être nettoyé et même mis en place comme méthode d'extension.

Ou comme mentionné dans les commentaires, vous savez que c'est / 4 alors

str = "1111222233334444";
for (int i = 0; i < stringLength; i += chunkSize) 
  {Console.WriteLine(str.Substring(i, chunkSize));} 
Colombe
la source
1
Vous pouvez sortir int chunkSize = 4de la boucle. Il ne sera modifié que lors de la dernière passe.
John Feminella
+1 pour une solution simple et efficace - c'est ainsi que je l'aurais fait, bien que j'aurais utilisé à la i += chunkSizeplace.
Ian Kemp
Probablement un petit problème, mais vous devriez également retirer str.Lengthla boucle de la boucle et la placer dans une variable locale. L'optimiseur C # peut être en mesure d'inline la longueur du tableau, mais je pense que le code tel qu'il est écrit fera un appel de méthode sur chaque boucle, ce qui n'est pas efficace, car la taille de strne change jamais.
Daniel Pryden
@Daniel, mets ton idée dedans. bien que je ne suis pas sûr que cela ne serait pas calculé lors de l'exécution, mais c'est une autre question;)
plongé le
@Daniel y revient, à peu près sûr que cette optimisation serait extraite par le compilateur.
plongé
41

Utilisation d' expressions régulières et de Linq :

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

Je trouve cela plus lisible, mais ce n'est qu'une opinion personnelle. Il peut également s'agir d'un aller simple:).

João Silva
la source
7
Modifiez le modèle en @ "\ d {1,4}" et cela fonctionne pour n'importe quelle longueur de chaîne. :)
Guffa
3
+1 Bien que ce soit plus lent que les autres solutions, il est certainement très lisible. Il n'est pas clair pour moi si l'OP nécessite des chiffres ou des caractères arbitraires; il serait probablement judicieux de remplacer la \dclasse de caractères par a .et de spécifier RegexOptions.Singleline.
Eamon Nerbonne
2
ou simplement Regex.Matches (s, @ "\ d {1,4}"). Sélectionnez (m => m.Value) .ToList (); Je n'ai jamais compris l'intérêt de cette syntaxe alternative qui ne sert qu'à masquer que nous utilisons des méthodes d'extension.
Le Dag
38

Ceci est basé sur la solution @dove mais implémenté comme méthode d'extension.

Avantages:

  • Méthode d'extension
  • Couvre les caisses d'angle
  • Divise la chaîne avec n'importe quel caractère: chiffres, lettres, autres symboles

Code

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

Usage

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

Tests unitaires supprimés par souci de concision (voir la révision précédente )

oleksii
la source
Solution intéressante, mais pour éviter les vérifications au-delà de null sur l'entrée, il semble plus logique de permettre à une chaîne vide de renvoyer une seule partie de chaîne vide:if (str.Length == 0) yield return String.Empty; else { for... }
Nyerguds
Je veux dire, c'est ainsi que le String.Split normal gère les chaînes vides; il renvoie une entrée de chaîne vide.
Nyerguds
Note latérale: votre exemple d'utilisation est incorrect. Vous ne pouvez pas simplement convertir IEnumerableen tableau, surtout pas implicitement.
Nyerguds
Personnellement, j'aime appeler cette méthode Chunkify.. Ce n'est pas la mienne, je ne me souviens pas où j'ai vu ce nom, mais c'était très agréable pour moi
quetzalcoatl
20

Comment est-ce pour un one-liner?

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

Avec cette expression régulière, peu importe si le dernier morceau est inférieur à quatre caractères, car il ne regarde que les caractères derrière lui.

Je suis sûr que ce n'est pas la solution la plus efficace, mais je n'ai eu qu'à la lancer.

Alan Moore
la source
dans le cas où target.Lenght % ChunckSize == 0cela renvoie une ligne vide supplémentaire, par exempleList<string> result = new List<string>(Regex.Split("fooo", @"(?<=\G.{4})", RegexOptions.Singleline));
fubo
9

Ce n'est pas joli et ce n'est pas rapide, mais ça marche, c'est une doublure et c'est 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
la source
Est-il garanti que GroupBy préserve l'ordre des éléments?
Konstantin Spirin
ToCharArrayest inutile depuis l' stringest IEnumerable<char>.
juharr
8

J'ai récemment dû écrire quelque chose qui accomplit cela au travail, alors j'ai pensé publier ma solution à ce problème. En prime, la fonctionnalité de cette solution fournit un moyen de diviser la chaîne dans la direction opposée et elle gère correctement les caractères unicode comme mentionné précédemment par Marvin Pinto ci-dessus. Voici donc:

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

En outre, voici un lien d'image vers les résultats de l'exécution de ce code: http://i.imgur.com/16Iih.png

Michael Nelson
la source
1
J'ai remarqué un problème avec ce code. Vous avez {str.ToString()}à la fin de votre première déclaration IF. Êtes-vous sûr de ne pas vouloir dire str.String? J'ai eu un problème avec le code ci-dessus, j'ai fait ce changement et tout a fonctionné.
gunr2171
@ gunr2171 Il semble que si str == null, cette ligne donnera également une NullReferenceException.
John Zabroski
5

Cela devrait être beaucoup plus rapide et plus efficace que l'utilisation de LINQ ou d'autres approches utilisées ici.

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
la source
Cela ressemble à une vérification précoce, mais ce n'est pas le cas. Vous n'obtenez pas d'erreur tant que vous n'avez pas commencé à énumérer l'énumérable. Vous devez diviser votre fonction en deux parties, où la première partie vérifie l'argument, puis renvoie les résultats de la deuxième partie privée qui effectue l'énumération.
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
la source
4

Vous pouvez utiliser morelinq par Jon Skeet. Utilisez Batch comme:

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

Cela renverra 4 morceaux pour la chaîne "1111222233334444". Si la longueur de la chaîne est inférieure ou égale à la taille du bloc Batch, la chaîne sera renvoyée comme seul élément deIEnumerable<string>

Pour la sortie:

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

et cela donnera:

1111
2222
3333
4444
Habib
la source
Parmi les auteurs de MoreLINQ, je vois Jonathan Skeet , mais pas Jon Skeet . Alors, tu veux dire le Jon Skeet, ou quoi? ;-)
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()));
}

et une autre approche:

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
la source
3

Six ans plus tard o_O

Juste parce que

    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 tous les cas de bord sont traités.

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
la source
Qu'en est-il du cas de bord "l'entrée est une chaîne vide"? Je m'attends à ce que, tout comme avec Split, retourne un IEnumerable avec une seule entrée contenant une chaîne vide.
Nyerguds
3

Simple et court:

// 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
la source
Pourquoi ne pas utiliser .?
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;
}

Il gère correctement la longueur de chaîne d'entrée non divisible par chunkSize.

Veuillez noter qu'un code supplémentaire peut être requis pour gérer correctement les cas marginaux (chaîne d'entrée nulle ou vide, chunkSize == 0).

Troncho
la source
2

Un conseil important si la chaîne qui est fragmentée doit prendre en charge tous les caractères Unicode.

Si la chaîne doit prendre en charge les caractères internationaux comme 𠀋, divisez la chaîne à l'aide de la classe System.Globalization.StringInfo. À l'aide de StringInfo, vous pouvez fractionner la chaîne en fonction du nombre d'éléments de texte.

string internationalString = '𠀋';

La chaîne ci-dessus a une longueur de 2, car la String.Lengthpropriété renvoie le nombre d'objets Char dans cette instance, pas le nombre de caractères Unicode.

Seth
la source
2

Meilleure réponse, la plus simple et la plus générique :).

    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
la source
Le calcul de la longueur dans la dernière ligne est redondant, utilisez simplement la Substringsurcharge qui ne nécessite pas le paramètre de longueur originalString.Substring(i). Vous pouvez également utiliser >au lieu de >=dans votre chèque.
Racil Hilan
@RacilHilan Je vais tester les changements de code avec votre suggestion et mettre à jour la réponse. Je suis content que quelqu'un avec une si bonne réputation ait eu le temps de revoir mon code. :) Merci, Sandeep
Sandeep Kushwah
2

Personnellement je préfère ma solution :-)

Il gère:

  • Longueurs de chaîne qui sont un multiple de la taille du bloc.
  • Longueurs de chaîne qui ne sont PAS un multiple de la taille de bloc.
  • Longueurs de chaîne inférieures à la taille de bloc.
  • Chaînes NULL et vides (lève une exception).
  • Tailles de morceaux inférieures à 1 (lève une exception).

Il est implémenté comme une méthode d'extension et calcule le nombre de morceaux à générer au préalable. Il vérifie le dernier morceau car dans le cas où la longueur du texte n'est pas un multiple, il doit être plus court. Propre, court, facile à comprendre ... et fonctionne!

    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
la source
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
la source
1
J'aime beaucoup cette réponse, mais peut-être devriez-vous utiliser if ((i + 1) * chunk> = input.Length) au lieu de try / catch car les exceptions le sont pour des cas exceptionnels.
nelsontruran
2

Je pense que c'est une réponse simple:

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

Et cela couvre les cas marginaux.

Milad
la source
2

Je sais que la question date de plusieurs années, mais voici une implémentation Rx. Il gère le length % chunkSize != 0problème hors de la boîte:

   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
la source
1

J'ai un peu développé la solution de João. Ce que j'ai fait différemment, c'est que dans ma méthode, vous pouvez réellement spécifier si vous voulez retourner le tableau avec les caractères restants ou si vous voulez les tronquer si les caractères de fin ne correspondent pas à la longueur de bloc requise, je pense que c'est assez flexible et le le code est assez simple:

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
la source
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
la source
Vous avez, eh, oublié le paramètre MaxLength.
Nyerguds
1

Modifié légèrement pour renvoyer les pièces dont la taille n'est pas égale à 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
la source
Je ne suis pas sûr de voir l'utilisation du back-casting Listpour IEnumerable; tout ce que cela fait est de cacher les fonctions spécifiques à la liste que vous voudrez peut-être utiliser. Il n'y a aucun inconvénient à simplement retourner le List.
Nyerguds
1

Je ne me souviens pas qui m'a donné ça, mais ça marche très bien. J'ai testé plusieurs façons de diviser les types énumérables en groupes. L'utilisation serait juste comme ça ...

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

Le code d'extension ressemblerait à ceci ...

#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
la source
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
la source
Vous pouvez légèrement améliorer votre code: déplacez l'expression d'incrémentation i += offSetdans votre forexpression.
JimiLoe
1

Modifié (maintenant il accepte tout non nul stringet tout positif chunkSize) la solution de Konstantin Spirin :

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

Tests:

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

démo

ren
la source
celui-ci conserve le reste de la chaîne (post split) même s'il est plus court que "chunkLenght", merci
Jason Loki Smith
0

Basé sur d'autres réponses d'affiches, ainsi que quelques exemples d'utilisation:

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
la source
0

Utilisation des extensions Buffer de la bibliothèque IX

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