views:

441

answers:

9

I'd like to break apart a String by a certain length variable.
It needs to bounds check so as not explode when the last section of string is not as long as or longer than the length. Looking for the most succinct (yet understandable) version.

Example:

string x = "AAABBBCC";
string[] arr = x.SplitByLength(3);
// arr[0] -> "AAA";
// arr[1] -> "BBB";
// arr[2] -> "CC"
+5  A: 

Easy to understand version:

string x = "AAABBBCC";
List<string> a = new List<string>();
for (int i = 0; i < x.Length; i += 3)
{
    if((i + 3) < x.Length)
        a.Add(x.Substring(i, 3));
    else
        a.Add(x.Substring(i));
}

Though preferably the 3 should be a nice const.

ho1
If the int to split by is larger than the string length, this will not yield any result.
JYelton
Good eye JYelton.
Mike Webb
@JYelton: No, it will still enter the loop and end up in the else statement.
ho1
@JYelton: I think you're mistaken. Are you suggesting there's some way to sneak past an `if`/`else` without triggering *either*?
Dan Tao
Nevermind, I was mistaken. I originally thought that the loop would not execute if the split length was longer than the string length, but the check mechanism is built-in.
JYelton
+8  A: 

You need to use a loop:

public static IEnumerable<string> SplitByLength(this string str, int maxLength) {
    for (int index = 0; index < str.Length; index += maxLength) {
        yield return str.Substring(index, Math.Min(maxLength, str.Length - index));
    }
}

Alternative:

public static IEnumerable<string> SplitByLength(this string str, int maxLength) {
    int index = 0;
    while(true) {
        if (index + maxLength >= str.Length) {
            yield return str.Substring(index);
            yield break;
        }
        yield return str.Substring(index, maxLength);
        index += maxLength;
    }
}

2nd alternative: (For those who can't stand while(true))

public static IEnumerable<string> SplitByLength(this string str, int maxLength) {
    int index = 0;
    while(index + maxLength <= str.Length) {
        yield return str.Substring(index, maxLength);
        index += maxLength;
    }

    yield return str.Substring(index);
}
SLaks
Right on! I didn't want to limit folks by asking for the IEnumerable version. This rocks.
tyndall
I was writing the same code when you posted this :P
Chris Shouts
Great minds think alike, I guess! (Or more humbly: great minds and mediocre minds sometimes have the same thought?)
Dan Tao
Why was this downvoted?
SLaks
Do not like the While (true) I think that is bad style, unless listening on a port.
Mike
@Mike: That's why I included the first version.
SLaks
@Mike: I think run-on sentences with no subject in the first clause are bad style. (Oh snap!)
Dan Tao
ugggghhhhh, I can't believe a +1 for oh snap, do you have an alternate account you vote yourself up. (period) Haven't heard that type of talk since grade school
Mike
@Mike: I was just joking, geez. +1 on a comment is meaningless anyway (and no, I did not +1 myself). I just thought it was a little ridiculous that you'd downvote someone because you didn't like his/her *style*. "It answers the question, and it works perfectly, but... I don't like that style, so I'll downvote it."
Dan Tao
@SLaks: I think in your final example, you meant `<=` instead of `>=`.
Dan Tao
@Dan: Fixed; thanks.
SLaks
+2  A: 

It's not particularly succinct, but I might use an extension method like this:

public static IEnumerable<string> SplitByLength(this string s, int length)
{
    for (int i = 0; i < s.Length; i += length)
    {
        if (i + length <= s.Length)
        {
            yield return s.Substring(i, length);
        }
        else
        {
            yield return s.Substring(i);
        }
    }
}

Note that I return an IEnumerable<string>, not an array. If you want to convert the result to an array, use ToArray:

string[] arr = x.SplitByLength(3).ToArray();
Mark Byers
+1 Slightly longer than some others but highly readable.
Andy West
+3  A: 

Here's what I'd do:

public static IEnumerable<string> EnumerateByLength(this string text, int length) {
    int index = 0;
    while (index < text.Length) {
        int charCount = Math.Min(length, text.Length - index);
        yield return text.Substring(index, charCount);
        index += length;
    }
}

This method would provide deferred execution (which doesn't really matter on an immutable class like string, but it's worth noting).

Then if you wanted a method to populate an array for you, you could have:

public static string[] SplitByLength(this string text, int length) {
    return text.EnumerateByLength(length).ToArray();
}

The reason I would go with the name EnumerateByLength rather then SplitByLength for the "core" method is that string.Split returns a string[], so in my mind there's precedence for methods whose names start with Split to return arrays.

That's just me, though.

Dan Tao
I wanted to upvote your use of a separate Inner/Impl method, but then you had to go and spoil it by calling .ToArray() in the outer method.
Joel Coehoorn
@Joel: Ha, but that's what the OP requested! Fine, I'll change it up.
Dan Tao
+1  A: 

Using Batch from MoreLinq, on .Net 4.0:

public static IEnumerable<string> SplitByLength(this string str, int length)
{
    return str.Batch(length, String.Concat);
}

On 3.5 Concat need an array, so we can use Concat with ToArray or, new String:

public static IEnumerable<string> SplitByLength(this string str, int length)
{
    return str.Batch(length, chars => new String(chars.ToArray()));
}

It may be a bit unintuitive to look at a string as a collection of characters, so string manipulation might be proffered.

Kobi
This will be much slower.
SLaks
That's probably true.
Kobi
+1: this wins for succinctness
RedFilter
A: 
    private string[] SplitByLength(string s, int d)
    {
        List<string> stringList = new List<string>();
        if (s.Length <= d) stringList.Add(s);
        else
        {
            int x = 0;
            for (; (x + d) < s.Length; x += d)
            {
                stringList.Add(s.Substring(x, d));
            }
            stringList.Add(s.Substring(x));
        }
        return stringList.ToArray();
    }
JYelton
A: 
    private void button2_Click(object sender, EventArgs e)
    {
        string s = "AAABBBCCC";
        string[] a = SplitByLenght(s,3);
    }

    private string[] SplitByLenght(string s, int split)
    {
        //Like using List because I can just add to it 
        List<string> list = new List<string>();

                    // Integer Division
        int TimesThroughTheLoop = s.Length/split;


        for (int i = 0; i < TimesThroughTheLoop; i++)
        {
            list.Add(s.Substring(i * split, split));

        }

        // Pickup the end of the string
        if (TimesThroughTheLoop * split != s.Length)
        {
            list.Add(s.Substring(TimesThroughTheLoop * split));
        }

        return list.ToArray();
    }
Mike
+1  A: 
ULysses
+1  A: 

Yet another slight variant (classic but simple and pragmatic):

class Program
{
    static void Main(string[] args) {
        string msg = "AAABBBCC";

        string[] test = msg.SplitByLength(3);            
    }
}

public static class SplitStringByLength
{
    public static string[] SplitByLength(this string inputString, int segmentSize) {
        List<string> segments = new List<string>();

        int wholeSegmentCount = inputString.Length / segmentSize;

        int i;
        for (i = 0; i < wholeSegmentCount; i++) {
            segments.Add(inputString.Substring(i * segmentSize, segmentSize));
        }

        if (inputString.Length % segmentSize != 0) {
            segments.Add(inputString.Substring(i * segmentSize, inputString.Length - i * segmentSize));
        }

        return segments.ToArray();
    }
}
IUnknown