VBA 有字典结构吗?

Posted

技术标签:

【中文标题】VBA 有字典结构吗?【英文标题】:Does VBA have Dictionary Structure? 【发布时间】:2010-10-29 06:08:45 【问题描述】:

像键值数组?

【问题讨论】:

【参考方案1】:

您可以通过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://***.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 的 answer 提供了大量有关 HashTable 的详细信息,以及为什么必须使用 mscorlib.IEnumerable(对 mscorlib 的早期绑定)来枚举键:值对。


【讨论】:

【参考方案2】:

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

它的实现实际上是一个fancy——只需执行myDict(x) = y,它就会检查字典中是否有一个键x,如果有不是这样,它甚至创造了它。如果它在那里,它会使用它。

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

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

【讨论】:

【参考方案3】:

是的。

设置对 MS 脚本运行时(“Microsoft 脚本运行时”)的引用。根据@regjo 的评论,转到工具->参考并勾选“Microsoft Scripting Runtime”框。

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

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 

【讨论】:

此数据结构类型由脚本运行时提供,而不是由 VBA 提供。基本上,VBA 几乎可以使用任何可以通过 COM 接口访问的数据结构类型。 为了完整起见:您需要参考“Microsoft Scripting Runtime”才能使其工作(转到工具->参考)并选中它的框。 呃,VBA 集合是键控的。但也许我们对keyed有不同的定义。 我正在使用 Excel 2010... 但没有参考“Microsoft Scripting Runtime”工具 - 参考。仅执行 CreateObject 不起作用。所以,@masterjo 我认为你上面的评论是错误的。除非我遗漏了什么。所以,伙计们工具-> 参考是必需的。 仅供参考,您不能在没有参考的情况下使用Dim dict As New Scripting.Dictionary。如果没有引用,则必须使用后期绑定CreateObject 方法来实例化此对象。【参考方案4】:

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

【讨论】:

很好地指出内置的 Collection 对象可以用作字典,因为 Add 方法有一个可选的“key”参数。 集合对象的坏处是,您无法检查集合中是否已经存在键。它只会抛出一个错误。这是最重要的,我不喜欢收藏。 (我知道,有一些变通方法,但大多数都是“丑陋的”) 请注意,在 VBA 字典中查找字符串键(例如 c.Item("Key2") )是散列的,但通过整数索引查找(例如 c.Item(20) )是不是 - 这是一个线性的 for/next 样式搜索,应该避免。最好将集合仅用于字符串键查找或每次迭代。 我找到了一个较短的ContainsOn Error Resume Next _ col(key) _ Contains = (Err.Number = 0) 或许函数应该命名为ContainsKey;只阅读调用的人可能会因为检查它是否包含特定值而混淆它。【参考方案5】:

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

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

它与微软的版本相同。

【讨论】:

【参考方案6】:

在cjrh's answer 的基础上,我们可以构建一个不需要标签的 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

【讨论】:

【参考方案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

【讨论】:

这个答案的概念是合理的,但示例代码不会像编写的那样运行。每个变量都需要自己的 Dim 关键字,CountryCapital 需要声明为 Variants,因为使用了 Array()i 应该声明(如果设置了 Option Explicit,则必须声明) ,并且循环计数器将抛出一个越界错误——使用UBound(Country) 作为To 值更安全。还可能值得注意的是,虽然 Array() 函数是一个有用的快捷方式,但它不是在 VBA 中声明数组的标准方法。【参考方案8】:

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

循环外:

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

【讨论】:

另外一个教程链接是:kamath.com/tutorials/tut009_dictionary.asp 这是一个非常好的答案,我使用了它。但是,我发现我无法像您那样在循环中引用 dict.Items(i) 或 dict.Keys(i) 。在进入循环之前,我必须将这些(项目列表和键列表)存储在单独的变量中,然后使用这些变量来获得我需要的值。像 - allItems = companyList.Items allKeys = companyList.Keys allItems(i) 如果没有,我会在尝试访问 Keys(i) 或循环中的项目(i)。【参考方案9】:

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

【讨论】:

您可以阅读更多问题:我已经询问过 VBA:Visual Basic for Application,不适用于 VB,不适用于 VB.Net,不适用于任何其他语言。 fessGUID:再说一遍,你应该多阅读答案!这个答案也可以用于VBA(特别是第一个链接)。 我承认。我读的问题太快了。但我确实告诉了他他需要知道的事情。 @Oorang,绝对没有证据表明 VBA 成为 VB.NET 的子集,Office 中的反向兼容规则 - 想象一下尝试转换曾经编写的每个 Excel 宏。 VBA 实际上是 VB6 的 SUPERSET。它使用与 VB6 相同的核心 DLL,但随后为 Office 中的特定应用程序添加了各种功能。【参考方案10】:

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

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

【讨论】:

【参考方案11】:

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

【讨论】:

以上是关于VBA 有字典结构吗?的主要内容,如果未能解决你的问题,请参考以下文章

自从学会了VBA字典,VLOOKUP都不那么香了

vba多表合sql,数组,字典并哪种最快

vba字典删除重复

使用拼写检查时使用 VBA 切换字典 (MS Access)

从嵌套字典中返回密钥excel vba

如何在 VBA(不是字典)中制作列表?