15
  • I have to check a piece of user input against a list of items; if the input is in the list of items, then direct the flow one way. If not, direct the flow to another.
  • This list is NOT visible on the worksheet itself; it has to be obfuscated under code.

I have thought of two strategies to do this:

  1. Declare as an enum and check if input is part of this enum, although I'm not sure on the syntax for this - do I need to initialise the enum every time I want to use it?
  2. Declare as an array and check if input is part of this array.

I was wondering for VBA which is better in terms of efficiency and readability?

ashleedawg
  • 20,365
  • 9
  • 72
  • 105
Evil Washing Machine
  • 1,293
  • 4
  • 18
  • 43
  • I presume you already have the list available in a txt file or Excel? - I would work with that directly as an array. Building a dictionary line by line as Mehow did isn't that practical – brettdj Jan 30 '14 at 09:53
  • No, the list is on a piece of paper! – Evil Washing Machine Jan 30 '14 at 10:04
  • Well that is a surprise :) I would still then run with adding them to a single code line with an array rather than add a new line of code for each new item – brettdj Jan 30 '14 at 10:21

3 Answers3

13

You can run a simple array test as below where you add the words to a single list:

Sub Main1()
arrList = Array("cat", "dog", "dogfish", "mouse")
Debug.Print "dog", Test("dog")   'True
Debug.Print "horse", Test("horse") 'False
End Sub

Function Test(strIn As String) As Boolean
Test = Not (IsError(Application.Match(strIn, arrList, 0)))
End Function

Or if you wanted to do a more detailed search and return a list of sub-string matches for further work then use Filter. This code would return the following via vFilter if looking up dog

dog, dogfish

In this particular case the code then checks for an exact match for dog.

Sub Main2()
arrList = Array("cat", "dog", "dogfish", "mouse")
Debug.Print "dog", Test1("dog")
Debug.Print "horse", Test1("horse")
End Sub

Function Test1(strIn As String) As Boolean
Dim vFilter
Dim lngCnt As Long
vFilter = Filter(arrList, strIn, True)
For lngCnt = 0 To UBound(vFilter)
    If vFilter(lngCnt) = strIn Then
        Test1 = True
        Exit For
    End If
Next
End Function
brettdj
  • 54,857
  • 16
  • 114
  • 177
  • 2
    Great answer, but it is too late -I've already manually added about 100 items onto a dictionary :(. Not that there was anything wrong with the solution aside from all the manual work, of course. – Evil Washing Machine Jan 30 '14 at 10:46
  • Good Idea of storing them in an array. A slight modification to the code would be to store the list in a variable with a delimiter and then using `Split` to create your array – Siddharth Rout Jan 30 '14 at 11:25
  • 1
    Sorry for reviving this old post, but I would like to know which method (between the dictionary and the array) would be the most efficient, faster. Also with the array, is using `Application.Match` faster than looping through the array? Thanks – Oscar Anthony Aug 29 '17 at 18:12
  • 1
    To answer my own question, I found out that using the `Match()` function is actually a lot slower than just iterating (looping) through the array. I did a test with an array of size 2000. The worst case scenario for looping through the array would be looking for the last item (at index 2000). After 5000 calls to both the `Match()` function and looping, the total time for `Match()` was `3.746094` but only `1.667969` for looping through the array. [Check the answer to this question for the code I used in my testing](https://stackoverflow.com/a/18769246/3023808) – Oscar Anthony Aug 29 '17 at 19:38
10

Unlike in .NET languages VBA does not expose Enum as text. It strictly is a number and there is no .ToString() method that would expose the name of the Enum. It's possible to create your own ToString() method and return a String representation of an enum. It's also possible to enumerate an Enum type. Although all is achievable I wouldn't recommend doing it this way as things are overcomplicated for such a single task.

How about you create a Dictionary collection of the items and simply use Exist method and some sort of error handling (or simple if/else statements) to check whether whatever user inputs in the input box exists in your list.

For instance:

Sub Main()

    Dim myList As Object
    Set myList = CreateObject("Scripting.Dictionary")

    myList.Add "item1", 1
    myList.Add "item2", 2
    myList.Add "item3", 3

    Dim userInput As String
    userInput = InputBox("Type something:")

    If myList.Exists(userInput) Then
        MsgBox userInput & " exists in the list"
    Else
        MsgBox userInput & " does not exist in the list"
    End If

End Sub

Note: If you add references to Microsoft Scripting Runtime library you then will be able to use the intelli-sense with the myList object as it would have been early bound replacing

 Dim myList As Object
 Set myList = CreateObject("Scripting.Dictionary")

with

Dim myList as Dictionary
Set myList = new Dictionary

It's up to you which way you want to go about this and what is more convenient. Note that you don't need to add references if you go with the Late Binding while references are required if you want Early Binding with the intelli-sense.


Just for the sake of readers to be able to visualize the version using Enum let me demonstrate how this mechanism could possibly work

Enum EList
    item1
    item2
    item3
    [_Min] = item1
    [_Max] = item3
End Enum

Function ToString(eItem As EList) As String
    Select Case eItem
        Case EList.item1
            ToString = "item1"
        Case EList.item2
            ToString = "item2"
        Case EList.item3
            ToString = "item3"
    End Select
End Function

Function Exists(userInput As String) As Boolean
    Dim i As EList
    For i = EList.[_Min] To EList.[_Max]
        If userInput = ToString(i) Then
            Exists = True
            Exit Function
        End If
    Next
    Exists = False
End Function

Sub Main()

    Dim userInput As String
    userInput = InputBox("type something:")

    MsgBox Exists(userInput)

End Sub

First you declare your List as Enum. I have added only 3 items for the example to be as simple as possible. [_Min] and [_Max] indicate the minimum value and maximum value of enum (it's possible to tweak this but again, let's keep it simple for now). You declare them both to be able to iterate over your EList.

ToString() method returns a String representation of Enum. Any VBA developer realizes at some point that it's too bad VBA is missing this as a built in feature. Anyway, you've got your own implementation now.

Exists takes whatever userInput stores and while iterating over the Enum EList matches against a String representation of your Enum. It's an overkill because you need to call many methods and loop over the enum to be able to achieve what a simple Dictionary's Exists method does in one go. This is mainly why I wouldn't recommend using Enums for your specific problem.

Then in the end you have the Main sub which simply gathers the input from the user and calls the Exists method. It shows a Message Box with either true or false which indicates if the String exists as an Enum type.

Community
  • 1
  • 1
  • 2
    mehow, am I missed something, or your `DoesntExist:` is odd? if user input does not exist in the dictionary, `myList.Exists(userInput)` just returns `false` and no error triggered – Dmitry Pavliv Jan 27 '14 at 11:18
  • 1
    youre right @simoco I haven't tested the code before posting and overlooked that. Already updated answer with a simpler approach –  Jan 27 '14 at 11:21
  • @mehow On http://msdn.microsoft.com/en-us/library/twsk0311.aspx seems to suggest that enums can be strings, no? – Evil Washing Machine Jan 27 '14 at 11:22
  • 1
    @SchwitJanwityanujit you're looking at Visual Basic reference not at VBA which are different. –  Jan 27 '14 at 11:24
  • @mehow newbie question but what do the numbers after the item do? e.g. in 'myList.Add "item3", 3' what does the '3' do? – Evil Washing Machine Jan 27 '14 at 14:36
  • 2
    @SchwitJanwityanujit http://www.experts-exchange.com/Software/Office_Productivity/Office_Suites/MS_Office/A_3391-Using-the-Dictionary-Class-in-VBA.html –  Jan 27 '14 at 15:00
  • Excellent link provided by Mehow, its a thorough overview by Patrick. – brettdj Feb 02 '14 at 08:55
  • Thanks for introducing me to dictionaries. With your information, I cut a ~30" function to <1" by replacing the Range.Find with Dictionary.Exists. Brilliant. – SSlinky Apr 04 '16 at 10:10
5

Just use the Select Case with a list:

Select Case entry
   Case item1,item2, ite3,item4 ' add up to limit for Case, add more Case if limit exceeded
      do stuff for being in the list
   Case Else
      do stuff for not being in list
End Select
Nisse Engström
  • 4,738
  • 23
  • 27
  • 42
Steve G
  • 61
  • 1
  • 1
  • 2
    This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post - you can always comment on your own posts, and once you have sufficient [reputation](http://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](http://stackoverflow.com/help/privileges/comment). – Sufian Aug 22 '15 at 16:24
  • I found this case idea to be a great answer to the OP's question. I prefer this over the other answers and am going to adopt it. I like SQL's `If String in (X,Y,Z)` syntax and was looking for a VBA equivalent. I hadn't thought of the simple idea of using a case statement as a VBA alternative, but I'm going to adopt this as my solution. The OP stated his list will be obfuscated under code. If it's a gigantic list, reading from another file and adding to a dictionary is a great idea. If it's just 10-20 items, with only a couple paths, this select case method is a great solution. – Sabatino Ognibene Apr 06 '22 at 15:06