277

Suppose I had a string:

string str = "1111222233334444"; 

How can I break this string into chunks of some size?

e.g., breaking this into sizes of 4 would return strings:

"1111"
"2222"
"3333"
"4444"
dove
  • 20,469
  • 14
  • 82
  • 108
  • 19
    Why use LINQ or regexes when C#'s standard string manipulation functions can do this with less effort and more speed? Also, what happens if the string is an odd number of characters in length? – Ian Kemp Sep 20 '09 at 10:58
  • I'd like to avoid loops etc. I think it can be done in one line using LINQ/regex. –  Sep 20 '09 at 11:01
  • 7
    "I'd like to avoid loops" - why? – Mitch Wheat Sep 20 '09 at 11:02
  • 1
    This scenario is part of much bigger problem (the string is also much bigger) and IMO using loops, chopping strings etc. is not very elegant. StringLength % 4 will always be 0. Do you think using loops is the only solution? I do not want to compromise on performance.So what is the best solution? TIA. –  Sep 20 '09 at 11:10
  • 12
    Using a simple loop is definitely what gives the best performance. – Guffa Sep 20 '09 at 11:23
  • you sure that Linq would be more performant than a for loop? I wonder what the difference in underlining CLR language would end up being. If this really is a performance bottleneck, which would seem strange, then you'd want to line up some stress testing so you'll know which is faster for the load you expect. – dove Sep 20 '09 at 11:25
  • 4
    http://www.nichesoftware.co.nz/blog/200909/linq-vs-loop-performance is a pretty good comparison between linq and actual looping over an array. I doubt you'll ever find linq faster than manually written code because it keeps calling run-time delegates that are hard to optimize away. Linq is more fun though :) – Blindy Sep 20 '09 at 11:26
  • 2
    Whether you're using LINQ or regexes, the loop is still there. – Anton Tykhyy Sep 21 '09 at 06:54
  • 1
    Dear God, no, not an arraylist. ArrayLists are EVIL – Joel Coehoorn Mar 09 '10 at 23:22

39 Answers39

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

Please note that additional code might be required to gracefully handle edge cases (null or empty input string, chunkSize == 0, input string length not divisible by chunkSize, etc.). The original question doesn't specify any requirements for these edge cases and in real life the requirements might vary so they are out of scope of this answer.

Konstantin Spirin
  • 20,609
  • 15
  • 72
  • 90
  • @Konstantin much more elegant but will it deal with case of string not being a factor of 4? e.g. string str = "111122223333444455"; – dove Sep 20 '09 at 11:21
  • Will check to see which solution performs best. Konstantin's is easy to follow though IMO. –  Sep 20 '09 at 11:25
  • @dove: red_Amazon in comments to his question said that str.Length % 4 == 0 – Konstantin Spirin Sep 20 '09 at 11:38
  • @Konstantin ah, didn't see that comment. could make my for loop a one-liner. – dove Sep 20 '09 at 11:44
  • 3
    @Harry Good catch! This can be remedied with a drop-in ternary expression on the count parameter of substring. Something like: `(i * chunkSize + chunkSize <= str.Length) ? chunkSize : str.Length - i * chunkSize`. An additional problem is that this function doesn't account for str being null. This can be fixed by wrapping the whole return statement in another ternary expression: `(str != null) ? ... : Enumerable.Empty();`. – Drew Spickes Jul 04 '12 at 16:49
  • 10
    This was close, but unlike the previous 30 upvoters, I had to change the loop count limit of Range from `str.Length / chunkSize` to `double length = str.Length; double size = chunkSize; int count = (int)Math.Ceiling(length/size); return Enumerable.Range(0, count)...` – gap Aug 25 '12 at 01:25
  • I'd like to keep the code short just to illustrate the idea. I'm sure people will be able to add error handling etc. themselves. – Konstantin Spirin Dec 31 '12 at 03:29
  • 8
    @KonstantinSpirin I agree if the code worked. It only handles the case where a string is a multiple of chunkSize, the rest of the string is lost. Please ammend. Also keep in mind that LINQ and it's magic is not as easy to understand to someone that just wants to look at a solution to this problem. A person must now understand what the Enumerable.Range() and .Select() functions do. I will not argue that you should have an understanding of that to write C#/.NET code as these functions have been in the BCL for many years now. – CodeMonkeyKing Jan 02 '13 at 22:33
  • 6
    Topic starter said in comments that `StringLength % 4 will always be 0`. If `Linq` is not as easy to understand then there are other answers that use loops and yields. Anyone is free to choose the solution she likes best. You can post your code as an answer and people will happily vote for it. – Konstantin Spirin Jan 03 '13 at 01:42
  • @gap, good stuff. Konstantin, why not just modify the answer. I mean, it literally is just to add the call to Math.Ceiling... #gosh. – Irwin May 02 '13 at 06:09
  • I posted an updated version of this method that works if "str.Length % chunkSize" is not zero – Chris Pietschmann May 09 '14 at 16:10
  • @ChrisPietschmann your solution is completely different from my original intention of writing a simple one-liner. I'd prefer if you posted your proposal as a separate answer. – Konstantin Spirin May 12 '14 at 05:02
  • 1
    I like it - as a coding exercise. For an actual program I'd claim a straightforward for loop is much preferable. Code should be obvious, ideally both in intent and implementation details. This has a reasonable signature and so the intent is reasonably clear, but the implementation is obscure. And slow. And wasteful of memory. A simple for (i=0; i < n; i += chunkSize) loop would be far superior in all aspects except amount of code, but six lines of simple code is still better than one or two WTF lines of code. Still, like I said, I do think it's an elegant solution. – The Dag Dec 01 '15 at 06:51
  • maybe an extension method for string? – ComeIn Jul 19 '16 at 12:39
  • @ComeIn only if you often have to do this (>10 times in your project?). Otherwise it'll just pollute IntelliSense with not much added value. – Konstantin Spirin Jul 21 '16 at 03:53
  • 9
    Enumerable.Range(0, (str.Length + chunkSize - 1) / chunkSize) .Select(i => str.Substring(i * chunkSize, Math.Min(str.Length - i * chunkSize, chunkSize))) – Sten Petrov Nov 09 '17 at 19:11
  • worked for me, i have given a if condition like `if (str.Length < chunkSize) chunkSize = str.Length;` , otherwise if the chunk size is less than string length the output was null. So total code is `public IEnumerable Split(string str, int chunkSize) { if (str.Length < chunkSize) chunkSize = str.Length; return Enumerable.Range(0, str.Length / chunkSize) .Select(i => str.Substring(i * chunkSize, chunkSize)); }` – Ashi Feb 16 '20 at 07:43
  • good thought, but I would need it to break on whole words also, not split a word in half. – Chizl Aug 25 '20 at 20:53
  • `static IEnumerable Split(string str, int chunkSize) { return Enumerable.Range(0, (str.Length + chunkSize -1) / chunkSize) .Select(i => str.Substring(i * chunkSize, (i * chunkSize + chunkSize <= str.Length) ? chunkSize : str.Length - i * chunkSize)); }` – cuiliang May 18 '21 at 03:19
177

In a combination of dove+Konstatin's answers...

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

This will work for all strings that can be split into a whole number of chunks, and will throw an exception otherwise.

If you want to support strings of any length you could use the following code:

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

However, the the OP explicitly stated he does not need this; it's somewhat longer and harder to read, slightly slower. In the spirit of KISS and YAGNI, I'd go with the first option: it's probably the most efficient implementation possible, and it's very short, readable, and, importantly, throws an exception for nonconforming input.

Eamon Nerbonne
  • 47,023
  • 20
  • 101
  • 166
  • 4
    +1 worth a nod. kinda hits nail upon head. he's looking for succinct sytnax and you're also giving the (probably) better performance. – dove Sep 20 '09 at 20:28
  • 9
    And if you make it "static ... Chunk(this string str, int chunkSize) {" you even have one more "new" C#-Feature into it. Then you can write "1111222233334444".Chunk(4). – MartinStettner Sep 21 '09 at 07:25
  • 1
    @MartinStettner: That's certainly a decent idea if this is a common operation. – Eamon Nerbonne Sep 21 '09 at 08:05
  • You should only include the latter code. The former requires that you understand and test for the string being a multiple of chunk size prior to using, or understand that it will not return the remainder of the string. – CodeMonkeyKing Jan 02 '13 at 22:37
  • The OP's question doesn't make it clear whether he needs that functionality. The first solution is simpler, faster and reliably fails with an exception iff the string cannot be evenly split into the specified chunk size. I agree that returning "wrong" results would be bad, but that not what it does - it just throws an exception, so I'd be OK with using it if you can live with the limitation. – Eamon Nerbonne Jan 03 '13 at 10:08
  • @CodeMonkeyKing: update, the OP explicitly stated he doesn't need non-whole chunks. – Eamon Nerbonne Feb 19 '13 at 14:11
  • @EamonNerbonne I understand your stating that is more clear, however, its a bug beartrap waiting to be sprung on the unsuspecting. Happy that you've included the other solution :) – CodeMonkeyKing Feb 19 '13 at 19:16
  • To each his own. It just depends what you want - the other variant can generate chunks that are smaller than the chunk size - which might cause bugs of its own, especially if you _initially_ always had whole chunk sizes (like the OP). All else being equal, I like strong post-conditions, so if you have whole chunk sizes, I'd prefer that to be guarranteed and invalid input to be detected rather than potentially buggy output (too short chunks) to be generated (i.e. fail-fast). Of course, if you expect non-whole size chunks it's a different story. – Eamon Nerbonne Feb 20 '13 at 11:14
  • I've renamed the methods - would that mitigate the potential confusion? – Eamon Nerbonne Feb 20 '13 at 11:17
  • @EamonNerbonne The first approach is good as dove way. As Konstantin way left remained characters without exception and Dove way select all items without care about their length your second way throw an exception and i didn't understand meaning of 'If you want to support strings of any length ' because of error thrown. I chose the first way. +1 anyway and appreciate if you describe more about second. I read your comment about prefer exception on buggy(too short chunk) output. I think it is better to write code can hint and get output rather exception . – QMaster Oct 20 '14 at 13:00
  • @QMaster: It's generally a good idea to [fail fast](http://en.wikipedia.org/wiki/Fail-fast) in cases like these because this is code that is not immediately user-facing, and therefore unexpected behavior will generally cause hard-to-debug errors down the line. So it's best to detect and halt on an unsupported edge-case (such as a string that doesn't divide cleanly into chunks) since that way the consumer can quickly and cheaply decide what to do, rather that after a long and expensive debugging session. – Eamon Nerbonne Oct 20 '14 at 13:33
  • @EamonNerbonne you right in special usage but in public purpose i think the programmer need a function can handle various situation. additionally i had mistake and wrote way one and two contrariwise. The first way get exception and second get chunk in any sizes as you said. I'm sorry and thanks to you'r explanation. – QMaster Oct 20 '14 at 13:41
  • Need to handle the case where the last chunk size is less than given size, otherwise will throw an error (https://www.codegrepper.com/code-examples/csharp/divide+string+in+chunks+c%23) – Arnold Vakaria Jun 11 '21 at 16:59
63

Why not loops? Here's something that would do it quite well:

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

I don't know how you'd deal with case where the string is not factor of 4, but not saying you're idea is not possible, just wondering the motivation for it if a simple for loop does it very well? Obviously the above could be cleaned and even put in as an extension method.

Or as mentioned in comments, you know it's /4 then

str = "1111222233334444";
for (int i = 0; i < stringLength; i += chunkSize) 
  {Console.WriteLine(str.Substring(i, chunkSize));} 
dove
  • 20,469
  • 14
  • 82
  • 108
  • 1
    You can pull `int chunkSize = 4` outside of the loop. It will only be modified on the final pass. – John Feminella Sep 20 '09 at 11:15
  • +1 for a simple and effective solution - this is how I would have done it, although I would've used `i += chunkSize` instead. – Ian Kemp Sep 20 '09 at 13:19
  • Probably a minor quibble, but you should probably also pull the `str.Length` out of the loop and into a local variable. The C# optimizer *may* be able to inline array length, but I think the code as written will do a method call on every loop, which is not efficient, since the size of `str` never changes. – Daniel Pryden Sep 21 '09 at 00:47
  • @Daniel, put your idea in there. though I'm not sure that this wouldn't be calculated at runtime, but that's another question ;) – dove Sep 21 '09 at 06:46
  • @Daniel coming back to this, pretty sure the that this optimisation would be extracted by the compiler. – dove Oct 29 '09 at 00:09
  • THIS WORKED well @dove – anandd360 Oct 09 '20 at 02:01
49

This is based on @dove solution but implemented as an extension method.

Benefits:

  • Extension method
  • Covers corner cases
  • Splits string with any chars: numbers, letters, other symbols

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

Unit tests removed for brevity (see previous revision)

Community
  • 1
  • 1
oleksii
  • 35,458
  • 16
  • 93
  • 163
  • Interesting solution, but for the sake of avoiding checks beyond null on the input, it seems more logical to allow an empty string to just return a single empty-string part: `if (str.Length == 0) yield return String.Empty; else { for... }` – Nyerguds Nov 10 '16 at 09:47
  • I mean, that's how the normal String.Split handles empty strings; it returns one empty-string entry. – Nyerguds Nov 10 '16 at 10:00
  • Side note: your usage example is wrong. You can't just cast `IEnumerable` to array, especially not implicitly. – Nyerguds Nov 10 '16 at 10:12
  • I personally like to call that method `Chunkify`.. It's not mine, I don't remember where I have seen that name, but it felt very nice to me – quetzalcoatl Feb 14 '18 at 11:52
44

Using regular expressions and Linq:

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

I find this to be more readable, but it's just a personal opinion. It can also be a one-liner : ).

João Silva
  • 89,303
  • 29
  • 152
  • 158
  • 7
    Change the pattern to @"\d{1,4}" and it works for any string length. :) – Guffa Sep 20 '09 at 18:08
  • 3
    +1 Though this is slower than the other solutions, it's definitely very readable. It's not clear to me whether the OP requires digits or arbitrary characters; it'd probably be wise to replace the `\d` character class with a `.` and to specify `RegexOptions.Singleline`. – Eamon Nerbonne Sep 23 '09 at 14:27
  • 4
    or just Regex.Matches(s, @"\d{1,4}").Select(m => m.Value).ToList(); I never got the point of this alternative syntax that serves only to obfuscate that we're using extension methods. – The Dag Dec 01 '15 at 06:56
37

Starting with .NET 6, we can also use the Chunk method:

var result = str
    .Chunk(4)
    .Select(x => new string(x))
    .ToList();
Magnetron
  • 7,495
  • 1
  • 25
  • 41
26

How's this for a one-liner?

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

With this regex it doesn't matter if the last chunk is less than four characters, because it only ever looks at the characters behind it.

I'm sure this isn't the most efficient solution, but I just had to toss it out there.

AStopher
  • 4,207
  • 11
  • 50
  • 75
Alan Moore
  • 73,866
  • 12
  • 100
  • 156
  • in case of `target.Lenght % ChunckSize == 0` that returns a additional empty row e.g `List result = new List(Regex.Split("fooo", @"(?<=\G.{4})", RegexOptions.Singleline));` – fubo Sep 04 '15 at 07:28
8

It's not pretty and it's not fast, but it works, it's a one-liner and it's 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
  • 687,336
  • 108
  • 737
  • 1,005
8

I recently had to write something that accomplishes this at work, so I thought I would post my solution to this problem. As an added bonus, the functionality of this solution provides a way to split the string in the opposite direction and it does correctly handle unicode characters as previously mentioned by Marvin Pinto above. So, here it is:

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

Also, here is an image link to the results of running this code: https://i.stack.imgur.com/Fh3Ym.png

Yi Jiang
  • 49,435
  • 16
  • 136
  • 136
Michael Nelson
  • 131
  • 2
  • 6
  • 1
    I noticed a problem with this code. You have `{str.ToString()}` at the end of your first IF statement. Are you sure you didn't mean `str.String`? I had a problem with the code above, made that change, and everything worked. – gunr2171 Jun 19 '12 at 20:53
  • @gunr2171 It seems that if str == null, that line will also give a NullReferenceException. – John Zabroski Apr 09 '20 at 16:32
6

This should be much faster and more efficient than using LINQ or other approaches used here.

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
  • 129,526
  • 32
  • 251
  • 272
  • This *looks* like it does early checking, but it doesn't. You don't get an error until you begin enumerating the enumerable. You need to break up your function into two parts, where the first part does the argument checking, and then returns the results of the second, **private** part that does the enumeration. – ErikE Oct 27 '15 at 01:32
5

You can use morelinq by Jon Skeet. Use Batch like:

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

This will return 4 chunks for the string "1111222233334444". If the string length is less than or equal to the chunk size Batch will return the string as the only element of IEnumerable<string>

For output:

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

and it will give:

1111
2222
3333
4444
Habib
  • 219,104
  • 29
  • 407
  • 436
5

Personally I prefer my solution :-)

It handles:

  • String lengths that are a multiple of the chunk size.
  • String lengths that are NOT a multiple of the chunk size.
  • String lengths that are smaller than the chunk size.
  • NULL and empty strings (throws an exception).
  • Chunk sizes smaller than 1 (throws an exception).

It is implemented as a extension method, and it calculates the number of chunks is going to generate beforehand. It checks the last chunk because in case the text length is not a multiple it needs to be shorter. Clean, short, easy to understand... and works!

    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
  • 51
  • 1
  • 1
5

Simple and short:

// 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
  • 34,064
  • 78
  • 298
  • 470
5

I know question is years old, but here is a Rx implementation. It handles the length % chunkSize != 0 problem out of the box:

   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
  • 2,796
  • 1
  • 13
  • 29
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
  • 41
  • 1
4

Best , Easiest and Generic Answer :).

    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
  • 590
  • 1
  • 18
  • 35
  • Calculating the length in the last line is redundant, simply use the `Substring` overload that doesn't require the length parameter `originalString.Substring(i)`. Also you can use `>` instead of `>=` in your check. – Racil Hilan Aug 11 '18 at 06:03
  • @RacilHilan I will test the code changes with your suggestion and update the answer. I am glad somebody with such good reputation got time to review my code. :) Thanks, Sandeep – Sandeep Kushwah Aug 12 '18 at 19:55
  • Straight forward answer. Simple and easy to understand. – Joseph Nov 14 '21 at 19:01
4
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;
}

It correctly handles input string length not divisible by chunkSize.

Please note that additional code might be required to gracefully handle edge cases (null or empty input string, chunkSize == 0).

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

and another approach:

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
  • 3,876
  • 7
  • 42
  • 72
3

Six years later o_O

Just because

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

or

    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 all edge cases are handled.

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
  • 581
  • 5
  • 16
  • What about the "input is an empty string" edge case? I'd expect that, just like with Split, to return an IEnumerable with a single empty-string containing entry. – Nyerguds Nov 10 '16 at 09:58
3
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
  • 377
  • 1
  • 3
  • 15
  • 1
    I like this answer a lot, but maybe you should use if ((i+1) * chunk >= input.Length) instead of try/catch as exceptions are for exceptional cases. – nelsontruran Apr 02 '18 at 22:23
3

I took this to another level. Chucking is an easy one liner, but in my case I needed whole words as well. Figured I would post it, just in case someone else needs something similar.

static IEnumerable<string> Split(string orgString, int chunkSize, bool wholeWords = true)
{
    if (wholeWords)
    {
        List<string> result = new List<string>();
        StringBuilder sb = new StringBuilder();

        if (orgString.Length > chunkSize)
        {
            string[] newSplit = orgString.Split(' ');
            foreach (string str in newSplit)
            {
                if (sb.Length != 0)
                    sb.Append(" ");

                if (sb.Length + str.Length > chunkSize)
                {
                    result.Add(sb.ToString());
                    sb.Clear();
                }

                sb.Append(str);
            }

            result.Add(sb.ToString());
        }
        else
            result.Add(orgString);

        return result;
    }
    else
        return new List<string>(Regex.Split(orgString, @"(?<=\G.{" + chunkSize + "})", RegexOptions.Singleline));
}

Results based on below comment:

string msg = "336699AABBCCDDEEFF";
foreach (string newMsg in Split(msg, 2, false))
{
    Console.WriteLine($">>{newMsg}<<");
}

Console.ReadKey();

Results:

>>33<<
>>66<<
>>99<<
>>AA<<
>>BB<<
>>CC<<
>>DD<<
>>EE<<
>>FF<<
>><<

Another way to pull it:

List<string> splitData = (List<string>)Split(msg, 2, false);

for (int i = 0; i < splitData.Count - 1; i++)
{
    Console.WriteLine($">>{splitData[i]}<<");
}

Console.ReadKey();

New Results:

>>33<<
>>66<<
>>99<<
>>AA<<
>>BB<<
>>CC<<
>>DD<<
>>EE<<
>>FF<<
Chizl
  • 2,004
  • 17
  • 32
  • One thing I noticed with your expression for the else-branch (not whole words) - when I use that expression with a chunk size of 2 for a string "336699AABBCCDDEEFF", the return is of size 10, where the last string (item 9) is null. That is, the return should be 9 strings of length 2, not 10 strings. What am I missing? – GTAE86 Dec 10 '20 at 17:09
  • 1
    Thats because of the new string added to the end of each line, (RegexOptions.Singleline). – Chizl Dec 11 '20 at 20:02
  • 1
    Added demo code to show it's results above. – Chizl Dec 11 '20 at 20:12
2

I think this is an straight forward answer:

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

And it covers edge cases.

Milad
  • 539
  • 8
  • 21
2
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;
}

demo

ren
  • 3,843
  • 9
  • 50
  • 95
2

Here's my 2 cents:

  IEnumerable<string> Split(string str, int chunkSize)
  {
     while (!string.IsNullOrWhiteSpace(str))
     {
        var chunk = str.Take(chunkSize).ToArray();
        str = str.Substring(chunk.Length);
        yield return new string(chunk);

     }

  }//Split
ShanieMoonlight
  • 1,623
  • 3
  • 17
  • 28
2

Changed slightly to return parts whose size not equal to 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;
    }
dove
  • 20,469
  • 14
  • 82
  • 108
  • Not sure I see the use of back-casting that `List` to `IEnumerable`; all that does is hiding List-specific functions you might want to use. There is no downside whatsoever to just returning the `List`. – Nyerguds Nov 10 '16 at 09:52
2

An important tip if the string that is being chunked needs to support all Unicode characters.

If the string is to support international characters like , then split up the string using the System.Globalization.StringInfo class. Using StringInfo, you can split up the string based on number of text elements.

string internationalString = '';

The above string has a Length of 2, because the String.Length property returns the number of Char objects in this instance, not the number of Unicode characters.

Marvin Pinto
  • 30,138
  • 7
  • 37
  • 54
Seth
  • 31
  • 1
1

I've slightly build up on João's solution. What I've done differently is in my method you can actually specify whether you want to return the array with remaining characters or whether you want to truncate them if the end characters do not match your required chunk length, I think it's pretty flexible and the code is fairly straight forward:

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
  • 16,829
  • 14
  • 80
  • 120
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
  • 8,848
  • 7
  • 70
  • 78
1

I can't remember who gave me this, but it works great. I speed tested a number of ways to break Enumerable types into groups. The usage would just be like this...

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

The extention code would look like this...

#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
  • 116
  • 4
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;
    }
}
Vikas Bansal
  • 10,662
  • 14
  • 58
  • 100
  • You can slightly improve your code: shift the increment expression `i += offSet` into your `for` expression. – JimiLoe Jul 05 '15 at 18:07
1

Modified (now it accepts any non null string and any positive chunkSize) Konstantin Spirin's solution:

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
  • 180,369
  • 20
  • 160
  • 215
0

Based around other posters answers, along with some samples of useage:

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
  • 61,249
  • 84
  • 283
  • 456
0

Using the Buffer extensions from the IX library

    static IEnumerable<string> Split( this string str, int chunkSize )
    {
        return str.Buffer(chunkSize).Select(l => String.Concat(l));
    }
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
0

This can be done in this way too

        string actualString = "1111222233334444";
        var listResult = new List<string>();
        int groupingLength = actualString.Length % 4;
        if (groupingLength > 0)
            listResult.Add(actualString.Substring(0, groupingLength));
        for (int i = groupingLength; i < actualString.Length; i += 4)
        {
            listResult.Add(actualString.Substring(i, 4));
        }

        foreach(var res in listResult)
        {
            Console.WriteLine(res);
        }
        Console.Read();
Chandan Kumar
  • 4,570
  • 4
  • 42
  • 62
0

If necessary to split by few different length: For example you have date and time in specify format stringstrangeStr = "07092016090532"; 07092016090532 (Date:07.09.2016 Time: 09:05:32)

public static IEnumerable<string> SplitBy(this string str, int[] chunkLength)
    {
        if (String.IsNullOrEmpty(str)) throw new ArgumentException();
        int i = 0;
        for (int j = 0; j < chunkLength.Length; j++)
        {
            if (chunkLength[j] < 1) throw new ArgumentException();
            if (chunkLength[j] + i > str.Length)
            {
                chunkLength[j] = str.Length - i;
            }
            yield return str.Substring(i, chunkLength[j]);
            i += chunkLength[j];
        }
    }

using:

string[] dt = strangeStr.SplitBy(new int[] { 2, 2, 4, 2, 2, 2, 2 }).ToArray();
Jarek
  • 307
  • 4
  • 12
0
        List<string> chunks = new List<string>();
        var longString = new string('s', 3000);
        var chunkLength = 1273;
        var ratio = (double)longString.Length / chunkLength;
        var countOfChunks = Convert.ToByte(Math.Round(ratio, MidpointRounding.ToPositiveInfinity));

        for (byte i = 0; i < countOfChunks; i++)
        {
            var remainingLength = longString.Length - chunkLength * i;
            if (chunkLength > remainingLength)
                chunks.Add(longString.Substring(i * chunkLength, remainingLength));
            else
                chunks.Add(longString.Substring(i * chunkLength, chunkLength));
        }
Andrew Zaitsev
  • 323
  • 3
  • 4
0
        public static List<string> DevideByStringLength(string text, int chunkSize)
    {
        double a = (double)text.Length / chunkSize;
        var numberOfChunks = Math.Ceiling(a);

        Console.WriteLine($"{text.Length} | {numberOfChunks}");

        List<string> chunkList = new List<string>();
        for (int i = 0; i < numberOfChunks; i++)
        {
            string subString = string.Empty;
            if (i == (numberOfChunks - 1))
            {
                subString = text.Substring(chunkSize * i, text.Length - chunkSize * i);
                chunkList.Add(subString);
                continue;
            }
            subString = text.Substring(chunkSize * i, chunkSize);
            chunkList.Add(subString);
        }
        return chunkList;
    }
-1
for (int i = 0; i <= Convert.ToInt32(Math.Truncate(Convert.ToDecimal(_string.Length / _chunkSize))); i++)
{
    var _currentChunk = _string.Substring(i * _chunkSize, Math.Min(_chunkSize, _string.Length - i * _chunkSize));
    //do something with current chunk 
}
  • 1
    Please don't post code-only answers. The main audience, future readers, will be grateful to see explained *why* this answers the question instead of having to infer it from the code. Also, since this is an old, well answered question, please explain how it complements **all** other answers. – Gert Arnold Jan 11 '23 at 18:37
-2

Try this:

    public static string[] Split(string str, int chunkSize)
    {
        return Enumerable.Range(0, str.Length / chunkSize)
            .Select(i => str.Substring(i * chunkSize, chunkSize)).ToArray();
    }