web-dev-qa-db-fra.com

Correspondance des valeurs dans le tableau de chaînes

Problème: recherche d'un moyen plus efficace de trouver s'il existe une valeur de correspondance exacte dans un tableau 1d - essentiellement un booléen true/false.

Suis-je en train d'oublier quelque chose d'évident? Ou est-ce que j'utilise simplement la mauvaise structure de données, en utilisant un tableau alors que je devrais probablement utiliser un objet de collection ou un dictionnaire ? Dans ce dernier, j'ai pu vérifier le .Contains ou .Exists méthode, respectivement

Dans Excel, je peux vérifier une valeur dans un tableau vectoriel comme:

If Not IsError(Application.Match(strSearch, varToSearch, False)) Then
' Do stuff
End If

Cela retourne un index de correspondance exact, évidemment soumis aux limitations de la fonction Match qui ne trouve que la valeur correspondante d'abord dans ce contexte. Il s'agit d'une méthode couramment utilisée, et que j'utilise également depuis longtemps.

C'est assez satisfaisant pour Excel - mais qu'en est-il des autres applications?

Dans d'autres applications, je peux faire essentiellement la même chose mais nécessite d'activer la référence à la bibliothèque d'objets Excel, puis:

   If Not IsError(Excel.Application.match(...))

Cela semble stupide, cependant, et est difficile à gérer sur les fichiers distribués en raison des autorisations/du centre de confiance/etc.

J'ai essayé d'utiliser la fonction Filter () :

 If Not Ubound(Filter(varToSearch, strSearch)) = -1 Then
    'do stuff
 End If

Mais le problème avec cette approche est que Filter renvoie un tableau de correspondances partielles, plutôt qu'un tableau de correspondances exactes. (Je ne sais pas pourquoi il serait utile de renvoyer des correspondances de sous-chaîne/partielles.)

L'autre alternative est d'itérer littéralement sur chaque valeur du tableau (c'est aussi très utilisé, je pense) - ce qui semble encore plus inutile que d'appeler la fonction Match d'Excel.

For each v in vArray
   If v = strSearch Then
    ' do stuff
   End If
Next
17
David Zemens

Si nous allons parler de performances, il n'y a pas de substitut pour exécuter certains tests. D'après mon expérience, Application.Match () est jusqu'à dix fois plus lent que d'appeler une fonction qui utilise une boucle.

Sub Tester()

    Dim i As Long, b, t
    Dim arr(1 To 100) As String

    For i = 1 To 100
        arr(i) = "Value_" & i
    Next i

    t = Timer
    For i = 1 To 100000
        b = Contains(arr, "Value_50")
    Next i
    Debug.Print "Contains", Timer - t

    t = Timer
    For i = 1 To 100000
        b = Application.Match(arr, "Value_50", False)
    Next i
    Debug.Print "Match", Timer - t

End Sub


Function Contains(arr, v) As Boolean
Dim rv As Boolean, lb As Long, ub As Long, i As Long
    lb = LBound(arr)
    ub = UBound(arr)
    For i = lb To ub
        If arr(i) = v Then
            rv = True
            Exit For
        End If
    Next i
    Contains = rv
End Function

Sortie:

Contains       0.8710938 
Match          4.210938 
29
Tim Williams

"Un moyen plus efficace (par rapport à Application.Match) De trouver si une valeur de chaîne existe dans un tableau":

Je crois qu'il n'y a pas de moyen plus efficace que celui que vous utilisez, c'est-à-dire Application.Match.

Les tableaux permettent un accès efficace à n'importe quel élément si nous connaissons l'index de cet élément. Si nous voulons faire quelque chose par valeur d'élément (même en vérifiant si un élément existe), nous devons analyser tous les éléments du tableau dans le pire des cas. Par conséquent, le pire des cas nécessite des comparaisons d'éléments n, où n est la taille du tableau. Ainsi, le temps maximum dont nous avons besoin pour trouver si un élément existe est linéaire dans la taille de l'entrée, c'est-à-dire O(n). Cela s'applique à tout langage utilisant des tableaux conventionnels.

Le seul cas où nous pouvons être plus efficaces, c'est lorsque le tableau a une structure spéciale. Pour votre exemple, si les éléments du tableau sont triés (par exemple par ordre alphabétique), nous n'avons pas besoin de scanner tout le tableau: nous comparons avec l'élément du milieu, puis comparons avec la partie gauche ou droite du tableau (- recherche binaire) . Mais sans assumer aucune structure particulière, il n'y a aucun espoir ..

Le Dictionary/Collection Que vous pointez, offre un accès constant à leurs éléments (O(1)). Ce qui n'est peut-être pas très bien documenté, c'est que l'on peut aussi avoir accès à l'index aux éléments du dictionnaire (Keys and Items): l'ordre dans lequel les éléments sont entrés dans le Dictionary est conservé. Leur principal inconvénient est qu'ils utilisent plus de mémoire car deux objets sont stockés pour chaque élément.

Pour conclure, bien que If Not IsError(Excel.Application.match(...)) ait l'air idiot, c'est toujours le moyen le plus efficace (au moins en théorie). Sur les questions de permission, mes connaissances sont très limitées. Selon l'application hôte, il existe toujours des fonctions de type Find (C++ A find et find_if Par exemple).

J'espère que ça aide!

Modifier

Je voudrais ajouter quelques réflexions, après avoir lu la version modifiée du message et la réponse de Tim. Le texte ci-dessus se concentre sur la complexité temporelle théorique des différentes structures de données et ignore les problèmes de mise en œuvre. Je pense que l'esprit de la question était plutôt, "étant donné une certaine structure de données (tableau)", quelle est la manière la plus efficace en pratique de vérifier l'existence.

À cette fin, la réponse de Tim est une révélation.

La règle conventionnelle "si VBA peut le faire pour vous alors ne l'écrivez pas à nouveau vous-même" n'est pas toujours vraie. Des opérations simples telles que le bouclage et les comparaisons peuvent être plus rapides que les fonctions "d'accord" VBA. Deux liens intéressants sont ici et ici .

1
Ioannis

J'avais l'habitude de chercher une meilleure solution de remplacement. Cela devrait également fonctionner pour une recherche simple.

Pour trouver la première instance d'une chaîne, vous pouvez essayer d'utiliser ce code:

Sub find_strings_1()

Dim ArrayCh() As Variant
Dim rng As Range
Dim i As Integer

 ArrayCh = Array("a", "b", "c")

With ActiveSheet.Cells
    For i = LBound(ArrayCh) To UBound(ArrayCh)
        Set rng = .Find(What:=ArrayCh(i), _
        LookAt:=xlPart, _
        SearchOrder:=xlByColumns, _
        MatchCase:=False)

        Debug.Print rng.Address

    Next i
End With

End Sub

Si vous souhaitez trouver toutes les instances, essayez ce qui suit.

Sub find_strings_2()

Dim ArrayCh() As Variant
Dim c As Range
Dim firstAddress As String
Dim i As Integer

 ArrayCh = Array("a", "b", "c") 'strings to lookup

With ActiveSheet.Cells
    For i = LBound(ArrayCh) To UBound(ArrayCh)
        Set c = .Find(What:=ArrayCh(i), LookAt:=xlPart, LookIn:=xlValues)

        If Not c Is Nothing Then
            firstAddress = c.Address 'used later to verify if looping over the same address
            Do
                '_____
                'your code, where you do something with "c"
                'which is a range variable,
                'so you can for example get it's address:
                Debug.Print ArrayCh(i) & " " & c.Address 'example
                '_____
                Set c = .FindNext(c)

            Loop While Not c Is Nothing And c.Address <> firstAddress
        End If
    Next i
End With

End Sub

Gardez à l'esprit que s'il existe plusieurs instances de chaîne recherchée dans une cellule, elle ne renverra qu'un seul résultat en raison de la spécificité de FindNext.

Pourtant, si vous avez besoin d'un code pour remplacer les valeurs trouvées par un autre, j'utiliserais la première solution, mais vous devrez le changer un peu.

1
GrzMat