Is that a letter?

In IsLetter Function for VBA, Outlook MVP David Lee posts a function for checking if a given string is a letter.

I like it because it actually checks if the entire string, regardless of its length, is comprised of letters. It doesn't hardcode the string length — the loop makes the length of the string superficial. If there is only one character, it simply loops once.

It uses the Asc function to determine the character code for each character in the string, taking advantage of the fact that the letters A-Z and a-z are consecutive. You can see this if you use the character set workbook I use.

One thing it does that I don't like is it explicitly sets the return value to
False

, and it repeatedly sets the return value to
True

. A better workflow would be to assume that the input string contains only letters (IsLetter =
True

), then set IsLetter to
False

(and immediately exit) if we encounter a non-letter (is that a word?). This way, we only set IsLetter to
True

or
False

once, instead of wasting processor time setting it to
True

N times.

Function IsLetter(strValue As String) As Boolean
  Dim intPos As Integer
  ' assume True
  IsLetter = True

  For intPos = 1 To Len(strValue)
    Select Case Asc(Mid(strValue, intPos, 1))
    Case 65 To 90, 97 To 122

    Case Else
      ' not a letter, return False
      IsLetter = False
      Exit For
    End Select
  Next intPos
End Function

In a comment on that article I posted two additional ways to check if a given string contains only letters. They still loop through each character of the input string, but they use the Like Operator instead of comparing each character to character codes.

RegExp to the Rescue

We can also use Regular Expressions to check if a string contains only letters. This would mean only one pass through a string, instead of looping!

We need two functions from the Regular Expressions page: GetRegEx and TestRegex. Once you copy those functions to a standard module, the following function may be used to check if a given string consists only of letters a-z (case insensitive):

Function IsAlpha(stringToCheck As String) As Boolean
  Dim regex As Object ' VBScript.RegExp
  Set regex = GetRegEx
  If Not regex Is Nothing Then
    IsAlpha = TestRegex(regex, "^[a-zA-Z]+$", stringToCheck)
  End If
End Function

"^[a-zA-Z]+$" roughly translates to from the start of the string, match any number/combination of upper or lower case letters until the end of the string.

RegEx vs. Loop

How does the RegEx perform against a loop?

I used the following extremely un-scientific procedure to test the relative speeds of the IsLetter (loop) and IsAlpha (RegEx) functions.

Sub TestRegexLoop()

  Dim i As Long
  Dim startTimeRegex As Single
  Dim endTimeRegex As Single
  Dim startTimeLoop As Single
  Dim endTimeLoop As Single
  Dim msg As String
  Dim is_a_letter As Boolean

  Const numberOfLoops As Long = 1000
  Const stringToCheck As String = _
    "Thequickbrownfoxjumpsoverthelazydog"

  ' use Regex
  startTimeRegex = Timer
  For i = 1 To numberOfLoops
    is_a_letter = IsAlpha(stringToCheck)
  Next i
  endTimeRegex = Timer

  ' use Loop
  startTimeLoop = Timer
  For i = 1 To numberOfLoops
    is_a_letter = IsLetter(stringToCheck)
  Next i
  endTimeLoop = Timer

  msg = "Number of iterations: " & numberOfLoops & vbCrLf
  msg = msg & "Using Regex: " & _
    Format(endTimeRegex - startTimeRegex, "#.###") & " seconds" & vbCrLf
  msg = msg & "Using Loop: " & _
    Format(endTimeLoop - startTimeLoop, "#.###") & " seconds" & vbCrLf
  MsgBox msg
End Sub

Results

Let's see how both functions performed. At just 1,000 iterations, RegEx is already much slower than a loop.

Test Regex and Loop, 1000 iterations

At 10,000 iterations, this is a Mike Tyson fight. The function that uses a loop is barely breaking a sweat, while RegEx is clocking in at 14 seconds. I quit.

Test Regex and Loop, 10000 iterations

RegEx vs. Loop Rematch

We're not being fair. After all, IsAlpha had to instantiate the RegExp object every time it is called. That has to account for some of the slowdown. The original IsAlpha is a function trying to do too much. So we alter the IsAlpha function to take in an existing RegExp object…

Function IsAlpha(regex As Object, stringToCheck As String) As Boolean
  IsAlpha = TestRegex(regex, "^[a-zA-Z]+$", stringToCheck)
End Function

…and we create the RegExp object in our test procedure and pass it to IsAlpha:

Sub TestRegexLoop2()

  Dim i As Long
  Dim startTimeRegex As Single
  Dim endTimeRegex As Single
  Dim startTimeLoop As Single
  Dim endTimeLoop As Single
  Dim msg As String
  Dim is_a_letter As Boolean
  Dim regex As Object  ' RegExp

  Const numberOfLoops As Long = 1000
  Const stringToCheck As String = "Thequickbrownfoxjumpsoverthelazydog"

  ' use Regex
  startTimeRegex = Timer

  Set regex = GetRegEx
  If Not regex Is Nothing Then
    For i = 1 To numberOfLoops
      is_a_letter = IsAlpha(regex, stringToCheck)
    Next i
  End If
  endTimeRegex = Timer

  ' use Loop
  startTimeLoop = Timer
  For i = 1 To numberOfLoops
    is_a_letter = IsLetter(stringToCheck)
  Next i
  endTimeLoop = Timer

  msg = "Number of iterations: " & numberOfLoops & vbCrLf
  msg = msg & "Using Regex: " & _
    Format(endTimeRegex - startTimeRegex, "#.###") & " seconds" & vbCrLf
  msg = msg & "Using Loop: " & _
    Format(endTimeLoop - startTimeLoop, "#.###") & " seconds" & vbCrLf
  MsgBox msg
End Sub

I have a feeling this is going to be more of a fair fight.

Results

That's more like it! Even passing the RegExp object between two functions has barely slowed it down.

Test Regex and Loop Rematch, 1000 iterations

At 10,000 iterations the RegEx function is only slightly slower. In my humble non-scientific opinion, it is statistically insignificant.

Test Regex and Loop Rematch, 10000 iterations

Even at 100,000 iterations they are both taking nearly 1 second to complete.

Test Regex and Loop Rematch, 100000 iterations

Conclusion

Even though RegExp can evaluate the entire string in one go, the loop was still faster. Refactoring the IsAlpha function to create the RegExp object only once, however, and passing it to another function, closed the gap. I like RegEx but don't have a strong preference, so when I use it I will keep in mind that the RegExp object should be created in a parent procedure and passed to a child function for it to use RegExp parsing methods.

About JP

I'm just an average guy who writes VBA code for a living. This is my personal blog. Excel and Outlook are my thing, with a sprinkle of Access and Word here and there. Follow this space to learn more about VBA. Keep Reading »


Related Articles:


Share This Article:

Share and bookmark this articledelicious buttonfacebook buttonlinkedin buttonstumbleupon buttontwitter button

comment bubble 10 Comment(s) on Is that a letter?:

  1. NickV writes:

    So à, ç, é, ö, etc. aren't letters?

  2. If I recall correctly, using "if then" statements is faster than "select case" statements too. Not sure how much faster. Probably wouldn't be significant.

  3. BTW, RegExp's are pretty neat, but I've had a hard time trying to figure them out! How did you learn them?

  4. Michael writes:

    Perhaps test the Like function:

    For intPos = 1 To Len(strValue)
        If Mid$(strValue, intPos, 1)) Like "[!A-Za-z]" then
           IsLetter = False
           Exit For
       End if
    Next intPos

    Using Mid$() probably speeds things up, too.
    …mrt

    • That is more or less what I posted as a reply in the original article.

    • Michael writes:

      According to the Excel Help, for the Like operator, accented characters fall after unaccented characters in the sort order. [a-e] matches a, à, b, c, ç, d, e. It does not match é since é falls after e in the sort order.

    • Getting rid of the Mid function call and the loop probably speeds things up even more…

      Function IsAllLetters(S As String) As Boolean
        IsAllLetters = Not S Like "*[!A-Za-z]*"
      End Function
      • Actually, we need to modify this to protect against it returning True for the empty string…

        Function IsAllLetters(S As String) As Boolean
          IsAllLetters = Len(S) > 0 And Not S Like "*[!A-Za-z]*"
        End Function
This article is closed to new comments. Why?
Peltier Tech Charting Utilities for Excel