Compter les occurrences d’une sous-chaine dans un texte

L’objectif est de compter les occurrences d’une chaîne de longueur quelconque en tenant compte ou non de la casse des caractères.
Je vous présente ici deux techniques, l’une par une UDF (User-Defined Function) et l’autre directement en ligne dans la requête SQL.

Création de la table et des ses lignes
Cette fontion créée la table tOccurrence qui est peuplée de 10 000 lignes de textes aléatoires.
Pour l’utiliser, copier l’ensemble du code dans un module VBA, placer le focus dans la fonction ‘CreationTableOccurrence()’ puis appuyer sur ‘F5′ pour l’exécuter.

Public Function CreationTableOccurrence()
   With DoCmd
      .SetWarnings False
      .RunSQL "CREATE TABLE tOccurrence (Texte TEXT);"
      .SetWarnings True
   End With
   GenereTextes
 
   MsgBox "Création terminée"
End Function
 
Private Function GenereTextes()
   Const clMaxLignes As Long = 10000
   Const cMinAscii As Long = 32   '= espace - chr(32)
  Const cMaxAscii As Long = 122 - cMinAscii + 1
 
   Dim odb As DAO.Database
   Dim ors As DAO.Recordset
   Dim Texte As String
   Dim i As Long, j As Long, l As Long
 
   Set odb = CurrentDb
   Set ors = odb.OpenRecordset("tOccurrence", dbOpenDynaset)
   Randomize
 
   With ors
      For i = 1 To clMaxLignes
         l = Int(Rnd() * 256)   'longueur variable du texte : 0 à 255
        Texte = String(l, Chr$(Int(cMaxAscii * Rnd()) + cMinAscii))   'Génère le texte avec un caractère par défaut
        For j = 1 To Int(l * Rnd())   ' de 0 à longueur texte-1 modifications
           Mid$(Texte, Int(l * Rnd()) + 1, 1) = Chr$(Int(cMaxAscii * Rnd()) + cMinAscii)   'affecte un caractère à une position aléatoire
        Next j
         .AddNew
         !Texte = Texte
         .Update
      Next i
      .Close
   End With
 
   Set ors = Nothing
   Set odb = Nothing
End Function

La technique via UDF
La fonction VBA utilise la fonction standard Split() qui génère un tableau (de base 0) de chaînes de caractères à partir d’un texte en le découpant via un séparateur à définir.
On peut définir le comportement de Split() en fonction de la casse des caractères grâce à son dernier paramètre optionnel :

  • vbBinaryCompare (valeur = 0) : Comparaison binaire des caractères donc sensible à la casse
  • vbTextCompare (valeur = 1) : Comparaison textuelle donc insensible à la casse des caractères
  • vbDatabaseCompare (valeur = 2) : Comparaison en fonction de la configuration locale de windows

Seules les valeurs 0 et 1 sont reproductibles d’un système à l’autre.

l’UDF :

Public Function CompteOccurrenceChaine(ByVal Texte As String, ByVal Chaine As String, Optional ByVal TypeComparaison As VbCompareMethod = vbBinaryCompare) As Long
   If Texte <> vbNullString Then CompteOccurrenceChaine = UBound(Split(Texte, Chaine, -1, TypeComparaison))
End Function

Cette fonction demande 3 paramètres dont le dernier est optionnel.
Le ‘Texte’ dans lequel on souhaite compter le nombre de sous-chaîne ‘Chaine’ et la méthode de comparaison qui est binaire par défaut.
La 1ère ligne du code retourne le plus grand indice de la dimension du tableau généré qui correspond donc au nombre d’occurrences de la chaine. En effet, le texte ‘A-B-C’ découpé sur ‘-‘ aura ‘A’ en indice 0 du tableau généré, ‘B’ en indice 1 et ‘C’ en indice 2. L’indice le plus grand est 2 ce qui correspond bien au nombre d’occurrences de ‘-‘.
Que se passe t’il si aucune occurrence n’est trouvée ou si Chaine= » » ? Split() retourne alors un tableau ayant qu’un seul indice égal à 0, donc pas de problème.
Que se passe t’il si le texte est vide Texte= » » ? Dans ce cas, Split() retourne un tableau particulier avec un indice le plus grand égal à -1 qui est inférieur à l’indice le plus petit du tableau (LBound() = 0)… C’est pour cette raison que l’on teste si ‘Texte’ est différent d’une chaîne vide (If Texte <> vbNullString Then …). Dans ce cas la fonction retournera 0.

On remarquera que Texte et Chaine sont de type String, la fonction n’appréciera donc pas une valeur NULL… Lorsque le cas peut se présenter, je passe en paramètre Texte & «  » et le problème du NULL est réglé ! Pas très beau pour les puristes mais efficace. Une autre solution serait de définir Texte et Chaine en tant que Variant et de vérifier s’ils sont différents de NULL ou bien encore, utiliser la fonction Nz() pour convertir les NULL en texte.

La requête SQL est dans ce cas :

PARAMETERS [Texte Recherché] Text ( 255 ), [TYPE Comparaison (0=Binaire/1=Texte)] Byte;
SELECT [Texte Recherché] AS [Texte Cherché],
       CompteOccurrences([texte] & "",[Texte Recherché] & "",[TYPE Comparaison (0=Binaire/1=Texte)]) AS Compte,
       Texte
FROM tOccurrence
WHERE CompteOccurrences([texte] & "",[Texte Recherché] & "",[TYPE Comparaison (0=Binaire/1=Texte)])>0
ORDER BY CompteOccurrences([texte] & "",[Texte Recherché] & "",[TYPE Comparaison (0=Binaire/1=Texte)]) DESC;

La requête a deux paramètres : Le texte recherché et le type de comparaison de texte (0 ou 1).
La clause WHERE permet de ne retourner que les textes qui possèdent au moins une occurrence et ORDER BY tri les résultats par ordre décroissant du nombre d’occurrences.

La technique directe dans le SQL
On utilise bien sûr des fonctions VBA mais elles sont appelées directement dans la requête.
Par contre, On n’utilise plus Split() et Ubound() car elles ne peuvent pas être appelées directement du SQL.

La technique retenue consiste à remplacer dans le Texte chaque occurrence de la chaine recherchée par une chaine vide. En connaissant aussi la longueur initiale du Texte et la longueur de la chaine recherchée on peut facilement déterminer le compte des occurrences.
Reprenons notre exemple ‘A-B-C’. La longueur initiale est 5, la longueur de la chaîne recherchée est 1 (‘-‘) et la longueur du texte après avoir retiré les chaînes recherchées est 3 (‘ABC’)
Donc (5 – 3) / 1 = 2 occurrences.
On utilise la fonction Len() pour déterminer la longueur des chaînes de caractères et la fonction Replace() pour remplacer les chaînes recherchées par une chaîne vide et pour gérer le type de comparaison (binaire ou texte).

La requête est :

PARAMETERS [Texte Recherché] Text ( 255 ), [TYPE Comparaison (0=Binaire/1=Texte)] Byte;
SELECT [Texte Recherché] AS [Texte Cherché],
       (Len([Texte])-Len(REPLACE([texte] & "",[Texte Recherché] & "","",1,-1,[TYPE Comparaison (0=Binaire/1=Texte)])))/Len([Texte Recherché]) AS Compte,
      Texte
FROM tOccurrence
WHERE (Len([Texte])-Len(REPLACE([texte] & "",[Texte Recherché] & "","",1,-1,[TYPE Comparaison (0=Binaire/1=Texte)])))/Len([Texte Recherché])>0
ORDER BY (Len([Texte])-Len(REPLACE([texte] & "",[Texte Recherché] & "","",1,-1,[TYPE Comparaison (0=Binaire/1=Texte)])))/Len([Texte Recherché]) DESC;

On remarquera que l’on passe [Texte] & «  » à la fonction Replace() pour éviter le problème des valeurs NULL (même chose pour le texte recherché).

Utilisation des requêtes
La requête exige deux paramètres. Le texte recherché doit contenir au moins un caractère et le type de comparaison (0 pour binaire ou 1 pour texte).

Performance
Pour comparer la performance des deux méthodes, il est nécessaire tout d’abord de fermer l’application Access puis de la réouvrir pour que la requête avec la fonction VBA soit performante.

@+

Philippe

Laisser un commentaire