VBA có cấu trúc từ điển không?


Câu trả lời:


341

Đúng.

Đặt tham chiếu đến thời gian chạy MS Scripting ('Microsoft Scripting Runtime'). Theo nhận xét của @ regjo, hãy chuyển đến Công cụ-> Tài liệu tham khảo và đánh dấu vào ô cho 'Microsoft Scripting Runtime'.

Cửa sổ tham khảo

Tạo một ví dụ từ điển bằng cách sử dụng mã dưới đây:

Set dict = CreateObject("Scripting.Dictionary")

hoặc là

Dim dict As New Scripting.Dictionary 

Ví dụ sử dụng:

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

Đừng quên đặt từ điển Nothingkhi bạn sử dụng xong.

Set dict = Nothing 

17
Kiểu cấu trúc dữ liệu này được cung cấp bởi thời gian chạy tập lệnh, không phải bởi VBA. Về cơ bản, VBA thực tế có thể sử dụng bất kỳ loại cấu trúc dữ liệu nào có thể truy cập được thông qua giao diện COM.
David-W-Fenton

163
Chỉ vì mục đích hoàn chỉnh: bạn cần tham khảo "Microsoft Scripting Runtime" để làm việc này (đi tới Công cụ-> Tài liệu tham khảo) và đánh dấu vào ô của nó.
regjo

7
Uh, bộ sưu tập VBA được khóa. Nhưng có lẽ chúng ta có một định nghĩa khác về keyed.
David-W-Fenton

8
Tôi đang sử dụng Excel 2010 ... nhưng không có tham chiếu đến Công cụ "Microsoft Scripting Runtime" - Tham khảo .. Chỉ làm CreatObject không hoạt động. Vì vậy, @masterjo Tôi nghĩ nhận xét của bạn ở trên là sai. Trừ khi tôi đang thiếu một cái gì đó .. Vì vậy, các công cụ của mọi người -> cần có tài liệu tham khảo.
ihightower

4
Là một FYI, bạn không thể sử dụng Dim dict As New Scripting.Dictionarymà không có tài liệu tham khảo. Không có tài liệu tham khảo, bạn phải sử dụng CreateObjectphương pháp liên kết muộn để khởi tạo đối tượng này.
David Zemens

179

VBA có đối tượng bộ sưu tập:

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

Đối Collectiontượng thực hiện tra cứu dựa trên khóa bằng cách sử dụng hàm băm để nhanh chóng.


Bạn có thể sử dụng một Contains()chức năng để kiểm tra xem một bộ sưu tập cụ thể có chứa khóa hay không:

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

Chỉnh sửa ngày 24 tháng 6 năm 2015 : Ngắn hơn Contains()nhờ @TWiStErRob.

Chỉnh sửa ngày 25 tháng 9 năm 2015 : Đã thêm Err.Clear()nhờ @scipilot.


5
Hoàn thành tốt việc chỉ ra đối tượng Bộ sưu tập tích hợp có thể được sử dụng làm từ điển, vì phương thức Thêm có đối số "khóa" tùy chọn.
Simon Tewsi

8
Điều tệ hại về đối tượng bộ sưu tập là, bạn không thể kiểm tra xem một khóa đã có trong bộ sưu tập chưa. Nó sẽ chỉ ném một lỗi. Đó là điều quan trọng, tôi không thích về các bộ sưu tập. (tôi biết rằng có cách giải quyết, nhưng hầu hết trong số họ là "xấu xí")
MiVoth

5
Lưu ý rằng việc tra cứu các khóa chuỗi (vd tìm kiếm phong cách và nên tránh. Tốt nhất để sử dụng các bộ sưu tập chỉ để tra cứu khóa chuỗi hoặc cho mỗi lần lặp.
Ben McIntyre

4
Tôi tìm thấy một đoạn ngắn hơn Contains: On Error Resume Next_ col(key)_Contains = (Err.Number = 0)
TWiStErRob

5
Có lẽ chức năng nên được đặt tên ContainsKey; ai đó chỉ đọc lời mời có thể nhầm lẫn nó để kiểm tra xem nó có chứa một giá trị cụ thể không.
jpmc26

44

VBA không có triển khai nội bộ từ điển, nhưng từ VBA bạn vẫn có thể sử dụng đối tượng từ điển từ Thư viện thời gian chạy 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

29

Một ví dụ từ điển bổ sung hữu ích cho việc chứa tần suất xuất hiện.

Bên ngoài vòng lặp:

Dim dict As New Scripting.dictionary
Dim MyVar as String

Trong một vòng lặp:

'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

Để kiểm tra tần suất:

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
Một liên kết hướng dẫn bổ sung là: kamath.com/tutorials/tut009_dipedia.asp
John M

Đây là một câu trả lời rất tốt và tôi đã sử dụng nó. Tuy nhiên, tôi thấy rằng tôi không thể tham chiếu dict.Items (i) hoặc dict.Keys (i) trong vòng lặp như bạn làm. Tôi đã phải lưu trữ những thứ đó (danh sách vật phẩm và danh sách khóa) trong các lọ riêng biệt trước khi vào vòng lặp và sau đó sử dụng các lọ đó để đến các giá trị tôi cần. Giống như - allItems = companyList.Items allKeys = companyList.Keys allItems (i) Nếu không, tôi sẽ nhận được lỗi: "Thuộc tính cho phép thủ tục không được xác định và thủ tục nhận thuộc tính không trả về một đối tượng" khi cố gắng truy cập Khóa (i) hoặc Các mục (i) trong vòng lặp.
raddevus

10

Xây dựng câu trả lời của cjrh , chúng ta có thể xây dựng hàm Chứa không yêu cầu nhãn (Tôi không thích sử dụng nhãn).

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

Đối với một dự án của tôi, tôi đã viết một tập hợp các hàm trợ giúp để thực hiện một Collectionhành vi giống như a Dictionary. Nó vẫn cho phép các bộ sưu tập đệ quy. Bạn sẽ nhận thấy Key luôn xuất hiện đầu tiên vì nó là bắt buộc và có ý nghĩa hơn trong việc triển khai của tôi. Tôi cũng chỉ sử dụng Stringchìa khóa. Bạn có thể thay đổi nó trở lại nếu bạn muốn.

Bộ

Tôi đã đổi tên này để đặt vì nó sẽ ghi đè lên các giá trị cũ.

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

Được

Các errcông cụ dành cho các đối tượng vì bạn sẽ chuyển các đối tượng sử dụng setvà các biến không có. Tôi nghĩ rằng bạn chỉ có thể kiểm tra nếu nó là một đối tượng, nhưng tôi đã bị ép thời gian.

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

Lý do cho bài viết này ...

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

Tẩy

Không ném nếu nó không tồn tại. Chỉ cần chắc chắn rằng nó được gỡ bỏ.

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

Chìa khóa

Lấy một mảng các phím.

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

Từ điển thời gian chạy kịch bản dường như có một lỗi có thể làm hỏng thiết kế của bạn ở các giai đoạn nâng cao.

Nếu giá trị từ điển là một mảng, bạn không thể cập nhật giá trị của các phần tử có trong mảng thông qua tham chiếu đến từ điển.


6

Đúng. Đối với VB6 , VBA (Excel) và VB.NET


2
Bạn có thể đọc thêm câu hỏi: Tôi đã hỏi về VBA: Visual Basic for Application, không dành cho VB, không dành cho VB.Net, không cho bất kỳ ngôn ngữ nào khác.

1
fessGUID: sau đó một lần nữa, bạn nên đọc câu trả lời nhiều hơn! Câu trả lời này cũng có thể được sử dụng cho VBA (đặc biệt là liên kết đầu tiên).
Konrad Rudolph

5
Tôi thừa nhận. Tôi đọc câu hỏi quá nhanh. Nhưng tôi đã nói với anh ấy những gì anh ấy cần biết.
Matthew Flaschen

5
@Oorang, hoàn toàn không có bằng chứng nào về việc VBA trở thành một tập hợp con của VB.NET, các quy tắc backcompat trong Office - hãy tưởng tượng bạn đang cố gắng chuyển đổi mọi macro Excel từng được viết.
Richard Gadsden

2
VBA thực sự là một SUPERSET của VB6. Nó sử dụng DLL lõi giống như VB6, nhưng sau đó thêm vào tất cả các loại chức năng cho các ứng dụng cụ thể trong Office.
David-W-Fenton

4

Nếu vì bất kỳ lý do gì, bạn không thể cài đặt các tính năng bổ sung cho Excel của mình hoặc không muốn, bạn cũng có thể sử dụng các mảng, ít nhất là cho các sự cố đơn giản. Như WhatIsCapital, bạn đặt tên quốc gia và chức năng trả lại cho bạn vốn của quốc gia đó.

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
Khái niệm của câu trả lời này là âm thanh, nhưng mã mẫu sẽ không chạy như được viết. Mỗi biến cần Dimtừ khóa riêng CountryCapitalcần được khai báo là Biến do sử dụng Array(), inên được khai báo (và phải là nếu Option Explicitđược đặt) và bộ đếm vòng lặp sẽ đưa ra một lỗi ràng buộc - an toàn hơn cho sử dụng UBound(Country)cho Togiá trị. Cũng có thể đáng lưu ý rằng mặc dù Array()chức năng là một phím tắt hữu ích, nhưng đó không phải là cách tiêu chuẩn để khai báo mảng trong VBA.
jcb

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.