VBA是否具有字典结构?


Answers:


341

是。

设置对MS脚本运行时的引用(“ Microsoft脚本运行时”)。按照@regjo的注释,转到“工具”->“参考”,然后选中“ Microsoft脚本运行时”框。

参考窗口

使用以下代码创建字典实例:

Set dict = CreateObject("Scripting.Dictionary")

要么

Dim dict As New Scripting.Dictionary 

使用示例:

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

Nothing使用完字典后,请不要忘记将其设置为。

Set dict = Nothing 

17
此数据结构类型由脚本运行时提供,而不是由VBA提供。基本上,VBA实际上可以使用可通过COM接口访问的任何数据结构类型。
戴维·芬顿

163
仅出于完整性考虑:您需要引用“ Microsoft Scripting Runtime”以使其工作(转到“工具”->“参考”)并选中其复选框。
regjo

7
嗯,VBA集合被锁定。但也许我们对的定义不同keyed
戴维·芬顿

8
我正在使用Excel 2010 ...,但未引用“ Microsoft Scripting Runtime”工具-参考。仅执行CreateObject不起作用。因此,@ masterjo我认为您在上面的评论是错误的。除非我缺少任何东西。所以,需要工具->引用。
ihightower 2012年

4
仅供参考,没有参考就不能使用Dim dict As New Scripting.Dictionary。如果没有引用,则必须使用CreateObject实例化此对象的后期绑定方法。
David Zemens 2013年

179

VBA具有收集对象:

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

Collection对象使用哈希执行基于键的查找,因此速度很快。


您可以使用Contains()函数来检查特定集合是否包含键:

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

编辑2015年6月24日Contains()感谢@TWiStErRob。

编辑2015年9月25日Err.Clear()感谢@scipilot。


5
指出内置Collection对象做得很好的方法可以用作字典,因为Add方法具有可选的“ key”参数。
西蒙·图西

8
关于集合对象的坏事是,您无法检查集合中是否已存在密钥。只会抛出一个错误。那是大事,我不喜欢收藏。(我知道,有解决方法,但大多数都是“丑陋的”)
MiVoth 2013年

5
请注意,在VBA词典中对字符串键(例如c.Item(“ Key2”))的查询是经过哈希处理的,但不是通过整数索引(例如c.Item(20))进行的查找-它是线性的for / next样式搜索,应避免使用。最好仅将集合用于字符串键查找或每次迭代。
Ben McIntyre

4
我发现了一个较短的ContainsOn Error Resume Next_ col(key)_Contains = (Err.Number = 0)
TWiStErRob 2015年

5
也许函数应该被命名ContainsKey; 仅阅读该调用的人可能会混淆它以检查它是否包含特定值。
jpmc26

44

VBA没有字典的内部实现,但是从VBA中,您仍然可以使用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

另一个字典示例,对于包含发生频率很有用。

循环外:

Dim dict As New Scripting.dictionary
Dim MyVar as String

循环内:

'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

要检查频率:

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
附加的教程链接为:kamath.com/tutorials/tut009_dictionary.asp
John M

这是一个很好的答案,我使用了它。但是,我发现无法像您那样在循环中引用dict.Items(i)或dict.Keys(i)。在进入循环之前,我必须将那些(项列表和键列表)存储在单独的变量中,然后使用这些变量来获得所需的值。像-allItems = companyList.Items allKeys = companyList.Keys allItems(i)如果没有,我将收到错误:尝试访问Keys(i)时出现“未定义属性让过程且属性get过程未返回对象”循环中的项目(i)。
raddevus

10

建立cjrh的答案,我们可以构建一个不需要标签的Contains函数(我不喜欢使用标签)。

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

对于我的一个项目,我编写了一组辅助函数来使Collection行为更像Dictionary。它仍然允许递归集合。您会注意到Key始终是第一位的,因为它是强制性的,在我的实现中更有意义。我也只使用了String键。您可以根据需要将其更改回去。

我将其重命名为set,因为它将覆盖旧值。

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

得到

这些err东西是用于对象的,因为您将使用set和而不使用对象来传递对象。我认为您可以检查它是否是一个对象,但是我被迫等待时间。

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

这篇文章的原因...

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

去掉

如果不存在,则不抛出。只要确保将其删除即可。

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

按键

获取一个键数组。

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

脚本运行时词典似乎存在一个错误,该错误可能在高级阶段破坏您的设计。

如果字典值是一个数组,则无法通过引用字典来更新数组中包含的元素的值。


6

是。对于VB6,VBA(Excel)和VB.NET


2
您可以阅读更多的问题:我问过有关VBA的问题:Visual Basic for Application,而不是VB,不是VB.Net,不是任何其他语言。

1
fessGUID:再说一遍,您应该阅读更多答案!此答案也可以用于VBA(尤其是第一个链接)。
康拉德·鲁道夫

5
我承认。我读得太快了。但是我确实告诉他他需要知道什么。
马修·弗莱申

5
@Oorang,绝对没有证据表明VBA成为VB.NET,Office中的反向兼容规则的子集-试着尝试转换曾经编写的每个Excel宏。
理查德·加兹登

2
VBA实际上是VB6的SUPERSET。它使用与VB6相同的核心DLL,但随后为Office中的特定应用程序添加了各种功能。
戴维·芬顿

4

如果由于某种原因您不能或不想将其他功能安装到Excel中,则也可以使用数组,至少对于简单的问题而言。作为WhatIsCapital,您输入国家的名称,该函数返回您的资本。

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
这个答案的概念是合理的,但是示例代码不会像编写的那样运行。每个变量都需要自己Dim的关键字,Country并且Capital需要被声明为变体由于使用的Array()i应当声明(而且必须是,如果Option Explicit是一套),以及循环计数器会抛出一个出界错误的-更安全使用UBound(Country)To价值。还可能值得注意的是,虽然该Array()函数是有用的快捷方式,但它不是在VBA中声明数组的标准方法。
jcb

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.