Click here to Skip to main content
15,886,840 members
Articles / Programming Languages / C#
Alternative
Tip/Trick

How to Toggle String Case in .NET

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
5 Mar 2011CPOL1 min read 6.9K   1  
This code is 27% faster on my machine (.NET 4/AnyCPU on a 64bit i5 processor) for the Unicode test than the Ayoola/Bell algorithm.No unsafe code (in fact was 18% faster than the unsafe version as posted) and works with Unicode.public static string ToggleCase_SimmoTech(string s){ ...
This code is 27% faster on my machine (.NET 4/AnyCPU on a 64bit i5 processor) for the Unicode test than the Ayoola/Bell algorithm.
No unsafe code (in fact was 18% faster than the unsafe version as posted) and works with Unicode.

C#
public static string ToggleCase_SimmoTech(string s)
{
    char[] chs = s.ToCharArray();

    for (var i = 0; i < s.Length; ++i)
    {
        char ch = chs[i];

        if (ch <= 0xff)
        {
            if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
                (ch >= 0xc0 && ch <= 0xde && ch != 0xd7) || (ch >= 0xe0 && ch <= 0xfe && ch != 0xf7))
            {
                chs[i] = (char) (ch ^ 0x20);
            }

            continue;
        }

        switch (char.GetUnicodeCategory(ch))
        {
            case UnicodeCategory.UppercaseLetter:
                chs[i] = char.ToLower(ch);
                break;

            case UnicodeCategory.LowercaseLetter:
                chs[i] = char.ToUpper(ch);
                break;
        }
    }

    return (new string(chs));
}


Some musings:

  1. No mention of .NET version has been made for comparsion. On my machine, .NET 3.5 was much faster for some reason - 28% faster in fact and I have no idea why! I suspect that the StringBuilder solutions will be faster under .NET 4.0 as StringBuilder.Append(char) has been vastly improved.
  2. In for next loops, ++i is faster then i++. I only discovered this recently!
  3. Every solution seems to have a different version of timing!
    For this, I created a unit test class (NUnit) and a different Test for each algorithm. Each test calls the same Timing method which shows the individual test times, shows the fastest time, garbage collects between repeats to minimize differences in times, and checks that the algorithm actually works!
    NB: Only run one test at a time! Timings can be vastly different when running multiple tests and completely ruins comparisons. I use Resharper so I can click in the margin for any given Test to run it.

Here is the code (looks better when pasted into a file!):
C#
using System;
using System.Diagnostics;
using System.Globalization;

using NUnit.Framework;

namespace ToggleCaseSpeedTest
{
    [TestFixture]
    public class ToggleCase
    {
        const int IterationCount = 100001; // Must be odd number to account for the unsafe inline toggle!
        const int RepeatCount = 5;

        const string TestString1_Unicode =          @"Π=3.1415926?!! Καλό!#!%# ÜBERGRößEN!!Аффтар Жжот??@!e=2.71828182?#!!$@\^i^/!@$";
        const string TestString1_Unicode_Result =   @"π=3.1415926?!! κΑΛΌ!#!%# übergrÖßen!!аФФТАР жЖОТ??@!E=2.71828182?#!!$@\^I^/!@$";
        const string TestString2_Numbers =          @"π=3.14159265358979323846264338327950288419716939937510....!!!!";
        const string TestString2_Numbers_Result =   @"Π=3.14159265358979323846264338327950288419716939937510....!!!!";
        const string TestString3_LowerCase =        @"nevr un-den-erstimate ze pauer of stoopid piplz in larg grupp!\*^*/";
        const string TestString3_LowerCase_Result = @"NEVR UN-DEN-ERSTIMATE ZE PAUER OF STOOPID PIPLZ IN LARG GRUPP!\*^*/";
        const string TestString4_UpperCase =        @"DUDE, WHY U R HERE?? U SHOULDA BE IN THE MEETING (BLAH-BLAH) $\*o*/$!";
        const string TestString4_UpperCase_Result = @"dude, why u r here?? u shoulda be in the meeting (blah-blah) $\*O*/$!";

        [Test]
        public void ToggleCase_Ayoola_Bell_Test()
        {
            TimeTest(TestString1_Unicode, TestString1_Unicode_Result, ToggleCase_Ayoola_Bell);
            TimeTest(TestString2_Numbers, TestString2_Numbers_Result, ToggleCase_Ayoola_Bell);
            TimeTest(TestString3_LowerCase, TestString3_LowerCase_Result, ToggleCase_Ayoola_Bell);
            TimeTest(TestString4_UpperCase, TestString4_UpperCase_Result, ToggleCase_Ayoola_Bell);
        }

        [Test]
        public void ToggleCase_SimmoTech_Test()
        {
            TimeTest(TestString1_Unicode, TestString1_Unicode_Result, ToggleCase_SimmoTech);
            TimeTest(TestString2_Numbers, TestString2_Numbers_Result, ToggleCase_SimmoTech);
            TimeTest(TestString3_LowerCase, TestString3_LowerCase_Result, ToggleCase_SimmoTech);
            TimeTest(TestString4_UpperCase, TestString4_UpperCase_Result, ToggleCase_SimmoTech);
        }

        [Test]
        public void ToggleCase_Alternate_11_Test()
        {
            TimeTest(TestString1_Unicode, TestString1_Unicode_Result, ToggleCase_Alternate_11);
            TimeTest(TestString2_Numbers, TestString2_Numbers_Result, ToggleCase_Alternate_11);
            TimeTest(TestString3_LowerCase, TestString3_LowerCase_Result, ToggleCase_Alternate_11);
            TimeTest(TestString4_UpperCase, TestString4_UpperCase_Result, ToggleCase_Alternate_11);
        }

        static void TimeTest(string testInput, string expectedResult, Func<string, string> algorithmToTest)
        {
            long bestTime = long.MaxValue;

            for(var repeat = 0; repeat < RepeatCount; ++repeat)
            {
                GC.Collect();

                string result = null;

                var timer = Stopwatch.StartNew();

                for (var i = 0; i < IterationCount; ++i)
                {
                    result = algorithmToTest(testInput);
                }

                timer.Stop();

                if (repeat == 0 && result != null)
                {
                    var compareResult = string.CompareOrdinal(result, expectedResult);

                    if (compareResult != 0)
                    {
                        Console.WriteLine(expectedResult);
                        Console.WriteLine(result);

                        int matchLength = 0;
                        for(var i = 0; i < Math.Min(result.Length, expectedResult.Length); ++i, matchLength++)
                        {
                            if (result[i] != expectedResult[i]) break;
                        }

                        Console.WriteLine(new string(' ', matchLength) + "^");
                        throw new Exception("Did not produce the correct result!");

                    }

                    if (algorithmToTest(result) != testInput)
                    {
                        throw new Exception("Did not round trip!");
                    }
                }

                var elapsedMilliseconds = timer.ElapsedMilliseconds;
                Console.WriteLine("{0}: Total time={1:n0} ms, single item={2:n3} us", repeat + 1, elapsedMilliseconds, elapsedMilliseconds / 100d);

                bestTime = Math.Min(bestTime, elapsedMilliseconds);
            }

            Console.WriteLine();
            Console.WriteLine("Best: Total time={0:n0} ms, single item={1:n3} us", bestTime, bestTime / 100d);
            Console.WriteLine();
            Console.WriteLine();
        }

        public static string ToggleCase_Ayoola_Bell(string s)
        {
            char[] chs = s.ToCharArray();
            for (int i = s.Length - 1; i >= 0; i--)
            {
                char ch = chs[i];
                if (char.IsLetter(ch))
                {
                    char foo = (char) (ch & ~0x20);
                    if ((foo >= 0x41 && foo <= 0x5a) || (foo >= 0xc0 && foo <= 0xde && foo != 0xd7)) chs[i] = (char) (ch ^ 0x20);
                    else if ((foo == 0xdf || ch > 0xff)) chs[i] = char.IsLower(ch) ? char.ToUpper(ch) : char.ToLower(ch);
                }
            }
            return (new String(chs));
        }

        public static unsafe string ToggleCase_Alternate_11(string s)
        {
            fixed (char* p = s)
            {
                for (int i = s.Length - 1; i != -1; --i)
                {
                    char ch = p[i];
                    char foo = (char) (ch & ~0x20);
                    if ((foo >= 0x41 && foo <= 0x5a) || (foo >= 0xc0 && foo <= 0xde && foo != 0xd7))
                    {
                        p[i] = (char) (ch ^ 0x20);
                    }
                    else if ((foo == 0xdf || ch > 0xff) && char.IsLetter(ch))
                    {
                        p[i] = char.IsLower(ch) ? char.ToUpper(ch) : char.ToLower(ch);
                    }
                }
            }

            return s;
        }

        public static string ToggleCase_SimmoTech(string s)
        {
            char[] chs = s.ToCharArray();

            for (var i = 0; i < s.Length; ++i)
            {
                char ch = chs[i];

                if (ch <= 0xff)
                {
                    if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
                        (ch >= 0xc0 && ch <= 0xde && ch != 0xd7) || (ch >= 0xe0 && ch <= 0xfe && ch != 0xf7))
                    {
                        chs[i] = (char) (ch ^ 0x20);
                    }

                    continue;
                }

                switch (char.GetUnicodeCategory(ch))
                {
                    case UnicodeCategory.UppercaseLetter:
                        chs[i] = char.ToLower(ch);
                        break;

                    case UnicodeCategory.LowercaseLetter:
                        chs[i] = char.ToUpper(ch);
                        break;
                }
            }

            return (new string(chs));
        }
    }
}


Cheers,
Simon

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Hunton Information Systems Ltd.
United Kingdom United Kingdom
Simon Hewitt is a freelance IT consultant and is MD of Hunton Information Systems Ltd.

He is currently looking for contract work in London.

He is happily married to Karen (originally from Florida, US), has a lovely daughter Bailey, and they live in Kings Langley, Hertfordshire, UK.

Comments and Discussions

 
-- There are no messages in this forum --