77

I need to split a number into even parts for example:

32427237 needs to become 324 272 37
103092501 needs to become 103 092 501

How does one go about splitting it and handling odd number situations such as a split resulting in these parts e.g. 123 456 789 0?

RoguePlanetoid
  • 4,516
  • 7
  • 47
  • 64

17 Answers17

139

If you have to do that in many places in your code you can create a fancy extension method:

static class StringExtensions {

  public static IEnumerable<String> SplitInParts(this String s, Int32 partLength) {
    if (s == null)
      throw new ArgumentNullException(nameof(s));
    if (partLength <= 0)
      throw new ArgumentException("Part length has to be positive.", nameof(partLength));

    for (var i = 0; i < s.Length; i += partLength)
      yield return s.Substring(i, Math.Min(partLength, s.Length - i));
  }

}

You can then use it like this:

var parts = "32427237".SplitInParts(3);
Console.WriteLine(String.Join(" ", parts));

The output is 324 272 37 as desired.

When you split the string into parts new strings are allocated even though these substrings already exist in the original string. Normally, you shouldn't be too concerned about these allocations but using modern C# you can avoid this by altering the extension method slightly to use "spans":

public static IEnumerable<ReadOnlyMemory<char>> SplitInParts(this String s, Int32 partLength)
{
    if (s == null)
        throw new ArgumentNullException(nameof(s));
    if (partLength <= 0)
        throw new ArgumentException("Part length has to be positive.", nameof(partLength));

    for (var i = 0; i < s.Length; i += partLength)
        yield return s.AsMemory().Slice(i, Math.Min(partLength, s.Length - i));
}

The return type is changed to public static IEnumerable<ReadOnlyMemory<char>> and the substrings are created by calling Slice on the source which doesn't allocate.

Notice that if you at some point have to convert ReadOnlyMemory<char> to string for use in an API a new string has to be allocated. Fortunately, there exists many .NET Core APIs that uses ReadOnlyMemory<char> in addition to string so the allocation can be avoided.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • 2
    IMHO extension method here is overkill. The "number" in question to be split is `a` specific type of thing, e.g. an order number. If you could restrict the extension method to only apply to Order Numbers, then fine. As it is you can apply it to any entity that happens to be stored in a string, this breaks encapsulation. – Binary Worrier Nov 09 '10 at 12:26
  • Indeed I thought of a method myself that would allow a string to be split into any sized part - will give this one a try! – RoguePlanetoid Nov 09 '10 at 12:30
  • 2
    Just a reminder about extensions methods, from the programming guide: "In general, we recommend that you implement extension methods sparingly and only when you have to." Sorry, I just think extension methods are being overused. – steinar Nov 09 '10 at 15:36
  • 2
    If you prefer to not use an extension method remove the `this` keyword and rename class and method appropriately. I think this particular method is pretty generic (and not specific to say order numbers) and a good candidate for an extension method. Putting the class in a separate namespace enables the developer to decide if he wants to enable the extension methods just as adding `using System.Linq` adds a bunch of extension methods to `IEnumerable`. – Martin Liversage Nov 10 '10 at 14:34
  • Could someone tell me if there is a reason to use String with S in capital letters? Or I can just use string normal? Thanks – Jhonnatan Eduardo Jun 16 '21 at 18:16
  • @JhonnatanEduardo: [What is the difference between String and string in C#?](https://stackoverflow.com/questions/7074/what-is-the-difference-between-string-and-string-in-c) `System.String` is the name of the type and `string` is the C# alias. Personally, I believe that the proper type names of the primitive types work much better when creating new identifiers. It's `Object.ToString()` not `Object.Tostring()`, and `Convert.ToInt64()` not `Convert.Tolong()`. However, the majority of C# developers use the aliases. – Martin Liversage Jun 18 '21 at 11:18
10

You could use a simple for loop to insert blanks at every n-th position:

string input = "12345678";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < input.Length; i++)
{
    if (i % 3 == 0)
        sb.Append(' ');
    sb.Append(input[i]);
}
string formatted = sb.ToString();
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
7

This is half a decade late but:

int n = 3;
string originalString = "32427237";
string splitString = string.Join(string.Empty,originalString.Select((x, i) => i > 0 && i % n == 0 ? string.Format(" {0}", x) : x.ToString()));
Tyress
  • 3,573
  • 2
  • 22
  • 45
7

One very simple way to do this (not the most efficient, but then not orders of magnitude slower than the most efficient).

    public static List<string> GetChunks(string value, int chunkSize)
    {
        List<string> triplets = new List<string>();
        while (value.Length > chunkSize)
        {
            triplets.Add(value.Substring(0, chunkSize));
            value = value.Substring(chunkSize);
        }
        if (value != "")
            triplets.Add(value);
        return triplets;
    }

Heres an alternate

    public static List<string> GetChunkss(string value, int chunkSize)
    {
        List<string> triplets = new List<string>();
        for(int i = 0; i < value.Length; i += chunkSize)
            if(i + chunkSize > value.Length)
                triplets.Add(value.Substring(i));
            else
                triplets.Add(value.Substring(i, chunkSize));

        return triplets;
    }
Binary Worrier
  • 50,774
  • 20
  • 136
  • 184
  • A generic `n` version would be better. – st0le Nov 09 '10 at 12:12
  • Both these methods generate wrong output, for example `String.Join(", ", GetChunks("20000", 3).ToArray())` will generate `200, 00` which is incorrect for currency (should be `20, 000`), splitting should start from end. Also note that the question itself asking to split the number in that way which is also incorrect for currency splitting – AaA Jun 02 '21 at 07:20
7

LINQ rules:

var input = "1234567890";
var partSize = 3;

var output = input.ToCharArray()
    .BufferWithCount(partSize)
    .Select(c => new String(c.ToArray()));

UPDATED:

string input = "1234567890";
double partSize = 3;
int k = 0;
var output = input
    .ToLookup(c => Math.Floor(k++ / partSize))
    .Select(e => new String(e.ToArray()));
kame
  • 20,848
  • 33
  • 104
  • 159
QrystaL
  • 4,886
  • 2
  • 24
  • 28
  • 2
    `System.Linq.EnumerableEx.BufferWithCount` is part of Reactive Extensions for .NET (Rx): http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx. You will have to download and install this for the code to run. – Martin Liversage Nov 09 '10 at 13:51
  • 1
    Thanks I had not seen BufferWithCount before - now I know why - I have Rx installed so may be useful for something else. – RoguePlanetoid Nov 10 '10 at 12:02
5

If you know that the whole string's length is exactly divisible by the part size, then use:

var whole = "32427237!";
var partSize = 3;
var parts = Enumerable.Range(0, whole.Length / partSize)
    .Select(i => whole.Substring(i * partSize, partSize));

But if there's a possibility the whole string may have a fractional chunk at the end, you need to little more sophistication:

var whole = "32427237";
var partSize = 3;
var parts = Enumerable.Range(0, (whole.Length + partSize - 1) / partSize)
    .Select(i => whole.Substring(i * partSize, Math.Min(whole.Length - i * partSize, partSize)));

In these examples, parts will be an IEnumerable, but you can add .ToArray() or .ToList() at the end in case you want a string[] or List<string> value.

Dave Lampert
  • 191
  • 2
  • 3
  • You should add your own answer to an already answered question if you think you're adding value to the accepted answer. It does not look like this is the case. – Marco Scabbiolo Jul 03 '16 at 17:05
  • I don't understand. This was the 10th answer to this question, and only the 3rd which can be done in a single statement (using Linq), and I believe to be the most efficient of those 3. Did I do wrong to add this different and possibly better answer? – Dave Lampert Jul 04 '16 at 20:07
4

The splitting method:

public static IEnumerable<string> SplitInGroups(this string original, int size) {
  var p = 0;
  var l = original.Length;
  while (l - p > size) {
    yield return original.Substring(p, size);
    p += size;
  }
  yield return original.Substring(p);
}

To join back as a string, delimited by spaces:

var joined = String.Join(" ", myNumber.SplitInGroups(3).ToArray());

Edit: I like Martin Liversage solution better :)

Edit 2: Fixed a bug.

Edit 3: Added code to join the string back.

Fábio Batista
  • 25,002
  • 3
  • 56
  • 68
2

I would do something like this, although I'm sure there are other ways. Should perform pretty well.

public static string Format(string number, int batchSize, string separator)
{      
  StringBuilder sb = new StringBuilder();
  for (int i = 0; i <= number.Length / batchSize; i++)
  {
    if (i > 0) sb.Append(separator);
    int currentIndex = i * batchSize;
    sb.Append(number.Substring(currentIndex, 
              Math.Min(batchSize, number.Length - currentIndex)));
  }
  return sb.ToString();
}
steinar
  • 9,383
  • 1
  • 23
  • 37
2

I like this cause its cool, albeit not super efficient:

var n = 3;
var split = "12345678900"
            .Select((c, i) => new { letter = c, group = i / n })
            .GroupBy(l => l.group, l => l.letter)
            .Select(g => string.Join("", g))
            .ToList();
Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
1

Try this:

Regex.Split(num.toString(), "(?<=^(.{8})+)");
esdebon
  • 2,460
  • 5
  • 26
  • 33
  • Interesting answer, but could you explain the regexp? – kotchwane Apr 19 '20 at 11:41
  • I don't think this works. `(?<=_pattern_)` matches at the point after _pattern_ is matched. `.{8}` matches 8 chars. So this is supposed to match 8, 16, 24, 32 etc chars from the start of the string. But I find this pattern outputs the first group repeatedly..? – FSCKur Jan 06 '22 at 18:20
1

A nice implementation using answers from other StackOverflow questions:

"32427237"
    .AsChunks(3)
    .Select(vc => new String(vc))
    .ToCsv(" ");  // "324 272 37"

"103092501"
    .AsChunks(3)
    .Select(vc => new String(vc))
    .ToCsv(" "); // "103 092 501"

AsChunks(): https://stackoverflow.com/a/22452051/538763

ToCsv(): https://stackoverflow.com/a/45891332/538763

crokusek
  • 5,345
  • 3
  • 43
  • 61
1

I went through all the comments and decided to build this extension method:

public static string FormatStringToSplitSequence(this string input, int splitIndex, string splitCharacter)
    {
        if (input == null)
            return string.Empty;

        if (splitIndex <= 0)
            return string.Empty;

        return string.Join(string.Empty, input.Select((x, i) => i > 0 && i % splitIndex == 0 ? string.Format(splitCharacter + "{0}", x) : x.ToString()));
    }

Example:

var text = "24455";
var result = text.FormatStringToSplitSequence(2, ".");

Output: 24.45.5

Zahari Kitanov
  • 510
  • 7
  • 15
0

This might be off topic as I don't know why you wish to format the numbers this way, so please just ignore this post if it's not relevant...

How an integer is shown differs across different cultures. You should do this in a local independent manner so it's easier to localize your changes at a later point.

int.ToString takes different parameters you can use to format for different cultures. The "N" parameter gives you a standard format for culture specific grouping.

steve x string formatting is also a great resource.

simendsjo
  • 4,739
  • 2
  • 25
  • 53
  • They're just numbers I'm not obeying any kind of normal number formatting which is why I mentioned string / number as I'm going to be using them for something that requires this particular formatting. – RoguePlanetoid Nov 09 '10 at 12:28
0

For a dividing a string and returning a list of strings with a certain char number per place, here is my function:

public List<string> SplitStringEveryNth(string input, int chunkSize)
    {
        var output = new List<string>();
        var flag = chunkSize;
        var tempString = string.Empty;
        var lenght = input.Length;
        for (var i = 0; i < lenght; i++)
        {
            if (Int32.Equals(flag, 0))
            {
                output.Add(tempString);
                tempString = string.Empty;
                flag = chunkSize;
            }
            else
            {
                tempString += input[i];
                flag--;
            }

            if ((input.Length - 1) == i && flag != 0)
            {
                tempString += input[i];
                output.Add(tempString);
            }
        }
        return output;
    }
Adil B
  • 14,635
  • 11
  • 60
  • 78
0

You can try something like this using Linq.

var str = "11223344";
var bucket = 2;    
var count = (int)Math.Ceiling((double)str.Length / bucket);
        
Enumerable.Range(0, count)
    .Select(_ => (_ * bucket))
    .Select(_ => str.Substring(_,  Math.Min(bucket, str.Length - _)))
    .ToList()
0

You can also use the StringReader class to reads a block of characters from the input string and advances the character position by count.

StringReader Class Read(Char[], Int32, Int32)

bjhamltn
  • 420
  • 5
  • 6
0

The simplest way to separate thousands with a space, which actually looks bad, but works perfect, would be:

yourString.ToString("#,#").Replace(',', ' ');
ouflak
  • 2,458
  • 10
  • 44
  • 49