7
\$\begingroup\$

I have a method that I am using to remove accents from certain characters. The problem is the massive slew of characters I am expected to work with. I have to, basically, remove accents from all Latin characters that fit within the 26 English Latin characters. (A through Z.) Performance is a very large requirement. It has to be lightning fast, as I have to run this on every character within a string, and process many large strings at a time.

Currently, I use a gigantic switch statement to detect what character it is, and return the appropriate A through Z "naked" character, while preserving case.

As of now, my switch looks something like the following:

switch (input)
{
    case 'À': // 0192
    case 'Á': // 0193
    case 'Â': // 0194
    case 'Ã': // 0195
    case 'Ä': // 0196
    case 'Å': // 0197
    case 'Ā': // 0256
    case 'Ă': // 0258
    case 'Ą': // 0260
        return 'A';
    case 'Ç': // 0199
    case 'Ć': // 0262
    case 'Ĉ': // 0264
    case 'Ċ': // 0266
    case 'Č': // 0268
        return 'C';
    case 'Ď': // 0270
    case 'Đ': // 0272
        return 'D';
    // Other upper case characters
    case 'à': // 0224
    case 'á': // 0225
    case 'â': // 0226
    case 'ã': // 0227
    case 'ä': // 0228
    case 'å': // 0229
    case 'ā': // 0257
    case 'ă': // 0259
    case 'ą': // 0261
        return 'a';
    case 'ç': // 0231
    case 'ć': // 0263
    case 'ĉ': // 0265
    case 'ċ': // 0267
    case 'č': // 0269
        return 'c';
    case 'ď': // 0271
    case 'đ': // 0273
        return 'D';
    // Other lower case characters
    default:
        return input;
}

As you can probably imagine, this method is over 200 lines, and this is the only thing it does.

private char RemoveAccent(char input)
{
    switch (input)
    {
        // You saw all the case statements
    }
}

Literally, that is it. My questions come down to the following, and this is more of a question of performance/better ways of handling the situation.

I know I can take a Regex and do the same thing very easily. It's what I used in the beginning as a shortcut. The problem, is that the regex is phenomenally slow. Essentially, what I was doing is looping over the 26 alphas, and then using the regex "[ALPHACHARACTER]", and replacing it with ALPHACHARACTER.

This method, however, was horrendous in performance. Over a set of input x, with data logic y, it was a whopping 1500ms.

Now, when I exclude this method from the logic, it performed at about 110ms. This is with the same input x and logic y, mind you. The only difference is that this method had a body of return input;.

So, I went to the switch method, which is very high in performance, and very low in maintainability. (The Calculate Code Metrics in Visual Studio puts it at a 41 for this method.) This one performed at 120ms with the exact same remaining logic 'y' and input 'x' as the Regex option. This allows me to exclude all other logic as being the issue.

The only other option I can think of, is to use an if statement on character ranges to at least make it more maintainable. The question on it is performance.

if ((input >= 'À' && input <= 'Å') || (input >= 'Ā' && input <= 'Ą' && input % 2 == 0))
    return 'A';
else if ((input == 'Ç') || (input >= 'Ć' && input <= 'Č' && input % 2 == 0))
    return 'C';
else if (input >= 'Ď' && input <= 'Đ' && input % 2 == 0)
    return 'D';
// Other upper case characters
else if ((input >= 'à' && input <= 'å') || (input >= 'ā' && input <= 'ą' && input % 2 == 1))
    return 'A';
else if ((input == 'ç') || (input >= 'ć' && input <= 'č' && input % 2 == 1))
    return 'C';
else if (input >= 'ď' && input <= 'đ' && input % 2 == 1)
    return 'D';
// Other lower case characters

return input;

Also, this is not all of the inputs. The 200 lines I mentioned earlier doesn't even cover a quarter of the characters I have to support. The thing is, I find the switch more readable, personally.

My questions are:

  1. Is there a significantly more readable way, to write this, without significantly losing performance?
  2. Is my switch statement the way to go if I favour performance over maintainability?
  3. Should I stick with it regardless of what metrics Visual Studio might mention if it's more readable/understandable to me, and already has acceptable performance?
  4. Is there something already in .NET to do what I am that might perform faster?

The only reason I ask these things, is that I feel like I'm doing something wrong, like I'm missing something in the way I approach this.

Personally, I think that since I find it readable, and it's already fast enough, there's no point to change it. The only problem is that I still have a lot of options to add to it, so it will only grow in size. The management of it could get a bit out of hand.

\$\endgroup\$
11
\$\begingroup\$

This appears to be a duplicate of this question. The link suggests using .NET's String.Normalize. If it's too slow, you could simply create an associative array (e.g., a Dictionary that maps char->char) for constant-time lookup. This is going to be large, too, but I would think it's probably easier to maintain.

\$\endgroup\$
  • \$\begingroup\$ Interesting, that seems to be working quite well, and is just as quick as my switch. (Maybe even faster.) I think it's safe to say that this resolves my issue, and I'm not sure why I decided to do it the hard way. (I'm not sure how I missed that SO question. I guess I just assumed no one else has this issue.) \$\endgroup\$ – Der Kommissar Jun 12 '15 at 17:50

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.