c # – algorithme de correspondance de chaîne de Boyer Moore

Vous recherchez des commentaires sur l'exactitude, l'efficacité, la clarté et les usages idiomatiques de C #. Il passe tous les tests sur LeetCode.

en utilisant le système;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

Programme de classe statique interne
{
    void statique privé Main (string[] args)
    {
        string text = "Hello World";
        string pattern = "World";
        Console.WriteLine (string.Join (",", BoyerMoore (text, pattern)));
    }

    public statique IEnumerable BoyerMoore (texte de chaîne, motif de chaîne)
    {
        if (pattern.Equals (""))
        {
            rendement retour 0;
            rupture de rendement;
        }

        // Pour une première lecture de l'algorithme, il est conseillé de lire ceci plus tard, supposons simplement que vous avez une table de décalage magique calculée
        int[] goodSuffixShifts = BuildGoodSuffixShifts (modèle);

        dictionnaire badCharacterShifts = BuildBadCharacterShifts (modèle);

        int i = 0;
        tandis que (i + pattern.Length <= text.Length)
        {
            // Consider the pattern is placed so that its first character it under the ith character of the text
            // We started the matching from the right of the pattern
            int patternIndex = pattern.Length - 1;
            int textIndex = i + patternIndex;

            // TODO: Avoid comparing known matches
            while (patternIndex >= 0 && text[textIndex] == motif[patternIndex])
            {
                textIndex--;
                patternIndex--;
            }

            // Si la boucle s'est terminée avec patternIndex == 0
            if (patternIndex == -1)
            {
                // Le motif entier correspond
                rapport de rendement i;
            }

            // Détermine combien de caractères on peut décaler le motif
            int matched = pattern.Length - patternIndex - 1;
            Debug.Assert (correspondant <= pattern.Length);

            int badCharacterShift = 0;
            if (patternIndex! = -1)
            {
                char badCharacter = text[textIndex];
                if (badCharacterShifts.ContainsKey (badCharacter))
                {
                    badCharacterShift = badCharacterShifts[badCharacter][patternIndex];
                }
                autre
                {
                    badCharacterShift = patternIndex + 1;
                }
            }

            int goodSuffixShift = 0;

            si (goodSuffixShifts[matched] == 0)
            {
                // Idéalement, nous aimerions commencer à comparer le motif décalé de la longueur du motif. Par exemple, nous pouvons avoir:
                //
                // texte: abceabcd
                // modèle avant décalage: abcd
                // modèle après décalage: abcd
                //
                goodSuffixShift = pattern.Length;
            }
            autre
            {
                // Mais ce n'est pas toujours possible, dans le cas où un suffixe du motif se répète en lui-même, par exemple, on peut avoir:
                //
                // texte: aaaaaaapqbbbbbbbbbbbbb
                // pattern before shift: pqaxpqxpq
                // modèle après décalage: pqaxpqxpq
                //
                // Dans le cas ci-dessus, nous avons déterminé que nous avons 2 caractères correspondants, comment déterminons-nous que nous devrions décaler de 7 caractères?
                // L'idée clé est que nous savons que nous n'avons pas seulement 2 caractères correspondants, nous savons également que le troisième caractère ne correspond pas,
                // cela nous amène à la notion de suffixe maximal.
                //
                // Une sous-chaîne du modèle est un suffixe maximal si elle correspond à un suffixe approprié du modèle et si elle ne peut pas s'étendre à gauche ou
                // s'il est étendu à gauche, il ne correspond plus à un modèle de suffixe.
                //
                // Dans l'exemple ci-dessus, nous avons deux suffixes maximaux:
                // **
                // ***
                // pqaxpqxpq
                //
                // Après la première correspondance, nous connaissons 2 correspondances de caractères. Nous décalerions donc le motif en essayant d'aligner le suffixe de longueur 2.
                // pour les caractères correspondants, nous obtenons le décalage de 7 caractères comme prévu.
                //
                // La question clé serait: pourquoi est-ce correct?
                // Si nous avions un suffixe maximal de longueur 1, l'aligner ne fera pas l'affaire, car nous échouerions au deuxième caractère.
                // Si nous avions un suffixe maximal de longueur 3, l'aligner ne va pas fonctionner non plus, car nous échouerions au troisième caractère,
                // comme nous le savions, le troisième caractère du texte ne correspond pas au modèle, mais le suffixe de longueur 3 ne
                //
                // Il y a juste un dernier tournant, si nous avions plus d'un suffixe de longueur 2, nous devons l'aligner sur le plus à droite pour être sûr
                // nous n'ignorons pas les hits potentiels
                //
                // Maintenant, lisez la ComputeShiftTable () pour voir comment les suffixes maximaux sont trouvés et la table de décalage construite
                //
                goodSuffixShift = goodSuffixShifts[matched];
            }
            i + = Math.Max ​​(badCharacterShift, goodSuffixShift);
        }
    }

    Dictionnaire statique privé BuildBadCharacterShifts (modèle de chaîne)
    {
        dictionnaire badCharacterShifts = nouveau dictionnaire();
        pour (int i = 0; i <pattern.Length; i ++)
        {
            char c = motif[i];
            int[] décalage;
            if (! badCharacterShifts.TryGetValue (c, out shift))
            {
                // Un nouveau tableau est par défaut rempli de zéro
                shift = new int[pattern.Length];
                badCharacterShifts.Add (c, shift);
            }

            // Par conséquent, nous avons un 1 dans le tableau à l'index correspondant si le
            // le caractère apparaît et 0 sinon
            décalage[i] = 1;
        }

        foreach (int[] changement dans badCharacterShifts.Values)
        {
            // Une valeur d'espace réservé pour indiquer que le tableau n'est jamais décalé
            int lastShift = -1;
            pour (int i = 0; i < shift.Length; i++)
            {
                if (shift[i] == 0)
                {
                    // At this point we are at a mismatch
                    if (lastShift == -1)
                    {
                        // There is no occurrence of the character before this position
                        // Therefore we can safely shift the pattern past the character
                        shift[i] = i + 1;
                    }
                    else
                    {
                        // Last time we shifted lastShift characters and then it aligns
                        // Therefore we can shift one more character now to align
                        shift[i] = ++lastShift;
                    }
                }
                else
                {
                    // Here we have as hit, the driver should not access this value
                    shift[i] = -1;
                    // And we need to shift no character to achieve alignment now
                    lastShift = 0;
                }
            }
        }

        return badCharacterShifts;
    }

    private static int[] BuildGoodSuffixShifts(string s)
    {
        int length = s.Length;
        int[] maximalSuffixLengths = new int[length - 1];
        int left = -1;
        int right = 0;
        for (int i = s.Length - 2; i >= 0; je--)
        {
            int currentLeft = i;
            int currentRight = i + 1;
            int préfixeLeft = longueur - 1;
            si (à gauche! = -1)
            {
                // Ici nous avons un suffixe maximal, comme toujours, nous avons:
                // s[gauche, droite) = s[gauche + longueur - droite, longueur)
                Debug.Assert (IsMaximalSuffix (s, gauche, droite));

                si (laissé <= i && i < right)
                {
                    // Now we know s[i] lies inside the leftmost maximal suffix
                    // In particular, s[left, i + 1) = s[left + length - right, i + length - right + 1)

                    // So we are interested to see the maximal suffix starting from i + length - right
                    int knownRight = i + length - right + 1;

                    //   knownRight - 1 - i 
                    // = i + length - right + 1 - 1 - i
                    // = length - right > 0
                    // Par conséquent, nous savons knownRight - 1> i - nous accédons toujours au tableau qui doit déjà avoir été rempli
                    Debug.Assert (knownRight - 1> i);
                    int unknownLength = maximalSuffixLengths[knownRight - 1];

                    int unknownLeft = knownRight - unknownLength;
                    Debug.Assert (knownLeft> = 0);

                    // En termes de variables, nous avons
                    // s[knownLeft, knownRight) = s[connue à gauche + longueur - connue à droite, longueur)
                    Debug.Assert (IsSuffix (s, connu à gauche, connu à droite));

                    // Nous souhaitons utiliser la relation s[gauche, i + 1) = s[gauche + longueur - droite, i + longueur - droite + 1)
                    // Il faut donc s'assurer que s[knownLeft, knownRight) est une sous-chaîne de s[gauche + longueur - droite, i + longueur - droite + 1)
                    if (connueLeft <gauche + longueur - droite)
                    {
                        knownLeft = left + length - right;
                    }
                    Debug.Assert (left + length - right <= knownLeft && knownLeft <longueur);
                    Debug.Assert (left + length - right <knownRight && knownRight <= length);

                    // Now we can shift them back, and this now we have this
                    // s[knownLeft, knownRight = i + 1) = s[knownLeft + length - knownRight, length)
                    knownLeft = knownLeft + right - length;
                    knownRight = knownRight + right - length;
                    Debug.Assert(knownRight == i + 1);
                    Debug.Assert(IsSuffix(s, knownLeft, knownRight));

                    currentLeft = knownLeft - 1;
                    prefixLeft = knownLeft + length - knownRight - 1;
                }
            }

            // Now, we extend the maximal suffix until we cannot

            // Note that in this loop, we are either exploring characters already discovered in a known maximal suffix
            // in which the loop should terminate right away because the maximal suffix terminated inside, or we are 
            // discovering new characters. Therefore the total time spent on this loop should be proportional to the length of s
            while (currentLeft >= 0 && s[currentLeft] == s[prefixLeft])
            {
                currentLeft--;
                prefixLeft--;
            }

            // nous avons trop bougé, rajuste
            currentLeft ++;
            prefixLeft ++;

            // Maintenant nous avons trouvé le suffixe maximal
            Debug.Assert (IsMaximalSuffix (s, currentLeft, currentRight));

            // Tenue de livre pour le suffixe le plus maximal à gauche
            if (left == -1 || currentLeft <left)
            {
                left = currentLeft;
                right = currentRight;
            }

            // Et économise les longueurs
            maximumSuffixLengths[i] = currentRight - currentLeft;
        }

        // Assurez-vous que tout est bien en mode débogage
        pour (int i = 0; i < s.Length - 1; i++)
        {
            Debug.Assert(IsMaximalSuffix(s, i + 1 - maximalSuffixLengths[i], i + 1));
        }

        // Now we compute the shift table, the number of character matched could range from 0 to length
        int[] shifts = new int[length + 1];

        // Starting from the back
        for (int i = length - 2; i >= 0; je--)
        {
            int maximalSuffixLength = maximalSuffixLengths[i];

            // Notez que i + shift = longueur - 1, ceci est conçu pour que le ième caractère s'aligne sur le dernier caractère.
            int shift = longueur - 1 - i;

            si (changements[maximalSuffixLength] == 0)
            {
                des changements[maximalSuffixLength] = décalage;
            }
            si (changements[length] == 0 && maximalSuffixLength == (i + 1))
            {
                //
                // Si le motif complet correspond, nous n'aurons jamais de suffixe maximal de longueur n (il doit être correct)
                // Par conséquent, la règle ci-dessus ne peut pas gérer ce cas particulier et doit être analysée différemment
                //
                // Supposons qu'un motif complet soit mis en correspondance:
                // texte: abcabcxxxxxxxx
                // modèle: abcabc
                //
                // Dans ce cas, nous savons que nous devrions décaler de 3 car le préfixe correspond à un suffixe. Nous pouvons voir que pour tout
                // décale moins que la longueur du motif, ça doit être le cas.
                //
                // Nous pouvons détecter le préfixe correspondant à un suffixe en vérifiant que le suffixe maximal commence à 0. Notez que
                // lorsqu'un suffixe maximal commence à 0 et se termine à i, il a une longueur (i + 1), c'est ce que la condition vérifie
                //
                // Encore une fois, si nous avons plusieurs moyens pour que le préfixe corresponde au suffixe, nous choisissons celui qui a le moins de décalage pour nous assurer que
                // nous capturons toutes les occurrences, cela pourrait arriver dans ce cas:
                //
                // texte: aaxaaxaa
                // motif: aaxaa
                //
                // Dans ce cas, nous ne pouvons décaler que de 3, pas 4.
                //
                des changements[length] = décalage;
            }
        }

        retourner des quarts de travail;
    }

    chaîne statique privée SubstringLeftRight (cette chaîne s, int gauche, int droite)
    {
        si (à gauche == à la longueur)
        {
            Debug.Assert (right == s.Length);
            revenir "";
        }
        autre
        {
            Debug.Assert (left> = 0);
            Debug.Assert (right> = 0);
            Debug.Assert (à droite <= s.Length);
            Debug.Assert(right >= gauche);
            retour à la sous-chaîne (gauche, droite - gauche);
        }
    }

    private bool IsSuffix (chaîne s, int gauche, int droite)
    {
        return s.SubstringLeftRight (gauche, droite) == s.SubstringLeftRight (left + s.Length - right, s.Length);
    }

    private bool IsMaximalSuffix (chaîne s, int gauche, int droite)
    {
        si (IsSuffix (s, gauche, droite))
        {
            si (gauche> 0)
            {
                retour! IsSuffix (s, left - 1, right);
            }
            autre
            {
                retourne vrai;
            }
        }
        retourne faux;
    }
}