VBA a-t-il une structure de dictionnaire?

Réponses:

343

Oui.

Définissez une référence au runtime MS Scripting («Microsoft Scripting Runtime»). Selon le commentaire de @ regjo, allez dans Outils-> Références et cochez la case «Microsoft Scripting Runtime».

Fenêtre Références

Créez une instance de dictionnaire en utilisant le code ci-dessous:

Set dict = CreateObject("Scripting.Dictionary")

ou

Dim dict As New Scripting.Dictionary 

Exemple d'utilisation:

If Not dict.Exists(key) Then 
    dict.Add key, value
End If 

N'oubliez pas de définir le dictionnaire Nothinglorsque vous avez fini de l'utiliser.

Set dict = Nothing 
Blé Mitch
la source
17
Ce type de structure de données est fourni par le runtime de script, et non par VBA. Fondamentalement, VBA peut utiliser pratiquement n'importe quel type de structure de données qui lui est accessible via une interface COM.
David-W-Fenton
164
Juste pour être complet: vous devez référencer le "Microsoft Scripting Runtime" pour que cela fonctionne (allez dans Outils-> Références) et cochez sa case.
regjo
7
Euh, les collections VBA SONT saisies. Mais peut-être que nous avons une définition différente de keyed.
David-W-Fenton
8
J'utilise Excel 2010 ... mais sans la référence aux outils "Microsoft Scripting Runtime" - Réf. Faire simplement CreateObject ne fonctionne PAS. Donc, @masterjo, je pense que votre commentaire ci-dessus est faux. Sauf si je manque quelque chose .. Alors, les gars Tools -> références sont nécessaires.
ihightower
4
En tant que FYI, vous ne pouvez pas utiliser le Dim dict As New Scripting.Dictionarysans la référence. Sans la référence, vous devez utiliser la CreateObjectméthode de liaison tardive pour instancier cet objet.
David Zemens
182

VBA a l'objet de collection:

    Dim c As Collection
    Set c = New Collection
    c.Add "Data1", "Key1"
    c.Add "Data2", "Key2"
    c.Add "Data3", "Key3"
    'Insert data via key into cell A1
    Range("A1").Value = c.Item("Key2")

L' Collectionobjet effectue des recherches basées sur des clés à l'aide d'un hachage, donc c'est rapide.


Vous pouvez utiliser une Contains()fonction pour vérifier si une collection particulière contient une clé:

Public Function Contains(col As Collection, key As Variant) As Boolean
    On Error Resume Next
    col(key) ' Just try it. If it fails, Err.Number will be nonzero.
    Contains = (Err.Number = 0)
    Err.Clear
End Function

Edit 24 juin 2015 : Plus courtContains() merci à @TWiStErRob.

Edit 25 septembre 2015 : Ajouté Err.Clear()grâce à @scipilot.

Caleb Hattingh
la source
5
Bravo pour signaler que l'objet Collection intégré peut être utilisé comme dictionnaire, car la méthode Add a un argument "clé" facultatif.
Simon Tewsi
9
La mauvaise chose à propos de l'objet de collection est que vous ne pouvez pas vérifier si une clé est déjà dans la collection. Cela ne fera que lancer une erreur. C'est la grande chose, je n'aime pas les collections. (je sais qu'il existe des solutions de contournement, mais la plupart d'entre elles sont "laides")
MiVoth
5
Notez que la recherche de clés de chaîne (par exemple, c.Item ("Key2")) dans le dictionnaire VBA IS est hachée, mais la recherche par index entier (par exemple, c.Item (20)) ne l'est pas - c'est une ligne linéaire pour / suivant recherche de style et doit être évitée. Il est préférable d'utiliser les collections uniquement pour les recherches de clés de chaîne ou pour chaque itération.
Ben McIntyre
4
J'en ai trouvé un plus court Contains: On Error Resume Next_ col(key)_Contains = (Err.Number = 0)
TWiStErRob
5
Peut-être que la fonction devrait être nommée ContainsKey; une personne qui ne lit que l'invocation peut la confondre pour vérifier qu'elle contient une valeur particulière.
jpmc26
44

VBA n'a pas d'implémentation interne d'un dictionnaire, mais à partir de VBA, vous pouvez toujours utiliser l'objet dictionnaire de MS Scripting Runtime Library.

Dim d
Set d = CreateObject("Scripting.Dictionary")
d.Add "a", "aaa"
d.Add "b", "bbb"
d.Add "c", "ccc"

If d.Exists("c") Then
    MsgBox d("c")
End If
Jarmo
la source
29

Un exemple de dictionnaire supplémentaire qui est utile pour contenir la fréquence d'occurrence.

En dehors de la boucle:

Dim dict As New Scripting.dictionary
Dim MyVar as String

Dans une boucle:

'dictionary
If dict.Exists(MyVar) Then
    dict.Item(MyVar) = dict.Item(MyVar) + 1 'increment
Else
    dict.Item(MyVar) = 1 'set as 1st occurence
End If

Pour vérifier la fréquence:

Dim i As Integer
For i = 0 To dict.Count - 1 ' lower index 0 (instead of 1)
    Debug.Print dict.Items(i) & " " & dict.Keys(i)
Next i
John M
la source
1
Un lien de tutoriel supplémentaire est: kamath.com/tutorials/tut009_dictionary.asp
John M
C'était une très bonne réponse et je l'ai utilisée. Cependant, j'ai trouvé que je ne pouvais pas référencer les dict.Items (i) ou dict.Keys (i) dans la boucle comme vous le faites. J'ai dû les stocker (liste des éléments et liste des clés) dans des vars séparés avant d'entrer dans la boucle, puis utiliser ces vars pour obtenir les valeurs dont j'avais besoin. Comme - allItems = companyList.Items allKeys = companyList.Keys allItems (i) Sinon, j'obtiendrais l'erreur: "Propriété laisser la procédure non définie et propriété obtenir la procédure n'a pas renvoyé un objet" lors de la tentative d'accès à Keys (i) ou Éléments (i) dans la boucle.
raddevus
10

À partir de la réponse de cjrh , nous pouvons créer une fonction Contains ne nécessitant aucune étiquette (je n'aime pas utiliser d'étiquettes).

Public Function Contains(Col As Collection, Key As String) As Boolean
    Contains = True
    On Error Resume Next
        err.Clear
        Col (Key)
        If err.Number <> 0 Then
            Contains = False
            err.Clear
        End If
    On Error GoTo 0
End Function

Pour un de mes projets, j'ai écrit un ensemble de fonctions d'aide pour rendre un Collectioncomportement plus semblable à un Dictionary. Il permet toujours des collections récursives. Vous remarquerez que Key vient toujours en premier car il était obligatoire et plus logique dans ma mise en œuvre. J'ai aussi utilisé seulementString clés. Vous pouvez le modifier si vous le souhaitez.

Ensemble

J'ai renommé cela pour définir car il écrasera les anciennes valeurs.

Private Sub cSet(ByRef Col As Collection, Key As String, Item As Variant)
    If (cHas(Col, Key)) Then Col.Remove Key
    Col.Add Array(Key, Item), Key
End Sub

Avoir

Le errtruc est pour les objets puisque vous passeriez des objets en utilisant setet des variables sans. Je pense que vous pouvez simplement vérifier si c'est un objet, mais j'étais pressé par le temps.

Private Function cGet(ByRef Col As Collection, Key As String) As Variant
    If Not cHas(Col, Key) Then Exit Function
    On Error Resume Next
        err.Clear
        Set cGet = Col(Key)(1)
        If err.Number = 13 Then
            err.Clear
            cGet = Col(Key)(1)
        End If
    On Error GoTo 0
    If err.Number <> 0 Then Call err.raise(err.Number, err.Source, err.Description, err.HelpFile, err.HelpContext)
End Function

A

La raison de ce post ...

Public Function cHas(Col As Collection, Key As String) As Boolean
    cHas = True
    On Error Resume Next
        err.Clear
        Col (Key)
        If err.Number <> 0 Then
            cHas = False
            err.Clear
        End If
    On Error GoTo 0
End Function

Retirer

Ne jette pas s'il n'existe pas. S'assure juste qu'il est supprimé.

Private Sub cRemove(ByRef Col As Collection, Key As String)
    If cHas(Col, Key) Then Col.Remove Key
End Sub

Clés

Obtenez un tableau de clés.

Private Function cKeys(ByRef Col As Collection) As String()
    Dim Initialized As Boolean
    Dim Keys() As String

    For Each Item In Col
        If Not Initialized Then
            ReDim Preserve Keys(0)
            Keys(UBound(Keys)) = Item(0)
            Initialized = True
        Else
            ReDim Preserve Keys(UBound(Keys) + 1)
            Keys(UBound(Keys)) = Item(0)
        End If
    Next Item

    cKeys = Keys
End Function
Evan Kennedy
la source
6

Le dictionnaire d'exécution de script semble avoir un bogue qui peut ruiner votre conception à des stades avancés.

Si la valeur du dictionnaire est un tableau, vous ne pouvez pas mettre à jour les valeurs des éléments contenus dans le tableau via une référence au dictionnaire.

Kalidas
la source
6

Oui. Pour VB6 , VBA (Excel) et VB.NET

Matthew Flaschen
la source
2
Vous pouvez lire les questions plus: J'ai posé des questions sur VBA: Visual Basic pour Application, pas pour VB, pas pour VB.Net, pas pour toute autre langue.
1
fessGUID: encore une fois, vous devriez lire les réponses plus! Cette réponse peut également être utilisée pour VBA (en particulier, le premier lien).
Konrad Rudolph
5
J'admet. J'ai lu la question trop vite. Mais je lui ai dit ce qu'il devait savoir.
Matthew Flaschen
5
@Oorang, il n'y a absolument aucune preuve que VBA devienne un sous-ensemble de règles VB.NET, rétro-compatibles dans Office - imaginez essayer de convertir chaque macro Excel jamais écrite.
Richard Gadsden
2
VBA est en fait un SUPERSET de VB6. Il utilise la même DLL principale que VB6, mais ajoute ensuite toutes sortes de fonctionnalités pour les applications spécifiques dans Office.
David-W-Fenton
4

Si, pour une raison quelconque, vous ne pouvez pas installer de fonctionnalités supplémentaires sur votre Excel ou si vous ne le souhaitez pas, vous pouvez également utiliser des tableaux, au moins pour des problèmes simples. Comme WhatIsCapital vous mettez le nom du pays et la fonction vous rend son capital.

Sub arrays()
Dim WhatIsCapital As String, Country As Array, Capital As Array, Answer As String

WhatIsCapital = "Sweden"

Country = Array("UK", "Sweden", "Germany", "France")
Capital = Array("London", "Stockholm", "Berlin", "Paris")

For i = 0 To 10
    If WhatIsCapital = Country(i) Then Answer = Capital(i)
Next i

Debug.Print Answer

End Sub
user2604899
la source
1
Le concept de cette réponse est solide, mais l'exemple de code ne s'exécutera pas tel qu'il est écrit. Chaque variable a besoin de son propre Dimmot-clé, Countryet Capitaldoit être déclarée comme Variante en raison de l'utilisation de Array(), idoit être déclarée (et doit être si Option Explicitest définie), et le compteur de boucles va générer une erreur hors limite - plus sûr pour utiliser UBound(Country)pour la Tovaleur. Il convient également de noter que bien que la Array()fonction soit un raccourci utile, ce n'est pas la manière standard de déclarer des tableaux dans VBA.
jcb