11
\$\begingroup\$

I'm creating my own game and for some statistics I need to get the names of specific objects. The problem was that I had them declared like "SmallFireBall" but I wanted them to appear like"Small Fire Ball" because that's something the user is able to see, so I decided to create my own extension method to help me out:

public static string SplitOnCapitalLetters(this string inputString)
{
    List<char> cleanString = inputString.ToList();
    for (int i = 1; i < cleanString.Count; i++)
    {
        if (char.IsUpper(cleanString[i]))
        {
            char[] temp = new char[cleanString.Count - i];
            for (int j = 0; j < temp.Length; j++)
            {
                temp[j] = cleanString[j + i];
            }
            cleanString[i] = ' ';
            cleanString.Add(' ');
            int index = 0;
            for (int j = i + 1; j < cleanString.Count; j++)
            {
                cleanString[j] = temp[index];
                index++;
            }
            i++;
        }
    }
    return new string(cleanString.ToArray());
}

However, I'm not quite confident about it. It seems quite big and maybe a little bit ugly, so please address any performance or code style issues.

\$\endgroup\$

1 Answer 1

20
\$\begingroup\$

With regex it's virtually a one-liner:

var words = 
    Regex.Matches("SmallFireBall", @"([A-Z][a-z]+)")
    .Cast<Match>()
    .Select(m => m.Value);

var withSpaces = string.Join(" ", words);
  • Regex.Matches - searches an input string for all occurrences of a regular expression and returns all the matches. MSDN
  • [A-Z][a-z]+ - matches strings that begin with a capital letter and are followed by one or more lowercase letters

Output:

Small Fire Ball


Let's review your code anyway and optimize it a little bit. Even without regex it still can be very short.

  • Use a StringBuilder for building string dynamically
  • You can use a foreach loop for strings too

    public static string SplitOnCapitalLetters2(this string inputString)
    {
        var result = new StringBuilder();
    
        foreach (var ch in inputString)
        {
            if (char.IsUpper(ch) && result.Length > 0)
            {
                result.Append(' ');
            }
            result.Append(ch);
        }
        return result.ToString();
    }
    

The 3rd alternative would be only LINQ:

public static string SplitOnCapitalLetters3(this string inputString)
{
    // starts with an empty string and accumulates the new string into 'result'
    // 'next' is the next character
    return inputString.Aggregate(string.Empty, (result, next) =>
    {
        if (char.IsUpper(next) && result.Length > 0)
        {
            result += ' ';
        }
        return result + next;
    });
}
\$\endgroup\$
6
  • \$\begingroup\$ @denis see also the edit about your code ;-) \$\endgroup\$
    – t3chb0t
    Commented Jul 2, 2016 at 16:53
  • 2
    \$\begingroup\$ That's a great answer. You provided an alternative to mine and also reviewed my code that's all I can ask for thank you a lot ! \$\endgroup\$
    – Denis
    Commented Jul 2, 2016 at 16:55
  • 2
    \$\begingroup\$ Actually there is even a 3rd alternative if you like LINQ (see the latest edit) ;-] but it just replaces the loop with a lambda. \$\endgroup\$
    – t3chb0t
    Commented Jul 2, 2016 at 17:05
  • \$\begingroup\$ What does .Cast<Match>() buy you? \$\endgroup\$
    – ruffin
    Commented Sep 23, 2019 at 21:28
  • \$\begingroup\$ @ruffin the result of Matches is a collection but not an IEnumerable of the expected Match type so you need to cast it. Try it out. Without Cast it won't work. \$\endgroup\$
    – t3chb0t
    Commented Sep 24, 2019 at 5:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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