VBA 有字典结构吗?

Does VBA have Dictionary Structure?

提问人: 提问时间:5/27/2009 最后编辑:StayOnTarget 更新时间:1/3/2021 访问量:335849

问:

VBA有字典结构吗?喜欢键值数组<>?

VBA 字典 数据结构 VB6

评论


答:

6赞 Matthew Flaschen 5/27/2009 #1

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

评论

3赞 5/27/2009
您可以阅读更多问题:我问过 VBA:Visual Basic for Application,不是 VB,不是 VB.Net,不是任何其他语言。
1赞 Konrad Rudolph 5/27/2009
fessGUID:话又说回来,你应该多读答案!此答案也可用于 VBA(特别是第一个链接)。
5赞 Matthew Flaschen 5/28/2009
我承认。我读问题太快了。但我确实告诉了他他需要知道的事情。
5赞 Richard Gadsden 8/27/2010
@Oorang,绝对没有证据表明 VBA 成为 Office 中 VB.NET 反向兼容规则的子集 - 想象一下尝试转换曾经编写过的每个 Excel 宏。
2赞 David-W-Fenton 5/6/2011
VBA 实际上是 VB6 的超集。它使用与 VB6 相同的核心 DLL,但随后为 Office 中的特定应用程序添加了各种功能。
364赞 Mitch Wheat 5/27/2009 #2

是的。

设置对 MS 脚本运行时(“Microsoft 脚本运行时”)的引用。根据 @regjo 的评论,转到 Tools->References 并勾选“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赞 David-W-Fenton 5/29/2009
此数据结构类型由脚本运行时提供,而不是由 VBA 提供。基本上,VBA 几乎可以使用可通过 COM 接口访问的任何数据结构类型。
172赞 regjo 12/10/2009
为了完整起见:您需要引用“Microsoft脚本运行时”才能正常工作(转到 Tools->References)并选中其框。
7赞 David-W-Fenton 5/1/2011
呃,VBA 集合是键控的。但也许我们对 有不同的定义。keyed
9赞 ihightower 1/21/2012
我正在使用 Excel 2010...但没有引用“Microsoft 脚本运行时”工具 - 参考。仅仅执行 CreateObject 是行不通的。所以,@masterjo我认为你上面的评论是错误的。除非我错过了什么......所以,伙计们 工具 - > 参考资料是必需的。
4赞 David Zemens 9/25/2013
仅供参考,您不能在没有引用的情况下使用 。如果没有引用,则必须使用实例化此对象的后期绑定方法。Dim dict As New Scripting.DictionaryCreateObject
48赞 Jarmo 5/27/2009 #3

VBA 没有字典的内部实现,但从 VBA 中,您仍然可以使用 MS 脚本运行时库中的字典对象。

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
8赞 Kalidas 11/3/2011 #4

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

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

207赞 Caleb Hattingh 11/29/2011 #5

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赞 Simon Elms 5/5/2013
指出内置的 Collection 对象可以用作字典,因为 Add 方法具有可选的“key”参数。
12赞 MiVoth 6/18/2013
集合对象的坏处是,您无法检查集合中是否已有键。它只会抛出一个错误。这是一件大事,我不喜欢收藏。(我知道,有解决方法,但大多数都是“丑陋的”)
5赞 Ben McIntyre 12/20/2013
请注意,在VBA字典中查找字符串键(例如c.Item(“Key2”))是哈希的,但通过整数索引(例如c.Item(20))查找不是 - 它是线性的for/next样式搜索,应避免。最好仅将集合用于字符串键查找或每次迭代。
4赞 TWiStErRob 6/20/2015
我发现了一个较短的包含On Error Resume Next _ col(key) _ Contains = (Err.Number = 0)
7赞 jpmc26 2/22/2018
也许这个函数应该被命名为;仅读取调用的人可能会混淆它以检查它是否包含特定值。ContainsKey
33赞 John M 1/26/2012 #6

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

循环外:

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赞 John M 2/15/2012
另一个教程链接是:kamath.com/tutorials/tut009_dictionary.asp
0赞 raddevus 10/31/2017
这是一个很好的答案,我用了它。但是,我发现我无法引用字典。Items(i) 或 dict.Keys(i) 在循环中。在进入循环之前,我必须将它们(项目列表和键列表)存储在单独的变量中,然后使用这些变量来获得我需要的值。Like - allItems = companyList.Items allKeys = companyList.Keys allItems(i) 如果没有,我会在尝试访问循环中的 Keys(i) 或 Items(i) 时收到错误:“Property let procedure not defined and property get procedure did not return an object”。
5赞 user2604899 8/22/2013 #7

如果由于任何原因,您无法向 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赞 jcb 2/27/2018
这个答案的概念是合理的,但示例代码不会按编写的方式运行。每个变量都需要自己的关键字,并且由于使用了 ,因此需要声明为 Variants,should be declares(并且必须是 if is set),并且循环计数器将抛出一个越界错误 -- 用于该值更安全。另外值得注意的是,虽然该函数是一个有用的快捷方式,但它并不是在 VBA 中声明数组的标准方法。DimCountryCapitalArray()iOption ExplicitUBound(Country)ToArray()
11赞 Evan Kennedy 6/5/2015 #8

基于 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

对于我的一个项目,我编写了一组辅助函数,使行为更像 .它仍然允许递归收集。您会注意到 Key 始终排在第一位,因为它是强制性的,并且在我的实现中更有意义。我也只用了钥匙。如果您愿意,可以将其改回。CollectionDictionaryString

设置

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

获取

这些东西是针对对象的,因为你会传递对象 using 和 variables without out。我想你可以检查它是否是一个物体,但我的时间紧迫。errset

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
8赞 Michiel van der Blonk 11/23/2015 #9

所有其他人都已经提到了 Dictionary 类的 scripting.runtime 版本的使用。如果您无法使用此 DLL,您也可以使用此版本,只需将其添加到您的代码中即可。

https://github.com/VBA-tools/VBA-Dictionary/blob/master/Dictionary.cls

它与Microsoft的版本相同。

3赞 Vityata 10/22/2020 #10

VBA 可以使用 的字典结构。Scripting.Runtime

它的实现实际上是一个花哨实现 - 只需这样做,它就会检查字典中是否有键,如果没有键,它甚至会创建它。如果它在那里,它就会使用它。myDict(x) = yx

而且它不会“大喊大叫”或“抱怨”这个额外的步骤,而是在“引擎盖下”执行。当然,您可以显式检查密钥是否存在 .因此,这 5 行:Dictionary.Exists(key)

If myDict.exists("B") Then
    myDict("B") = myDict("B") + i * 3
Else
    myDict.Add "B", i * 3
End If

与此 1 班轮相同 - .一探究竟:myDict("B") = myDict("B") + i * 3

Sub TestMe()

    Dim myDict As Object, i As Long, myKey As Variant
    Set myDict = CreateObject("Scripting.Dictionary")
    
    For i = 1 To 3
        Debug.Print myDict.Exists("A")
        myDict("A") = myDict("A") + i
        myDict("B") = myDict("B") + 5
    Next i
    
    For Each myKey In myDict.keys
        Debug.Print myKey; myDict(myKey)
    Next myKey

End Sub

enter image description here

3赞 QHarr 1/3/2021 #11

您可以通过 访问非本机。HashTableSystem.Collections.HashTable

哈希表

表示键/值对的集合,这些键/值对基于 密钥的哈希代码。

不确定你是否想使用它,但为了完整起见,在这里添加。您可以查看这些方法,以防有一些感兴趣的方法,例如Scripting.DictionaryClone, 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://stackoverflow.com/a/56705428/6241235
    
    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 的早期绑定引用)来枚举键:值对。mscorlib.IEnumerable