¿VBA tiene estructura de diccionario?

Respuestas:

342

Si.

Establezca una referencia al tiempo de ejecución de MS Scripting ('Microsoft Scripting Runtime'). Según el comentario de @ regjo, vaya a Herramientas-> Referencias y marque la casilla para 'Microsoft Scripting Runtime'.

Ventana de referencias

Cree una instancia de diccionario con el siguiente código:

Set dict = CreateObject("Scripting.Dictionary")

o

Dim dict As New Scripting.Dictionary 

Ejemplo de uso:

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

No olvides configurar el diccionario Nothingcuando hayas terminado de usarlo.

Set dict = Nothing 
Trigo Mitch
fuente
17
Este tipo de estructura de datos lo proporciona el tiempo de ejecución de secuencias de comandos, no VBA. Básicamente, VBA puede usar prácticamente cualquier tipo de estructura de datos que sea accesible a través de una interfaz COM.
David-W-Fenton
163
Solo para completar: debe hacer referencia al "Tiempo de ejecución de secuencias de comandos de Microsoft" para que esto funcione (vaya a Herramientas-> Referencias) y marque su casilla.
regjo
77
Uh, las colecciones de VBA están codificadas. Pero tal vez tenemos una definición diferente de keyed.
David-W-Fenton
8
Estoy usando Excel 2010 ... pero sin la referencia a las herramientas "Microsoft Scripting Runtime" - Ref. Simplemente hacer CreateObject NO funciona. Entonces, @masterjo, creo que tu comentario anterior es incorrecto. A menos que me falte algo ... Entonces, chicos Herramientas -> se requieren referencias.
ihightower
44
Como FYI, no puedes usar el Dim dict As New Scripting.Dictionarysin la referencia. Sin la referencia, debe utilizar el CreateObjectmétodo de enlace tardío para crear instancias de este objeto.
David Zemens
181

VBA tiene el objeto de colección:

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

El Collectionobjeto realiza búsquedas basadas en claves utilizando un hash, por lo que es rápido.


Puede usar una Contains()función para verificar si una colección particular contiene una clave:

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

Edición 24 de junio de 2015 : más corto Contains()gracias a @TWiStErRob.

Edición 25 de septiembre de 2015 : agregado Err.Clear()gracias a @scipilot.

Caleb Hattingh
fuente
55
Bien hecho para señalar que el objeto de colección incorporado se puede usar como diccionario, ya que el método Add tiene un argumento opcional "clave".
Simon Tewsi
8
Lo malo del objeto de colección es que no puede verificar si una clave ya está en la colección. Simplemente arrojará un error. Eso es lo importante, no me gustan las colecciones. (sé que hay soluciones, pero la mayoría de ellas son "feas")
MiVoth
55
Tenga en cuenta que la búsqueda de claves de cadena (p. Ej., C.Item ("Key2")) en el diccionario VBA está cifrada, pero la búsqueda por índice entero (p. Ej. C.Item (20)) no es lineal; es lineal para / siguiente estilo de búsqueda y debe ser evitado. Es mejor usar colecciones solo para búsquedas de claves de cadena o para cada iteración.
Ben McIntyre
44
Encontré un más corto Contains: On Error Resume Next_ col(key)_Contains = (Err.Number = 0)
TWiStErRob
55
Quizás la función debería ser nombrada ContainsKey; alguien que lea solo la invocación puede confundirla por verificar que contiene un valor particular.
jpmc26
44

VBA no tiene una implementación interna de un diccionario, pero desde VBA aún puede usar el objeto de diccionario de 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
Jarmo
fuente
29

Un ejemplo adicional de diccionario que es útil para contener la frecuencia de ocurrencia.

Fuera del bucle:

Dim dict As New Scripting.dictionary
Dim MyVar as String

Dentro de un bucle:

'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

Para verificar la frecuencia:

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
John M
fuente
1
Un enlace tutorial adicional es: kamath.com/tutorials/tut009_dictionary.asp
John M
Esta fue una muy buena respuesta y la usé. Sin embargo, descubrí que no podía hacer referencia al dict.Items (i) o dict.Keys (i) en el bucle como lo hace. Tuve que almacenar esos (lista de elementos y lista de claves) en variables separadas antes de ingresar al ciclo y luego usar esas variables para obtener los valores que necesitaba. Me gusta: allItems = companyList.Items allKeys = companyList.Keys allItems (i) Si no, obtendría el error: "El procedimiento de propiedad no está definido y el procedimiento de obtención de propiedad no devolvió un objeto" al intentar acceder a las teclas (i) o Elementos (i) en el bucle.
raddevus
10

Partiendo de la respuesta de cjrh , podemos construir una función Contiene que no requiera etiquetas (no me gusta usar etiquetas).

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

Para un proyecto mío, escribí un conjunto de funciones auxiliares para hacer que un Collectioncomportamiento se parezca más a un Dictionary. Todavía permite colecciones recursivas. Notarás que Key siempre viene primero porque era obligatorio y tenía más sentido en mi implementación. Yo también usé soloString llaves. Puede volver a cambiarlo si lo desea.

Conjunto

Cambié el nombre de esto para establecer porque sobrescribirá los valores antiguos.

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

Obtener

El errmaterial es para objetos, ya que pasaría objetos usando sety variables sin. Creo que puedes comprobar si es un objeto, pero me presionaron por el tiempo.

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

Tiene

El motivo de esta publicación ...

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

Eliminar

No tira si no existe. Solo se asegura de que se elimine.

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

Llaves

Obtenga una variedad de claves.

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
Evan Kennedy
fuente
6

El diccionario de tiempo de ejecución de secuencias de comandos parece tener un error que puede arruinar su diseño en etapas avanzadas.

Si el valor del diccionario es una matriz, no puede actualizar los valores de los elementos contenidos en la matriz a través de una referencia al diccionario.

Kalidas
fuente
6

Si. Para VB6 , VBA (Excel) y VB.NET

Matthew Flaschen
fuente
2
Puede leer las preguntas más: he preguntado sobre VBA: Visual Basic para Aplicación, no para VB, ni para VB.Net, ni para ningún otro idioma.
1
fessGUID: una vez más, ¡deberías leer más las respuestas! Esta respuesta también se puede usar para VBA (en particular, el primer enlace).
Konrad Rudolph
55
Lo admito. Leí la pregunta demasiado rápido. Pero le dije lo que necesitaba saber.
Matthew Flaschen
55
@Oorang, no hay absolutamente ninguna evidencia de que VBA se convierta en un subconjunto de VB.NET, reglas de backcompat en Office: imagine intentar convertir cada macro de Excel que se haya escrito.
Richard Gadsden
2
VBA es en realidad un SUPERSET de VB6. Utiliza el mismo archivo DLL central que VB6, pero luego agrega todo tipo de funcionalidades para las aplicaciones específicas de Office.
David-W-Fenton
4

Si por algún motivo no puede instalar funciones adicionales en su Excel o no desea hacerlo, también puede usar matrices, al menos para problemas simples. Como WhatIsCapital pones el nombre del país y la función te devuelve su capital.

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
user2604899
fuente
1
El concepto de esta respuesta es sólido, pero el código de muestra no se ejecutará como está escrito. Cada variable necesita su propia Dimpalabra clave, Countryy Capitaldebe declararse como Variantes debido al uso de Array(), idebe declararse (y debe ser si Option Explicitestá configurado), y el contador de bucle arrojará un error fuera de límite, más seguro para utilizar UBound(Country)para el Tovalor. También vale la pena señalar que, si bien la Array()función es un atajo útil, no es la forma estándar de declarar matrices en VBA.
jcb