FirstUnmatchedIndex using CurrentCultureIgnoreCase

Viewed 118

I need to support languages that can use non ascii letters in input text, so i need to implement StringComparison.CurrentCultureIgnoreCase for FirstUnmatchedIndex. Ignoring casing isn't so bad, but i don't know how to convert combined symbols to a standard representation and then compare. So here are a few cases where the function should return -1 but returns something else instead....

encyclopædia = encyclopaedia
Archæology = Archaeology
ARCHÆOLOGY = archaeology
Archæology = archaeology
Weißbier = WEISSBIER

How to know if a char needs to be expanded and convert each char to the expanded form when needed ?

/// <summary>
/// Gets a first different char occurence index
/// </summary>
/// <param name="a">First string</param>
/// <param name="b">Second string</param>
/// <param name="compareSmallest">
/// If true, Returns the first difference found or -1 if the end of a string is reached without finding a difference.
/// IE, Return -1 if the smallest string is contained in the other.
/// Otherwise returns -1 only if both string are really the same and will return the position where the smallest string ends if no difference is found.
/// </param>
/// <returns>
/// Returns first difference index or -1 if no difference is found
/// </returns>
public static int FirstUnmatchedIndex(this string a, string b, bool compareSmallest = false, StringComparison comparisonType = StringComparison.CurrentCulture)
{
    //Treat null as empty
    if (String.IsNullOrEmpty(a)) {
        if (String.IsNullOrEmpty(b)) {
            //Equal, both empty.
            return -1;
        } else {
            //If compareSmallest, empty is always found in longest.
            //Otherwise, difference at pos 0.
            return compareSmallest ? -1 : 0;
        }
    }
    if (object.ReferenceEquals(a, b)) {
        //Same Ref.
        return -1;
    }

    //Convert strings before compare.
    switch (comparisonType) {
        case StringComparison.CurrentCulture:
            //FIXME
            break;
        case StringComparison.CurrentCultureIgnoreCase:
            //FIXME
            var currentCulture = System.Globalization.CultureInfo.CurrentCulture;
            a = a.ToLower(currentCulture);
            b = b.ToLower(currentCulture);
            break;
        case StringComparison.InvariantCulture:
            //FIXME
            break;
        case StringComparison.InvariantCultureIgnoreCase:
            //FIXME
            a = a.ToLowerInvariant();
            b = b.ToLowerInvariant();
            break;
        case StringComparison.OrdinalIgnoreCase:
            a = a.ToLower();
            b = b.ToLower();
            break;
        case StringComparison.Ordinal:
            //Ordinal(Binary) comprare, nothing special to do.
        default:
            break;
    }

    string longStr = a.Length > b.Length ? a : b;
    string shortStr = a.Length > b.Length ? b : a;

    int count = shortStr.Length;
    for (int idx = 0; idx < count; idx++) {
        //FIXME Check if char needs to be expanded ?
        if (shortStr[idx] != longStr[idx]) {
            return idx;
        }
    }
    return compareSmallest || longStr.Length == count ? -1 : count;
}
1 Answers

I'm not sure if I understood your question correctly, but you can use "dictionary + regular expression" combination. The idea is to create dictionary with the characters you want to expand and find them with the help of regex. The following code shows example how to do it.

Explanation of regular expression:

  • (?i) - This enables case insensitive search (same as RegexOptions.IgnoreCase, but in-line)
  • [^\p{IsBasicLatin}]+ - This will search for all characters that don't fit basic Latin characters set (from \u0000 through \u007F).

The code uses ToLower method which avoids adding upper-case non-Latin characters to dictionary. You can, of course, not to do it, if you want to be explicit (i.e. adding all both lower-case and upper-case characters into dictionary and removing ToLower).

var dic = new Dictionary<string, string>
{
    ["æ"] = "ae",
    ["ß"] = "ss"
};

var words = new[] { "encyclopædia", "Archæology", "ARCHÆOLOGY", "Archæology", "Weißbier" };
var pattern = @"(?i)[^\p{IsBasicLatin}]+";

int x = -1;
foreach(var word in words)
{
    // Each match (m.Value) is passed to dictionary 
    words[++x] = Regex.Replace(word, pattern, m => dic[m.Value.ToLower()]);
}
words.ToList().ForEach(WriteLine);

/*
    Output:
        encyclopaedia
        Archaeology
        ARCHaeOLOGY
        Archaeology
        Weissbier
*/
Related