12
\$\begingroup\$

I want to replace the fifth space in a sentence with a new line. Sample input:

"Hi this is empty string. This should be in new line!"

Expected output:

"Hi this is empty string.

This should be in new line!"

I'm getting the expected output with this code, but I need to know, is this the optimized solution or there are any better solution than this?

    string s = "Hi this is empty string. This should be in new line!", t = "";
    int countSpaces = s.Count(Char.IsWhiteSpace);
    if (countSpaces > 3)
    {
        int count = 0;
        char n;
        foreach (char c in s)
        {
            if (c == ' ') count++;
            if (count == 5)
            {
                n = '\n';
                count++;
            }
            else n = c;
            t += n;
        }
    }
    else t = s;
    MessageBox.Show(t);
\$\endgroup\$
13
\$\begingroup\$

Using += to concatenate strings in a loop scales very badly. Each iteration creates a new string instance by copying the previous string and the new character into a new string, and that string grows for each iteration. Using a StringBuilder (as Dmitry suggests) would be a great improvment.

However, you don't need to build the string character by character. You could use a regular expression to replace the fifth space with a newline:

string t = Regex.Replace(s, "^([^ ]+(?: [^ ]+){4}) ", "$1" + Environment.NewLine);

Explanation of the pattern:

^       mathces the beginning of the string
(       starts a catching group
[^ ]+   matches the first word (a sequence of non-space characters)
(?:     start a non-catching group
space   matches a space
[^ ]+   mathces a word
)       ends the non-catching group
{4}     repeats the group four times
)       ends the catching group
space   matches the fifth space

The $1 in the replacement string gets the value caught by the group.


Another alternative would be to loop through the string and look for spaces, then use Substring to get the parts before and after the fifth space:

int cnt = 0, index = 0;
while (cnt < 5) {
  if (s[index++] == ' ') cnt++;
}
string t = s.Substring(0, index - 1) + Environment.NewLine + s.Substring(index);
\$\endgroup\$
  • 1
    \$\begingroup\$ a big +1 for explaining the regex pattern ! \$\endgroup\$ – Heslacher Nov 7 '14 at 14:21
  • \$\begingroup\$ +1, I was trying to solve this myself with a mod and a string builder but to no avail. Regex is suited nicely to solving this. \$\endgroup\$ – d347hm4n Nov 7 '14 at 14:22
  • \$\begingroup\$ A better alternative to substrings and iterating chars could be String.Split and put the array back together with StringBuilder. \$\endgroup\$ – James Snell Nov 7 '14 at 16:52
  • \$\begingroup\$ @JamesSnell: That is another alternative, but I don't think that it's better. For one it creates more intermediate data. \$\endgroup\$ – Guffa Nov 7 '14 at 19:05
5
\$\begingroup\$

You could use the StringBuilder class and the String.IndexOf method as follows:

Option 1

private static string ReplaceNthOccurrence(string input, int n, char find, char replaceWith)
{
    int index = -1;
    int count = 0;
    StringBuilder sBuilder = new StringBuilder(input);
    while ((index = input.IndexOf(find, index + 1)) != -1)
    {
        if (++count == n)
        {
            sBuilder[index] = replaceWith;
            break;
        }
    }
    return sBuilder.ToString();
}

Usage:

string input = "Hi this is empty string. This should be in new line!";
string output = ReplaceNthOccurrence(input , 5, ' ', '\n');

The StringBuilder class allows us to replace a particular char in the string by its index.
The String.IndexOf method allows us to fast search for a desired char in the string.

Option 2

private static string ReplaceNthOccurrence(string input, int n, char find, char replaceWith)
{
    int index = -1;
    // Loop for `n` occurrences:
    for (int count = 0; count < n; count++)
    {
        // Find a position of the next occurrence:
        index = input.IndexOf(find, index + 1);
        // If not found, return the `input` string:
        if (index == -1)
            return input;
    }
    // Replace the char and return a resulting string:
    StringBuilder sBuilder = new StringBuilder(input);
    sBuilder[index] = replaceWith;
    return sBuilder.ToString();
}

In the latter option, you can use any other replacement approach instead of the StringBuilder.
For instance:

return input.Substring(0, index) + replaceWith + input.Substring(index + 1);
\$\endgroup\$
  • 1
    \$\begingroup\$ -1: this code is not very readable, I find it complex. \$\endgroup\$ – ANeves Nov 7 '14 at 13:58
  • 1
    \$\begingroup\$ ^^^ I don't agree. This code is clear and readable. \$\endgroup\$ – Heslacher Nov 7 '14 at 15:23
  • \$\begingroup\$ @ANeves I've updated the answer by introducing of the second option. Please revise. \$\endgroup\$ – Dmitry Nov 7 '14 at 16:36
  • \$\begingroup\$ The second option wouldn't do what the OP wants. I'll leave you the fun puzzle of working out why though. \$\endgroup\$ – James Snell Nov 7 '14 at 16:54
  • \$\begingroup\$ @JamesSnell are you kidding me? Both options have completely equivalent functionality. I've tested them. Are you sure you understand what the OP wants? \$\endgroup\$ – Dmitry Nov 7 '14 at 19:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.