ChatGPT解决这个技术问题 Extra ChatGPT

VBA 有字典结构吗?

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


d
danielcooperxyz

是的。

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

https://i.stack.imgur.com/ubtvU.png

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

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”工具 - Ref.. 只是做 CreateObject 不起作用。所以,@masterjo 我认为你上面的评论是错误的。除非我遗漏了什么。所以,伙计们工具-> 参考是必需的。
作为仅供参考,您不能在没有参考的情况下使用 Dim dict As New Scripting.Dictionary。如果没有引用,您必须使用实例化此对象的后期绑定 CreateObject 方法。
h
hmqcnoesy

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;只阅读调用的人可能会因为检查它是否包含特定值而混淆它。
J
Jarmo

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

C
Community

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

循环外:

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)。
C
Community

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 键。如果你愿意,你可以把它改回来。

我将其重命名为设置,因为它会覆盖旧值。

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

K
Kalidas

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

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


M
Matthew Flaschen

是的。对于 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 中的特定应用程序添加了各种功能。
M
Michiel van der Blonk

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

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

它与微软的版本相同。


u
user2604899

如果出于任何原因,您无法在 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 关键字,由于使用 Array(),需要将 CountryCapital 声明为 Variants,应该声明 i(如果设置了 Option Explicit,则必须声明) ,并且循环计数器将抛出一个越界错误——使用 UBound(Country) 作为 To 值更安全。还可能值得注意的是,虽然 Array() 函数是一个有用的快捷方式,但它不是在 VBA 中声明数组的标准方法。
V
Vityata

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

而它的实现实际上是一个花哨的——只需执行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

https://i.stack.imgur.com/L0KpJ.png


Q
QHarr

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