VBA有字典结构吗?

292

VBA有字典结构吗?就像键<>值数组一样?

11个回答

365

是的。

设置对MS Scripting runtime ('Microsoft Scripting Runtime')的引用。根据@regjo的评论,转到工具-> 引用并选中'Microsoft Scripting Runtime'的框。

References Window

使用以下代码创建一个字典实例:
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 

21
这种数据结构类型是由脚本运行时提供的,而不是由VBA提供的。基本上,VBA可以使用通过COM接口可访问的几乎任何数据结构类型。 - David-W-Fenton
172
为了完整起见:要使此代码正常工作,您需要引用 "Microsoft Scripting Runtime"(转到工具-> 引用),并选中它的复选框。 - regjo
7
嗯,VBA集合是有键的。但也许我们对“有键”的定义不同。 - David-W-Fenton
9
我正在使用Excel 2010,但没有“Microsoft Scripting Runtime”工具的参照。仅仅使用CreateObject是行不通的。因此,@masterjo,我认为你上面的评论是错误的。除非我漏掉了什么...所以,各位需要使用“工具”->“引用”。 - ihightower
4
提醒一下,如果没有引用,你无法使用Dim dict As New Scripting.Dictionary。没有引用的情况下,你需要使用后期绑定的CreateObject方法来实例化这个对象。 - David Zemens
显示剩余11条评论

207

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日更新: 感谢@TWiStErRob,Contains()变得更短了。

2015年9月25日更新: 感谢@scipilot,添加了Err.Clear()


5
感谢您指出内置的Collection对象可用作字典,因为Add方法有一个可选的“key”参数。 - Simon Elms
12
关于集合对象的不好之处是,你不能检查一个键是否已经存在于集合中。它会抛出错误。这就是我不喜欢集合的主要原因。(我知道有解决方法,但大部分都很“丑陋”) - MiVoth
5
请注意,VBA词典中字符串键(例如c.Item("Key2"))的查找是经过哈希处理的,但整数索引(例如c.Item(20))的查找不是 - 它是一种线性的for/next样式搜索,应该避免使用。最好只在需要使用字符串键进行查询或进行迭代时使用集合。 - Ben McIntyre
4
我发现了更短的 Contains 写法:On Error Resume Next _ col(key) _ Contains = (Err.Number = 0)。它的意思是,如果 col(key) 存在于集合中,则 Contains 返回 True;否则返回 False。 - TWiStErRob
7
也许这个函数应该被命名为ContainsKey; 只读取调用的人可能会将其与检查它是否包含特定值混淆。 - jpmc26
显示剩余7条评论

48

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

33

一个有用的额外字典示例,可用于包含出现频率。

循环之外:

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
另外一个教程链接是:http://www.kamath.com/tutorials/tut009_dictionary.asp - John M
这是一个非常好的答案,我使用了它。然而,我发现在循环中无法像您那样引用dict.Items(i)或dict.Keys(i)。在进入循环之前,我不得不将这些(项目列表和键列表)存储在单独的变量中,然后使用这些变量来获取我需要的值。例如 - allItems = companyList.Items allKeys = companyList.Keys allItems(i) 如果没有,当尝试访问循环中的Keys(i)或Items(i)时,我会收到错误消息:“未定义属性let过程,且属性get过程未返回对象”。 - raddevus

11

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。它仍然允许递归集合。你会注意到键总是第一个出现,因为在我的实现中这是强制性的并且更有意义。我也只使用了String键。如果您喜欢,您可以改回去。

Set

我将其重命名为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

Has

本帖的原因...

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

8

8

脚本运行时字典似乎存在一个漏洞,可能会在高级阶段破坏您的设计。

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


6

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


3
你可以再读一遍问题:我问的是关于VBA(Visual Basic for Application)的,不是VB,也不是VB.Net,也不是其他任何语言。 - fessGUID
5
我承认,我读题太快了。但我确实告诉了他他需要知道的事情。 - Matthew Flaschen
1
.Net的相关性在于,迟早VBA(目前是VB6的子集)将成为.Net的子集。此外,如果别人给你比你要求的更多,你就不应该抱怨。这只会显得不感激。 - Oorang
5
@Oorang,绝对没有证据表明VBA会成为VB.NET的子集,在Office中,向后兼容规则是必须的——想象一下试图转换每个编写过的Excel宏。 - Richard Gadsden
2
VBA实际上是VB6的超集。它使用与VB6相同的核心DLL,但为Office中的特定应用程序添加了各种功能。 - David-W-Fenton
显示剩余3条评论

5
如果由于任何原因,您无法安装其他功能到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

2
这个答案的概念是正确的,但示例代码无法按原样运行。每个变量都需要自己的“Dim”关键字,由于使用了“Array()”,“Country”和“Capital”需要声明为变体,“i”应该被声明(如果设置了“Option Explicit”则必须声明),循环计数器将抛出越界错误--更安全的方法是使用“UBound(Country)”作为“To”值。另外也许值得注意的是,虽然“Array()”函数是一个有用的快捷方式,但它不是在VBA中声明数组的标准方式。 - jcb

3
您可以通过System.Collections.HashTable访问非本地HashTable

HashTable

表示基于键的哈希代码组织的键/值对集合。

不确定您是否会在Scripting.Dictionary之上使用它,但为了完整性而添加。您可以查看方法以便了解感兴趣的一些方法,例如Clone, CopyTo

示例:

Option Explicit

Public Sub UsingHashTable()

    Dim h As Object
    Set h = CreateObject("System.Collections.HashTable")
   
    h.Add "A", 1
    ' h.Add "A", 1  ''<< Will throw duplicate key error
    h.Add "B", 2
    h("B") = 2
      
    Dim keys As mscorlib.IEnumerable 'Need to cast in order to enumerate  'https://dev59.com/NjYCtIcB2Jgan1znWZ_t#56705428
    
    Set keys = h.keys
    
    Dim k As Variant
    
    For Each k In keys
        Debug.Print k, h(k)                      'outputs the key and its associated value
    Next
    
End Sub

这篇由@MathieuGuindon撰写的答案详细介绍了HashTable以及为什么需要使用mscorlib.IEnumerable(对mscorlib的早期绑定引用)来枚举键值对。



网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接