Apakah VBA memiliki Struktur Kamus?

Jawaban:

342

Iya.

Tetapkan referensi untuk runtime MS Scripting ('Microsoft Scripting Runtime'). Sesuai komentar @ regjo, pergi ke Tools-> Referensi dan centang kotak untuk 'Microsoft Scripting Runtime'.

Jendela Referensi

Buat instance kamus menggunakan kode di bawah ini:

Set dict = CreateObject("Scripting.Dictionary")

atau

Dim dict As New Scripting.Dictionary 

Contoh penggunaan:

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

Jangan lupa mengatur kamus Nothingsaat Anda selesai menggunakannya.

Set dict = Nothing 
Mitch Wheat
sumber
17
Tipe struktur data ini disediakan oleh runtime scripting, bukan oleh VBA. Pada dasarnya, VBA dapat menggunakan hampir semua jenis struktur data yang dapat diakses melalui antarmuka COM.
David-W-Fenton
163
Hanya demi kelengkapan: Anda perlu merujuk "Microsoft Scripting Runtime" agar ini berfungsi (buka Tools-> Referensi) dan centang kotaknya.
regjo
7
Eh, koleksi VBA dikunci. Tapi mungkin kita punya definisi berbeda keyed.
David-W-Fenton
8
Saya menggunakan Excel 2010 ... tetapi tanpa referensi ke "Microsoft Scripting Runtime" Tools - Ref .. Hanya melakukan CreateObject TIDAK berfungsi. Jadi, @masterjo saya pikir komentar Anda di atas salah. Kecuali saya kehilangan sesuatu .. Jadi, Alat guys -> referensi diperlukan.
ihightower
4
Sebagai FYI, Anda tidak dapat menggunakan Dim dict As New Scripting.Dictionarytanpa referensi. Tanpa referensi, Anda harus menggunakan CreateObjectmetode pengikatan akhir dari instantiate objek ini.
David Zemens
181

VBA memiliki objek koleksi:

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

The Collectionobjek Melakukan pencarian berbasis kunci menggunakan hash sehingga sangat cepat.


Anda dapat menggunakan Contains()fungsi untuk memeriksa apakah koleksi tertentu berisi kunci:

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

Sunting 24 Juni 2015 : Contains()Terima kasih lebih pendek untuk @TWiStErRob.

Sunting 25 September 2015 : Ditambahkan Err.Clear()berkat @scipilot.

Caleb Hattingh
sumber
5
Dilakukan dengan baik untuk menunjukkan objek Koleksi bawaan dapat digunakan sebagai kamus, karena metode Tambahkan memiliki argumen "kunci" opsional.
Simon Tewsi
8
Hal buruk tentang objek koleksi adalah, bahwa Anda tidak dapat memeriksa apakah kunci sudah ada dalam koleksi. Itu hanya akan melempar kesalahan. Itu hal besar, saya tidak suka tentang koleksi. (saya tahu, bahwa ada solusi, tetapi kebanyakan dari mereka "jelek")
MiVoth
5
Perhatikan bahwa pencarian kunci string (mis. C.Item ("Key2")) dalam VBA Dictionary IS hash, tetapi pencarian dengan indeks integer (mis. C.Item (20)) tidak - itu linear untuk / selanjutnya pencarian gaya dan harus dihindari. Terbaik untuk menggunakan koleksi hanya untuk pencarian kunci string atau untuk setiap iterasi.
Ben McIntyre
4
Saya menemukan yang lebih pendek Contains: On Error Resume Next_ col(key)_Contains = (Err.Number = 0)
TWiStErRob
5
Mungkin fungsinya harus dinamai ContainsKey; seseorang yang hanya membaca doa dapat mengacaukannya karena memeriksa bahwa itu mengandung nilai tertentu.
jpmc26
44

VBA tidak memiliki implementasi internal kamus, tetapi dari VBA Anda masih dapat menggunakan objek kamus dari 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
sumber
29

Contoh kamus tambahan yang berguna untuk memuat frekuensi kemunculan.

Di luar lingkaran:

Dim dict As New Scripting.dictionary
Dim MyVar as String

Dalam satu lingkaran:

'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

Untuk memeriksa frekuensi:

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
sumber
1
Tautan tutorial tambahan adalah: kamath.com/tutorials/tut009_dictionary.asp
John M
Ini adalah jawaban yang sangat bagus dan saya menggunakannya. Namun, saya menemukan bahwa saya tidak dapat mereferensikan dict.Items (i) atau dict.Keys (i) di loop seperti yang Anda lakukan. Saya harus menyimpannya (daftar item dan daftar kunci) di vars terpisah sebelum memasuki loop dan kemudian menggunakan vars itu untuk mendapatkan nilai yang saya butuhkan. Seperti - allItems = companyList.Items allKeys = companyList.Keys allItems (i) Jika tidak, saya akan mendapatkan kesalahan: "Prosedur properti let tidak didefinisikan dan properti mendapatkan prosedur tidak mengembalikan objek" ketika mencoba mengakses Kunci (i) atau Item (i) dalam loop.
raddevus
10

Membangun jawaban cjrh , kita dapat membangun fungsi Contains yang tidak memerlukan label (saya tidak suka menggunakan label).

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

Untuk proyek saya, saya menulis satu set fungsi pembantu untuk membuat Collectionperilaku lebih seperti a Dictionary. Itu masih memungkinkan koleksi rekursif. Anda akan melihat Key selalu didahulukan karena itu wajib dan lebih masuk akal dalam implementasi saya. Saya juga hanya menggunakan Stringkunci. Anda dapat mengubahnya kembali jika Anda mau.

Set

Saya mengganti nama ini untuk ditetapkan karena akan menimpa nilai lama.

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

Dapatkan

The errhal ini untuk benda karena Anda akan melewati objek menggunakan setdan variabel tanpa. Saya pikir Anda hanya dapat memeriksa apakah itu objek, tapi saya terdesak waktu.

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

Memiliki

Alasan posting ini ...

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

Menghapus

Tidak melempar jika tidak ada. Pastikan itu dihapus.

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

Kunci

Dapatkan berbagai kunci.

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
sumber
6

Kamus runtime scripting tampaknya memiliki bug yang dapat merusak desain Anda pada tahap lanjut.

Jika nilai kamus adalah sebuah array, Anda tidak dapat memperbarui nilai elemen yang terkandung dalam array melalui referensi ke kamus.

Kalidas
sumber
6

Iya. Untuk VB6 , VBA (Excel), dan VB.NET

Matthew Flaschen
sumber
2
Anda dapat membaca pertanyaan lebih lanjut: Saya sudah bertanya tentang VBA: Visual Basic for Application, bukan untuk VB, bukan untuk VB.Net, bukan untuk bahasa lain.
1
fessGUID: sekali lagi, Anda harus membaca lebih banyak jawaban! Jawaban ini juga dapat digunakan untuk VBA (khususnya, tautan pertama).
Konrad Rudolph
5
Aku akui. Saya membaca pertanyaan terlalu cepat. Tapi aku memberitahunya apa yang perlu dia ketahui.
Matthew Flaschen
5
@Oorang, sama sekali tidak ada bukti VBA menjadi subset dari VB.NET, aturan backcompat di Office - bayangkan mencoba mengonversi setiap makro Excel yang pernah ditulis.
Richard Gadsden
2
VBA sebenarnya adalah SUPERSET dari VB6. Ini menggunakan DLL inti yang sama seperti VB6, tetapi kemudian menambahkan segala macam fungsionalitas untuk aplikasi spesifik di Office.
David-W-Fenton
4

Jika karena alasan apa pun, Anda tidak dapat menginstal fitur tambahan ke Excel Anda atau tidak mau, Anda dapat menggunakan array juga, setidaknya untuk masalah sederhana. Sebagai WhatIsCapital, Anda memasukkan nama negara dan fungsinya mengembalikan modal Anda.

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
pengguna2604899
sumber
1
Konsep jawaban ini masuk akal, tetapi kode sampel tidak akan berjalan seperti yang tertulis. Setiap variabel memerlukan Dimkata kunci sendiri , Countrydan Capitalperlu dinyatakan sebagai Varian karena penggunaan Array(), iharus dinyatakan (dan harus jika Option Explicitdiatur), dan penghitung lingkaran akan membuang kesalahan yang tidak terbatas - lebih aman untuk gunakan UBound(Country)untuk Tonilai. Mungkin juga perlu dicatat bahwa sementara Array()fungsi adalah jalan pintas yang bermanfaat, itu bukan cara standar untuk mendeklarasikan array di VBA.
jcb