Split a string into chunks of the same length Inspired by this question in chat


  Is there an easy way in .NET to split a string into an array by pulling chunks of the text in. I.e. I have an 18 character string I would like in 3 6-character pieces.  


I thought that ain't be that hard. I would like to get any thoughts and improvements you can think of.  

public static string[] Split(this string value, int desiredLength, bool strict = false)
{

    EnsureValid(value, desiredLength, strict);

    if (value.Length == 0) { return new string[0]; }

    int numberOfItems = value.Length / desiredLength;

    int remaining = (value.Length &gt; numberOfItems * desiredLength) ? 1 : 0;

    IList&lt;string&gt; splitted = new List&lt;string&gt;(numberOfItems + remaining);

    for (int i = 0; i &lt; numberOfItems; i++)
    {
        splitted.Add(value.Substring(i * desiredLength, desiredLength));
    }

    if (remaining != 0)
    {
        splitted.Add(value.Substring(numberOfItems * desiredLength));
    }

    return splitted.ToArray();
}

private static void EnsureValid(string value, int desiredLength, bool strict)
{
    if (value == null) { throw new ArgumentNullException(nameof(value)); }

    if (value.Length == 0 &amp;&amp; desiredLength != 0)
    {
        throw new ArgumentException($"The passed {nameof(value)} may not be empty if the {nameof(desiredLength)} &lt;&gt; 0");
    }

    if (value.Length != 0 &amp;&amp; desiredLength &lt; 1) { throw new ArgumentException($"The value of {nameof(desiredLength)} needs to be &gt; 0"); }

    if (strict &amp;&amp; (value.Length % desiredLength != 0))
    {
        throw new ArgumentException($"The passed {nameof(value)}'s length can't be split by the {nameof(desiredLength)}");
    }
}


I know the tests aren't complete and the naming could be better, like the missing of a test if the passed in value == null, but here they are for review as well  

[TestMethod()]
public void SplitTest()
{
    string input = "123456";
    string[] expected = new string[] { "123", "456" };
    string[] actual = input.Split(3);

    CollectionAssert.AreEqual(expected, actual);
}

[TestMethod()]
public void SplitTestNotEqual()
{
    string input = "12345";
    string[] expected = new string[] { "123", "45" };
    string[] actual = input.Split(3);

    CollectionAssert.AreEqual(expected, actual);
}

[TestMethod()]
[ExpectedException(typeof(ArgumentException))]
public void SplitTestDesiredlengthIsZero()
{
    string input = "12345";
    string[] actual = input.Split(0);
}

[TestMethod()]
public void SplitTestDesiredlengthIsZeroWithEmptyString()
{
    string input = "";
    string[] expected = new string[0];
    string[] actual = input.Split(0);

    CollectionAssert.AreEqual(expected, actual);
}

[TestMethod()][ExpectedException(typeof(ArgumentException))]
public void SplitTestWillThrowBecauseOfStrict()
{
    string input = "12345";
    string[] actual = input.Split(3,true);
}
Algorithm can't be improved (or at least I see no reason to do it, ignoring a minor micro-optimization about List&lt;string&gt; which usage, however, helps to keep code much more readable) and code is pretty clear, however:


You're returning string[] but consumers may not need it. List.ToArray() won't simply return internal array (because its capacity != size). It implies you will make another (relatively expansive) copy. I'd simply return IEnumerable&lt;string&gt;: if consumer needs an array it can call ToArray() LINQ extension method (which will/may check for underlying implementation), it he doesn't need an array then you saved an Array.Copy().
You're partitioning over Char but String is UTF-16 encoded then you may produce broken strings in, at least, three cases:


One character is encoded with more than one code unit. Unicode code point for that character is encoded as two UTF-16 code units, each code unit may end up in two different slices (and both strings will be invalid).
One character is composed by more than one code point. You're dealing with a character made by two separate Unicode code points (for example Han character ).
One character has combining characters or modifiers. This is more common than you may think: for example Unicode combining character like U+0300 COMBINING GRAVE ACCENT used to build à and Unicode modifiers such as U+02BC MODIFIER LETTER APOSTROPHE.

Definition of character for a programming language and for a human being are pretty different, for example in Czech Slovak (thanks svick to correct me about this) dž is a single character however it's made by 2/3 Unicode code points which are in this case also 2/3 UTF-16 code units then "dž".Length &gt; 1. More about this and other cultural issues on this Stack Overflow post.
Ligatures exist. Assuming one ligature is one code point (and also assuming it's encoded as one code unit) then you will treat it as a single glyph however it represents two characters. What to do in this case? In general definition of character may be pretty vague because it has a different meaning according to discipline where this word is used. You can't (probably) handle everything correctly but you should set some constraints and document code behavior.


To address all these issues (also third point if current culture is correctly set but not last one) I'd use:

string[] characters = StringInfo.GetTextElementEnumerator(value).ToArray();

And I'd perform subsequent processing over characters array instead of value. Note that this may lead to strings made of different length but with the same number of characters. For example (when performance are not an issue):

public static IEnumerable&lt;string&gt; Split(this string value, int desiredLength)
{
    var characters = StringInfo.GetTextElementEnumerator(value);
    while (characters.MoveNext())
        yield return String.Concat(Take(characters, desiredLength));
}

private static IEnumerable&lt;string&gt; Take(TextElementEnumerator enumerator, int count)
{
    for (int i = 0; i &lt; count; ++i)
    {
        yield return (string)enumerator.Current;

        if (!enumerator.MoveNext())
            yield break;
    }
}


To do not reinvent the wheel you may just create your own AsEnumerable() extension method to walk through an existing enumerator:

public static IEnumerable&lt;T&gt; AsEnumerable&lt;T&gt;(this IEnumerator enumerator)
{
    while (enumerator.MoveNext())
        yield return (T)enumerator.Current;
}


Your code will be then simplified to:

public static IEnumerable&lt;string&gt; Split(this string value, int desiredLength)
{
    var characters = StringInfo.GetTextElementEnumerator(value);
    do
    {
        yield return String.Concat(characters.AsEnumerable&lt;string&gt;().Take(desiredLength));
    } while (characters.MoveNext());
}


If you don't need string[] return type you may live with IEnumerable&lt;string&gt; and use LINQ ToArray() when/if required (code is slightly simplified for demonstrative purposes, in real world you will need some error checking).

If you're processing US ASCII text and you won't need to partition horse d'&#x153;vre then you can simply ignore these issues.