Czy VBA ma strukturę słownika?

Odpowiedzi:

341

Tak.

Ustaw odwołanie do środowiska wykonawczego MS Scripting („Microsoft Scripting Runtime”). Zgodnie z komentarzem @ regjo, przejdź do Narzędzia-> Referencje i zaznacz pole „Microsoft Scripting Runtime”.

Okno referencji

Utwórz instancję słownika za pomocą poniższego kodu:

Set dict = CreateObject("Scripting.Dictionary")

lub

Dim dict As New Scripting.Dictionary 

Przykład zastosowania:

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

Nie zapomnij ustawić słownika, Nothingkiedy skończysz go używać.

Set dict = Nothing 
Mitch Pszenica
źródło
17
Ten typ struktury danych zapewnia środowisko wykonawcze skryptów, a nie VBA. Zasadniczo VBA może wykorzystywać praktycznie każdy typ struktury danych, który jest dla niego dostępny za pośrednictwem interfejsu COM.
David-W-Fenton
163
Tylko ze względu na kompletność: musisz odwołać się do „środowiska wykonawczego skryptów Microsoft”, aby to zadziałało (przejdź do Narzędzia-> Referencje) i zaznacz jego pole.
regjo
7
Uh, zbiory VBA SĄ KLUCZOWANE. Ale może mamy inną definicję keyed.
David-W-Fenton
8
Korzystam z programu Excel 2010 ... ale bez odniesienia do narzędzi „Microsoft Scripting Runtime” - Ref. Po prostu tworzenie CreateObject NIE działa. @Masterjo Myślę, że powyższy komentarz jest błędny. Chyba, że ​​coś mi umknie. Więc chłopaki Narzędzia -> referencje są wymagane.
ihightower
4
Jako FYI nie możesz używać Dim dict As New Scripting.Dictionarybez odniesienia. Bez odwołania musisz użyć metody późnego wiązania CreateObjectw celu utworzenia tego obiektu.
David Zemens
179

VBA ma obiekt kolekcji:

    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")

Na Collectionobiekt wykonuje kluczowych oparte wyszukiwań za pomocą skrótu, dlatego szybko.


Możesz użyć Contains()funkcji, aby sprawdzić, czy dana kolekcja zawiera klucz:

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

Edytuj 24 czerwca 2015 : Krótsze Contains()dzięki @TWiStErRob.

Edytuj 25 września 2015 : Dodano Err.Clear()dzięki @scipilot.

Caleb Hattingh
źródło
5
Dobrze zrobione wskazanie wbudowanego obiektu Collection może być użyte jako słownik, ponieważ metoda Add ma opcjonalny argument „key”.
Simon Tewsi
8
Złą rzeczą w obiekcie kolekcji jest to, że nie można sprawdzić, czy klucz jest już w kolekcji. Po prostu wyrzuci błąd. To wielka rzecz, nie lubię kolekcji. (wiem, że istnieją obejścia, ale większość z nich jest „brzydka”)
MiVoth,
5
Zauważ, że wyszukiwanie kluczy łańcuchowych (np. C.Item („Key2”)) w haszowanym słowniku VBA IS, ale wyszukiwanie według indeksu liczb całkowitych (np. C.Item (20)) nie jest - jest liniowe dla / next wyszukiwanie stylu i należy go unikać. Najlepiej używać kolekcji tylko do wyszukiwania kluczy ciągów lub dla każdej iteracji.
Ben McIntyre,
4
Znalazłem krótszy Contains: On Error Resume Next_ col(key)_Contains = (Err.Number = 0)
TWiStErRob
5
Być może należy nazwać funkcję ContainsKey; ktoś czytający tylko wywołanie może pomylić je ze sprawdzeniem, czy zawiera ono określoną wartość.
jpmc26
44

VBA nie ma wewnętrznej implementacji słownika, ale z VBA nadal można używać obiektu słownika z biblioteki wykonawczej MS Scripting.

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
źródło
29

Dodatkowy przykład słownika, który jest przydatny do ograniczenia częstotliwości występowania.

Poza pętlą:

Dim dict As New Scripting.dictionary
Dim MyVar as String

W pętli:

'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

Aby sprawdzić częstotliwość:

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.
źródło
1
Dodatkowy link do samouczka to: kamath.com/tutorials/tut009_dictionary.asp
John M
To była bardzo dobra odpowiedź i wykorzystałem ją. Stwierdziłem jednak, że nie mogę odwoływać się do pętli dict.Items (i) ani dict.Keys (i) w pętli, jak ty. Musiałem je przechowywać (listę przedmiotów i listę kluczy) w osobnych zmiennych wersjach przed wejściem do pętli, a następnie użyć tych zmiennych, aby uzyskać potrzebne wartości. Like - allItems = companyList.Items allKeys = companyList.Keys allItems (i) Jeśli nie, otrzymam błąd: „Nie zdefiniowano procedury pozwolenia na właściwość, a procedura uzyskania właściwości nie zwróciła obiektu” podczas próby uzyskania dostępu do kluczy (i) lub Elementy (i) w pętli.
raddevus
10

Opierając się na odpowiedzi cjrh , możemy zbudować funkcję Contains nie wymagającą żadnych etykiet (nie lubię używać etykiet).

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

Dla mojego projektu napisałem zestaw funkcji pomocniczych, aby Collectionbardziej zachowywać się jak Dictionary. Nadal pozwala na kolekcje rekurencyjne. Zauważysz, że klucz zawsze jest najważniejszy, ponieważ był obowiązkowy i miał sens w mojej implementacji. Użyłem także tylko Stringkluczy. Możesz to zmienić, jeśli chcesz.

Zestaw

Zmieniłem nazwę, aby ustawić, ponieważ spowoduje to zastąpienie starych wartości.

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

Dostać

errRzeczy jest dla obiektów gdyż przechodzą obiekty przy użyciu seti bez zmiennych. Myślę, że możesz po prostu sprawdzić, czy to obiekt, ale byłem zmuszony do czasu.

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

Ma

Powód tego postu ...

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

Usunąć

Nie rzuca, jeśli nie istnieje. Tylko upewnia się, że został usunięty.

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

Klucze

Zdobądź tablicę kluczy.

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
źródło
6

Wydaje się, że słownik środowiska wykonawczego skryptów zawiera błąd, który może zepsuć projekt na zaawansowanym etapie.

Jeśli wartością słownika jest tablica, nie można aktualizować wartości elementów zawartych w tablicy przez odniesienie do słownika.

Kalidas
źródło
6

Tak. Dla VB6 , VBA (Excel) i VB.NET

Matthew Flaschen
źródło
2
Możesz przeczytać jeszcze jedno pytanie: Zapytałem o VBA: Visual Basic for Application, nie dla VB, nie dla VB.Net, nie dla żadnego innego języka.
1
fessGUID: ponownie powinieneś przeczytać więcej odpowiedzi! Tej odpowiedzi można również użyć w przypadku VBA (w szczególności pierwszego linku).
Konrad Rudolph
5
Przyznaję. Zbyt szybko przeczytałem pytanie. Ale powiedziałem mu, co powinien wiedzieć.
Matthew Flaschen
5
@Oorang, absolutnie nie ma dowodów na to, że VBA stanie się podzbiorem VB.NET, reguł backcompat w Office - wyobraź sobie, że próbujesz przekonwertować każde makro Excela, jakie kiedykolwiek napisano.
Richard Gadsden
2
VBA jest w rzeczywistości SUPERSETEM VB6. Korzysta z tej samej podstawowej biblioteki DLL co VB6, ale dodaje różnorodne funkcje dla określonych aplikacji w pakiecie Office.
David-W-Fenton
4

Jeśli z jakiegoś powodu nie możesz zainstalować dodatkowych funkcji w programie Excel lub nie chcesz, możesz również użyć tablic, przynajmniej w przypadku prostych problemów. Jako WhatIsCapital podajesz nazwę kraju, a funkcja zwraca ci swój kapitał.

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
użytkownik2604899
źródło
1
Koncepcja tej odpowiedzi jest solidna, ale przykładowy kod nie będzie działał zgodnie z opisem. Każda zmienna potrzebuje własnego Dimsłowa kluczowego Countryi Capitalmusi zostać zadeklarowana jako Warianty ze względu na użycie Array(), ipowinna zostać zadeklarowana (i musi być Option Explicitustawiona, jeśli jest ustawiona), a licznik pętli wyrzuci błąd spoza zakresu - bezpieczniej użyj UBound(Country)dla Towartości. Warto również zauważyć, że chociaż Array()funkcja jest przydatnym skrótem, nie jest to standardowy sposób deklarowania tablic w VBA.
jcb