O VBA possui estrutura de dicionário?

269

O VBA possui estrutura de dicionário? Como matriz de valores de chave <>?

UuDdLrLrSs
fonte

Respostas:

342

Sim.

Defina uma referência ao tempo de execução do MS Scripting ('Microsoft Scripting Runtime'). Conforme o comentário do @ regjo, vá em Ferramentas-> Referências e marque a caixa 'Microsoft Scripting Runtime'.

Janela Referências

Crie uma instância do dicionário usando o código abaixo:

Set dict = CreateObject("Scripting.Dictionary")

ou

Dim dict As New Scripting.Dictionary 

Exemplo de uso:

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

Não se esqueça de definir o dicionário para Nothingquando terminar de usá-lo.

Set dict = Nothing 
Mitch Wheat
fonte
17
Esse tipo de estrutura de dados é fornecido pelo tempo de execução do script, não pelo VBA. Basicamente, o VBA pode usar praticamente qualquer tipo de estrutura de dados acessível através de uma interface COM.
David-W-Fenton
163
Apenas para garantir a integridade: você precisa fazer referência ao "Microsoft Scripting Runtime" para que isso funcione (vá em Ferramentas-> Referências) e marque sua caixa.
regjo
7
As coleções VBA são codificadas. Mas talvez tenhamos uma definição diferente de keyed.
David-W-Fenton
8
Estou usando o Excel 2010 ... mas sem a referência às ferramentas "Microsoft Scripting Runtime" - Ref .. Apenas fazer o CreateObject NÃO funciona. Então, @masterjo, acho que seu comentário acima está errado. A menos que esteja faltando alguma coisa ... Então, pessoal Ferramentas -> referências é necessária.
Ihightower 21/01
4
Como FYI, você não pode usar o Dim dict As New Scripting.Dictionarysem a referência. Sem a referência, você deve usar o CreateObjectmétodo de ligação tardia para instanciar esse objeto.
David Zemens
181

O VBA tem o objeto de coleção:

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

O Collectionobjeto executa pesquisas baseadas em chave usando um hash, para que seja rápido.


Você pode usar uma Contains()função para verificar se uma coleção específica contém uma chave:

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

Editar 24 de junho de 2015 : Contains()Agradecimentos mais curtos a @TWiStErRob.

Editar 25 de setembro de 2015 : adicionado um Err.Clear()agradecimento a @scipilot.

Caleb Hattingh
fonte
5
Bem feito para apontar o objeto Collection incorporado pode ser usado como um dicionário, pois o método Add possui um argumento "chave" opcional.
Simon Tewsi 5/05
8
O ruim do objeto de coleção é que você não pode verificar se uma chave já está na coleção. Isso apenas gera um erro. Essa é a grande coisa, eu não gosto de coleções. (eu sei, que existem soluções alternativas, mas a maioria deles são "feio")
MiVoth
5
Observe que a pesquisa de chaves de seqüência de caracteres (por exemplo, c.Item ("Key2")) no VBA Dictionary IS, mas a pesquisa por índice inteiro (por exemplo, c.Item (20)) não é - é uma linear / estilo de pesquisa e deve ser evitado. É melhor usar coleções apenas para pesquisas de chave de cadeia ou para cada iteração.
precisa
4
Eu encontrei um menor Contains: On Error Resume Next_ col(key)_Contains = (Err.Number = 0)
TWiStErRob 20/15/15
5
Talvez a função deva ser nomeada ContainsKey; alguém lendo apenas a invocação pode confundi-la por verificar se ela contém um valor específico.
Jpmc26
44

O VBA não possui uma implementação interna de um dicionário, mas, no VBA, você ainda pode usar o objeto de dicionário da 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
fonte
29

Um exemplo de dicionário adicional que é útil para conter a frequência de ocorrência.

Fora do loop:

Dim dict As New Scripting.dictionary
Dim MyVar as String

Dentro de um loop:

'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 a frequência:

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
fonte
1
Uma ligação tutorial adicional é: kamath.com/tutorials/tut009_dictionary.asp
John M
Essa foi uma resposta muito boa e eu a usei. No entanto, descobri que não podia fazer referência ao dict.Items (i) ou dict.Keys (i) no loop, como você faz. Eu tive que armazenar esses itens (lista de itens e lista de chaves) em vars separados antes de entrar no loop e depois usá-los para obter os valores necessários. Like - allItems = companyList.Items allKeys = companyList.Keys allItems (i) Se não, eu receberia o erro: "O procedimento de permissão de propriedade não definido e o procedimento de obtenção de propriedade não retornaram um objeto" ao tentar acessar as Chaves (i) ou Itens (i) no loop.
Raddevus 30/10
10

Criando a resposta de cjrh , podemos criar uma função Contains que não requer etiquetas (não gosto de 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 um projeto meu, escrevi um conjunto de funções auxiliares para tornar o Collectioncomportamento mais parecido com a Dictionary. Ele ainda permite coleções recursivas. Você notará que Key sempre vem em primeiro lugar porque era obrigatório e fazia mais sentido na minha implementação. Eu também usei apenas Stringteclas. Você pode mudar de volta, se quiser.

Conjunto

Renomeei isso para definir, porque ele substituirá os valores antigos.

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

Obter

O errmaterial é para objetos, pois você passaria objetos usando sete variáveis ​​sem. Eu acho que você pode apenas verificar se é um objeto, mas fui pressionado pelo tempo.

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

Tem

O motivo deste post ...

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

Remover

Não joga se não existir. Apenas garante que ele seja removido.

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

Chaves

Obtenha uma matriz de chaves.

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
fonte
6

O dicionário de tempo de execução de script parece ter um bug que pode arruinar seu design em estágios avançados.

Se o valor do dicionário for uma matriz, você não poderá atualizar os valores dos elementos contidos na matriz por meio de uma referência ao dicionário.

Kalidas
fonte
6

Sim. Para VB6 , VBA (Excel) e VB.NET

Matthew Flaschen
fonte
2
Você pode ler mais perguntas: perguntei sobre o VBA: Visual Basic for Application, não para o VB, nem para o VB.Net, nem para qualquer outro idioma.
1
fessGUID: então, novamente, você deve ler mais respostas! Esta resposta também pode ser usada para VBA (em particular, o primeiro link).
289 Konrad Rudolph
5
Eu admito. Eu li a pergunta muito rápido. Mas eu disse a ele o que ele precisava saber.
Matthew Flaschen
5
@ Oorang, não há absolutamente nenhuma evidência de o VBA se tornar um subconjunto do VB.NET, regras de backcompat no Office - imagine tentar converter todas as macro do Excel já escritas.
Richard Gadsden
2
VBA é realmente um superconjunto de VB6. Ele usa a mesma DLL principal do VB6, mas adiciona todos os tipos de funcionalidade para aplicativos específicos no Office.
David-W-Fenton
4

Se, por qualquer motivo, você não puder instalar recursos adicionais no Excel ou não desejar, também poderá usar matrizes, pelo menos para problemas simples. Como WhatIsCapital, você coloca o nome do país e a função retorna seu 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
fonte
1
O conceito desta resposta é sólido, mas o código de exemplo não será executado como está escrito. Cada variável precisa de sua própria Dimpalavra-chave Countrye Capitalprecisa ser declarada como Variante devido ao uso de Array(), ideve ser declarada (e deve ser se Option Explicitfor definida), e o contador de loop lançará um erro fora do limite - mais seguro para use UBound(Country)para o Tovalor Talvez também seja interessante notar que, embora a Array()função seja um atalho útil, não é a maneira padrão de declarar matrizes no VBA.
jcb