This post was originally published on Coding Glamour.

In Nederland kennen we zo'n 240.000 geografische entiteiten (straten, buurten, plaatsen, gemeentes, etc.) die gebruik maken van NEN-norm 5825 voor de officiële schrijfwijze. Een norm waarin géén diakritische tekens mogen worden gebruikt. So far so good.

Wikipedia over diakrieten: Een diakritisch teken is een teken dat boven, onder of door een letter gezet wordt en nodig is voor de uitspraak. Met diakritische tekens kunnen verschillende aspecten van de uitspraak worden aangegeven. Voorbeelden hiervan zijn de tekens op é, â, ö.

Zelfde data, andere schrijfwijze?
So far so good, tot het CBS met een nieuwe aanlevering van buurtinformatie komt. Een groot Excel document waarin ze de schrijfwijze van buurten zoals de gemeente deze hanteert overnemen; waar uiteraard wél diakritische tekens kunnen voorkomen. Gevolg: problemen bij het koppelen van de nieuwe CBS-data, aan onze bestaande data.

Normalisatietijd dus!

Normaliseren van buurt- en gemeentenamen tussen twee datasets
1. Vind alle buurt / gemeente combinaties die direct matchen.
2. Verwijder diakritische tekens, en herhaal 1.
3. Nog niet gelukt? Gebruik het fonetische algoritme Soundex om overeenkomstige gebieden te vinden.

Nu hebben we voor bovenstaande stappen wel wat code nodig:

Diakrieten verwijderen in C#
Unicode kent een aantal schrijfwijzen; waarin de output hetzelfde is, maar de binaire representatie hetzelfde. Deze staan beschreven in de Unicode Normalization Forms. FormD is voor het verwijderen van diakritische tekens erg handig, omdat hierin het teken û wordt gerepresenteerd als 2 karakters: de 'u', en de toevoeging '^' als los karakter met een eigen unicode teken. In .NET is voor het normalizeren van Unicode de functie 'Normalize' beschikbaar, waarin ook FormD beschikbaar is.


string q = "Wûnseradiel";
char[] normalised = q.Normalize(NormalizationForm.FormD).ToCharArray();
q = new string(normalised.Where(c => (int) c <= 127).ToArray());
// q == "Wunseradiel"


Soundex in C#
Soundex is een fonetisch algoritme dat gebruikt kan worden om 2 strings te vergelijken op uitspraak. In de meeste SQL dialecten is een vorm van dit algoritme beschikbaar, maar meestal in het Engels. Daarom hieronder een Nederlandse versie van Soundex, op basis van een artikel van D. Patrick Caldwell.

var a = DutchSoundex("Overeisel"); // O4070205
var b = DutchSoundex("Overrijssel"); // O4070205
// omg! A en B zijn gelijk! It's magic!
private static Regex simplify = new Regex(@"(\d)\1*D?\1+", RegexOptions.Compiled);
public string DutchSoundex(string s)
{
    // encoding info, e.g. M heeft waarde 6
    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const string codes = "01230420002566012723044802";
    // karakters vervangen
    Dictionary<string, string> replacements = new Dictionary<string, string>()
                                                  {
                                                      {"QU", "KW"},
                                                      {"SCH", "SEE"},
                                                      {"KS", "XX"},
                                                      {"KX", "XX"},
                                                      {"KC", "KK"},
                                                      {"CK", "KK"},
                                                      {"DT", "TT"},
                                                      {"TD", "TT"},
                                                      {"CH", "GG"},
                                                      {"SZ", "SS"},
                                                      {"IJ", "YY"}
                                                  };
    s = s.ToUpper();
    // vervang de waardes in de dictionary
    foreach (var replacementRule in replacements)
        s = s.Replace(replacementRule.Key, replacementRule.Value);
    StringBuilder coded = new StringBuilder();
    // bereken de waardes op basis van de encoding array
    for (int i = 0; i < s.Length; i++)
    {
        int index = chars.IndexOf(s[i]);
        if (index >= 0)
            coded.Append(codes[index]);
    }
    string result = coded.ToString();
    // repeating karakters vervangen
    result = simplify.Replace(result, "$1").Substring(1);
    // return the first character followed by the coded string
    return string.Format("{0}{1}", s[0], result);
}


Op basis van regel 2. en 3. hebben we 94% van de probleemgevallen automatisch kunnen fixen!