Add space between colon in a string

Viewed 1347

Expected User Inputs:

Apple : 100

Apple:100

Apple: 100

Apple :100

Apple   :   100

Apple  :100

Apple:  100

Expected Result:

Apple : 100

I need only 1 space between the colon :

Code:

 string input = "Apple:100";

 if (input.Contains(":"))
 {
    string firstPart = input.Split(':').First();

    string lastPart = input.Split(':').Last();

    input = firstPart.Trim() + " : " + lastPart.Trim();
 }

Above code is working using Linq, but is there shorter or efficient code with performance in mind ?

Any help would be appreciated.

6 Answers

You can use this one liner:

input = string.Join(" : ", input.Split(':').Select(x => x.Trim()));

This is more efficient than splitting two times. However, if you want a more efficient solution you can use StringBuilder:

var builder = new StringBuilder(input.Length);
char? previousChar = null;
foreach (var ch in input)
{
    // don't add multiple whitespace
    if (ch == ' ' && previousChar == ch)
    {
        continue;
    }

     // add space before colon
     if (ch == ':' && previousChar != ' ')
     {
         builder.Append(' ');
     }

     // add space after colon
     if (previousChar == ':' && ch != ' ')
     {
          builder.Append(' ');
     }


    builder.Append(ch);
    previousChar = ch;
}

Edit: As mentioned in the comments by @Jimi seems like the foreach version is slower than LINQ.

You can try this old-fashioned string manipulation:

int colonPos = input.IndexOf(':');
if (colonPos>-1)
{
    string s1 = input.Substring(0,colonPos).Trim();
    string s2 = input.Substring(colonPos+1, input.Length-colonPos-1).Trim();
    string result = $"{s1} : {s2}";
}

Whether it is more performant, I don't know, Race Your Horses.

Edit: This one is even faster and simpler (completed 100000 iterations of the training set in 0.132 seconds):

string result = input.Replace(" ","").Replace(":", " : ");

Since you asked, here's a comparison between similar methods:

The two methods that Selman Genç presented and two other that differ in some details.

string.Join("[Separator]", string.Split())

This method glues together, using a Separator, the array of strings generated by .Split(char[]), which creates a string for each substring of the original one. The substrings are generated using the characters specified in the char array parameter as separator identifiers.
The StringSplitOptions.RemoveEmptyEntries parameter indicates to only return non-empty substrings.

string output = string.Join(" : ", input.Split(new[] { ":", " " }, StringSplitOptions.RemoveEmptyEntries));


StringBuilder.Append(SubString(IndexOf([Separator])))
(non optimized: TrimStart() and TrimEnd() are used here)

StringBuilder sb = new StringBuilder();
const string Separator = " : ";
int SplitPosition = input.IndexOf(':');
sb.Append(input.Substring(0, SplitPosition).TrimEnd());
sb.Append(Separator);
sb.Append(input.Substring(SplitPosition + 1).TrimStart());

Here are the result of the test in 5 different conditions:
(Where I remind myself to always test the .exe and not the IDE)

Debug Mode 32Bit - Visual Studio IDE
Debug Mode 64Bit - Visual Studio IDE
Release Mode 64Bit - Visual Studio IDE
Debug Mode 64Bit - Executable File
Release Mode 64Bit - Executable File

Test Machine: I5 4690K on Asus Z-97K MB
Visual Studio 15.8.2
C# 7.3

==========================================
     1 Million iterations x 10 times           
          Code Optimization: On
==========================================
-------------------------------------------
                Debug 32Bit                
-------------------------------------------

Selman Genç Join(.Split().Select()): 244 ~ 247 ms
Selman Genç StringBuilder:           299 ~ 303 ms 
Counter Test Join(.Split()):         187 ~ 226 ms
Counter Test StringBuilder:           90 ~  95 ms

-------------------------------------------
                Debug 64Bit                
-------------------------------------------

Selman Genç Join(.Split().Select()): 242 ~ 259 ms
Selman Genç StringBuilder:           292 ~ 302 ms 
Counter Test Join(.Split()):         183 ~ 227 ms
Counter Test StringBuilder:           89 ~  93 ms

-------------------------------------------
               Release 64Bit                
-------------------------------------------

Selman Genç Join(.Split().Select()): 235 ~ 253 ms
Selman Genç StringBuilder:           288 ~ 302 ms 
Counter Test Join(.Split()):         176 ~ 224 ms
Counter Test StringBuilder:           86 ~  94 ms

-------------------------------------------
          Debug 64Bit - .exe File               
-------------------------------------------

Selman Genç Join(.Split().Select()): 232 ~ 234 ms
Selman Genç StringBuilder:            45 ~  47 ms 
Counter Test Join(.Split()):         197 ~ 217 ms
Counter Test StringBuilder:           77 ~  78 ms

-------------------------------------------
         Release 64Bit - .exe File               
-------------------------------------------

Selman Genç Join(.Split().Select()): 226 ~ 228 ms
Selman Genç StringBuilder:            45 ~  48 ms 
Counter Test Join(.Split()):         190 ~ 208 ms
Counter Test StringBuilder:           73 ~  77 ms

Sample test:

string input = "Apple   :   100";

Stopwatch sw = new Stopwatch();
sw.Start();

// Counter test StringBuilder    
StringBuilder sb1 = new StringBuilder();
const string Separator = " : ";
for (int i = 0; i < 1000000; i++)
{
    int SplitPosition = input.IndexOf(':');
    sb1.Append(input.Substring(0, SplitPosition).TrimEnd());
    sb1.Append(Separator);
    sb1.Append(input.Substring(SplitPosition + 1).TrimStart());
    sb1.Clear();
}
sw.Stop();
//File write
sw.Reset();
sw.Start();

// Selman Genç StringBuilder    
StringBuilder sb2 = new StringBuilder();
for (int i = 0; i < 1000000; i++)
{
    char? previousChar = null;
    foreach (var ch in input)
    {
        if (ch == ' ' && previousChar == ch) { continue; }
        if (ch == ':' && previousChar != ' ') { sb2.Append(' '); }
        if (previousChar == ':' && ch != ' ') { sb2.Append(' '); }

        sb2.Append(ch);
        previousChar = ch;
    }
    sb2.Clear();
}

sw.Stop();
//File write
sw.Reset();
sw.Start();

for (int i = 0; i < 1000000; i++)
{
    string output = string.Join(" : ", input.Split(':').Select(x => x.Trim()));
}

sw.Stop();

/*(...)
*/

You indicated that the first word would not have any spaces. So in my opinion the most efficient, non-regex solution would be to remove all whitespace from the string (since you dont want any), then just replace the : with :

string input = "Apple   :     100";
input = new string(input.ToCharArray()
                 .Where(c => !Char.IsWhiteSpace(c))
                 .ToArray());
input = input.Replace(":", " : ");

Fiddle here

You could use Regex:

string input = "Apple:            100";

// Matches zero or more whitespace characters (\s*) followed by 
// a colon and zero or more whitespace characters
string result = Regex.Replace(input, @"\s*:\s*", " : "); // Result: "Apple : 100"

Im not sure how much performance a string builder and shortening to an array will do but you could try something like this.

string input = "Apple:100";

if (input.Contains(":"))
{
  string[] parts = input.Split(':');

  StringBuilder builder = new StringBuilder();
  builder.Append(parts[0].Trim());
  builder.Append(" : ");
  builder.Append(parts[1].Trim());
  input = builder.ToString();
}
Related