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

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