VBA ha la struttura del dizionario?


Risposte:


342

Sì.

Impostare un riferimento al runtime di MS Scripting ("Microsoft Scripting Runtime"). Come da commento di @ regjo, vai su Strumenti-> Riferimenti e seleziona la casella "Microsoft Scripting Runtime".

Finestra dei riferimenti

Crea un'istanza di dizionario usando il codice seguente:

Set dict = CreateObject("Scripting.Dictionary")

o

Dim dict As New Scripting.Dictionary 

Esempio di utilizzo:

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

Non dimenticare di impostare il dizionario su Nothingquando hai finito di usarlo.

Set dict = Nothing 

17
Questo tipo di struttura di dati è fornito dal runtime di scripting, non da VBA. Fondamentalmente, VBA può usare praticamente qualsiasi tipo di struttura di dati accessibile tramite un'interfaccia COM.
David-W-Fenton,

163
Solo per completezza: è necessario fare riferimento a "Microsoft Scripting Runtime" affinché funzioni (vai su Strumenti-> Riferimenti) e seleziona la relativa casella.
Regjo,

7
Le collezioni VBA sono codificate. Ma forse abbiamo una diversa definizione di keyed.
David-W-Fenton,

8
Sto usando Excel 2010 ... ma senza il riferimento agli strumenti "Microsoft Scripting Runtime" - Rif. Basta fare CreateObject NON funziona. Quindi, @masterjo, penso che il tuo commento sopra sia sbagliato. A meno che non mi manchi qualcosa ... Quindi, ragazzi Strumenti -> sono richiesti riferimenti.
ihightower,

4
Come FYI, non puoi usare il Dim dict As New Scripting.Dictionarysenza il riferimento. Senza il riferimento, è necessario utilizzare il CreateObjectmetodo di associazione tardiva per creare un'istanza di questo oggetto.
David Zemens,

181

VBA ha l'oggetto di raccolta:

    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' Collectionoggetto esegue ricerche basate su chiave utilizzando un hash, quindi è veloce.


È possibile utilizzare una Contains()funzione per verificare se una determinata raccolta contiene una chiave:

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

Modifica 24 giugno 2015 : Contains()abbreviato grazie a @TWiStErRob.

Modifica il 25 settembre 2015 : aggiunto Err.Clear()grazie a @scipilot.


5
Ben fatto per sottolineare l'oggetto Collection incorporato può essere usato come dizionario, poiché il metodo Add ha un argomento "chiave" opzionale.
Simon Tewsi,

8
L'aspetto negativo dell'oggetto raccolta è che non è possibile verificare se una chiave è già presente nella raccolta. Emetterà solo un errore. Questa è la cosa importante, non mi piacciono le collezioni. (lo so, ci sono soluzioni alternative, ma la maggior parte sono "brutti")
MiVoth,

5
Si noti che la ricerca di chiavi di stringa (ad es. C.Item ("Key2")) nel dizionario VBA È hash, ma la ricerca per indice intero (ad es. C.Item (20)) non è - è un lineare per / successivo stile di ricerca e dovrebbe essere evitato. È preferibile utilizzare raccolte solo per le ricerche di chiavi stringa o per ogni iterazione.
Ben McIntyre,

4
Ne ho trovato uno più corto Contains: On Error Resume Next_ col(key)_Contains = (Err.Number = 0)
TWiStErRob

5
Forse la funzione dovrebbe essere nominata ContainsKey; qualcuno che legge solo l'invocazione può confonderlo per verificare che contenga un valore particolare.
jpmc26,

44

VBA non ha un'implementazione interna di un dizionario, ma da VBA è ancora possibile utilizzare l'oggetto dizionario da 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

29

Un esempio di dizionario aggiuntivo utile per contenere la frequenza di occorrenza.

Al di fuori del ciclo:

Dim dict As New Scripting.dictionary
Dim MyVar as String

All'interno di un ciclo:

'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

Per verificare la frequenza:

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

1
Un ulteriore link tutorial è: kamath.com/tutorials/tut009_dictionary.asp
John M

Questa è stata un'ottima risposta e l'ho usata. Tuttavia, ho scoperto che non potevo fare riferimento a dict.Items (i) o dict.Keys (i) nel loop come fai tu. Ho dovuto memorizzarli (elenco degli elementi e elenco delle chiavi) in vari var prima di entrare nel ciclo e quindi usare quei vari per arrivare ai valori di cui avevo bisogno. Like - allItems = companyList.Items allKeys = companyList.Keys allItems (i) In caso contrario, visualizzerei l'errore: "La proprietà lascia la procedura non definita e la proprietà get procedure non ha restituito un oggetto" quando si tenta di accedere a Keys (i) o Elementi (i) nel ciclo.
Raddevus,

10

Costruendo la risposta di cjrh , possiamo creare una funzione Contains che non richiede etichette (non mi piace usare le etichette).

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

Per un mio progetto, ho scritto una serie di funzioni di supporto per rendere un Collectioncomportamento più simile a un Dictionary. Permette ancora raccolte ricorsive. Noterai che Key viene sempre per primo perché era obbligatorio e aveva più senso nella mia implementazione. Ho anche usato solo le Stringchiavi. Puoi cambiarlo se vuoi.

Impostato

Ho rinominato questo per impostare perché sovrascriverà i vecchi valori.

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

Ottenere

La errroba è per gli oggetti poiché passeresti usando oggetti sete senza variabili. Penso che puoi solo verificare se si tratta di un oggetto, ma mi è stato premuto per tempo.

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

ha

Il motivo di questo 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

Rimuovere

Non getta se non esiste. Assicurati solo che sia stato rimosso.

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

chiavi

Ottieni una serie di chiavi.

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

6

Il dizionario di runtime di script sembra avere un bug che può rovinare il tuo progetto in fasi avanzate.

Se il valore del dizionario è un array, non è possibile aggiornare i valori degli elementi contenuti nell'array mediante un riferimento al dizionario.


6

Sì. Per VB6 , VBA (Excel) e VB.NET


2
Puoi leggere altre domande: ho fatto domande su VBA: Visual Basic, Application, non per VB, non per VB.Net, non per altre lingue.

1
fessGUID: poi, dovresti leggere di più le risposte! Questa risposta può essere utilizzata anche per VBA (in particolare, il primo collegamento).
Konrad Rudolph,

5
Lo ammetto. Ho letto la domanda troppo in fretta. Ma gli ho detto quello che doveva sapere.
Matthew Flaschen,

5
@Oorang, non c'è assolutamente alcuna prova che VBA diventi un sottoinsieme di VB.NET, regole di backcompat in Office - immagina di provare a convertire ogni macro di Excel mai scritta.
Richard Gadsden,

2
VBA è in realtà un SUPERSET di VB6. Utilizza la stessa DLL principale di VB6, ma aggiunge quindi tutti i tipi di funzionalità per le applicazioni specifiche in Office.
David-W-Fenton,

4

Se per qualsiasi motivo, non è possibile installare funzionalità aggiuntive in Excel o non si desidera, è possibile utilizzare anche array, almeno per problemi semplici. Come WhatIsCapital inserisci il nome del paese e la funzione ti restituisce la sua capitale.

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

1
Il concetto di questa risposta è valido, ma il codice di esempio non verrà eseguito come scritto. Ogni variabile ha bisogno della propria Dimparola chiave Countrye Capitaldeve essere dichiarata come Varianti a causa dell'uso di Array(), ideve essere dichiarata (e deve essere se Option Explicitè impostata), e il contatore del ciclo genererà un errore fuori limite - più sicuro per utilizzare UBound(Country)per il Tovalore. Inoltre, forse vale la pena notare che mentre la Array()funzione è un collegamento utile, non è il modo standard per dichiarare le matrici in VBA.
jcb,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.