
Solution to Event 3 in the 2008 Winter Scripting Games Sudden Death Challenge.
Here’s some Scripting Games trivia for you. At first we had a completely different scenario lined up for Event 3; at the last minute, however, we decided that our original event 3 was too easy, even for the Sudden Death Challenge.
Note. Remember, the Sudden Death Challenge was designed to reward the ability to come back time after time and rapidly solve a problem. It was never intended to be particularly difficult. |
At any rate, that meant that we had to quickly come up with a replacement for Event 3. The event we eventually decided upon (this one) turned out to be a little tricky. We didn’t think it was hard, but if you weren’t careful you might end up writing far more code than you needed to.
So was that fair, to remove a really easy event and substitute in its place an event that might make you do a lot more work than you had to? Hey, who ever said the Scripting Games were fair?
Really? We said that? Well, sorry. But, now that the event is over, well, what are you going to do?
At any rate, Event 3 required you to read in a list of US Presidents from a text file and then perform three tasks:
| • | Determine which President had the longest first name. |
| • | Determine which letters (if any) are not part of any President’s initials. |
| • | Determine the total number of vowels (a, e, i, o, and u) that appear in all the Presidential names. |
How are we supposed to write a script that does all that? Well, here’s one way. This particular script uses VBScript; however, you could have also completed this event using Windows PowerShell or Perl.
Here’s the VBScript solution:
Const ForReading = 1
intNameLength = 0
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile("C:\Scripts\Presidents.txt", ForReading)
strContents = objFile.ReadAll
objFile.Close
arrContents = Split(strContents, vbCrlf)
For Each strName in arrContents
arrNames = Split(strName, ", ")
intLength = Len(arrNames(1))
If intLength > intNameLength Then
strLongestName = arrNames(1) & " " & arrNames(0)
intNameLength = intLength
End If
Next
Wscript.Echo "Longest first name: " & strLongestName
Wscript.Echo
Wscript.Echo "Letters not used in initials:"
Set objRegEx = CreateObject("VBScript.RegExp")
objRegEx.Global = True
objRegEx.Pattern = "[^A-Z]"
strNewString = objRegEx.Replace(strContents, "")
For i = 65 to 90
strTarget = Chr(i)
If InStr(strNewString, strTarget) Then
Else
Wscript.Echo strTarget
End If
Next
Wscript.Echo
strContents = LCase(strContents)
objRegEx.Pattern = "[aeiou]"
Set colMatches = objRegEx.Execute(strContents)
Wscript.Echo "Total vowels used: " & colMatches.Count
So how did we manage to fulfill all the requirements for this somewhat-eclectic event? Well, to begin with, our script defines a constant named ForReading and sets the value of that constant to 0; we’ll use ForReading when we go to open the text file C:\Scripts\Presidents.txt. After assigning the value 0 to a counter variable named intNameLength we create an instance of the Scripting.FileSystemObject object, then use this line of code to open Presidents.txt for reading:
Set objFile = objFSO.OpenTextFile("C:\Scripts\Presidents.txt", ForReading)
We then use the ReadAll method to read the entire contents of the file into a variable named strContents. Once we’ve done that we use the Close method to close Presidents.txt.
Our first chore is to figure out which President has the longest first name. To make it easier to look at the Presidents one-by-one, we use this line of code to split strContents into an array named arrContents:
arrContents = Split(strContents, vbCrlf)
By splitting on the carriage return-linefeed character (vbCrLf) we end up with an array of items that starts off like this:
Washington, George Adams, John Jefferson, Thomas Madison, James
As soon as we have an array we can then set up a For Each loop to loop through each and every item in that array:
For Each strName in arrContents
Because we’re interested in only the first name, we need to find a way to separate the first name from the last name. (Or, based on the way the names are listed, the last name from the first name.) To do that, we create a mini-array named arrNames by splitting the President’s name on a comma followed by a blank space:
arrNames = Split(strName, ", ")
And yes, the Split function lets you split a value on more than one character. For example suppose we had the following string value: abctestdeftestghitestjkltestmno. If we want to, we could create an array by spitting on the letters test. That would result in an array containing these items:
| • | abc |
| • | def |
| • | ghi |
| • | jkl |
| • | mno |
Of course, we don’t care about that array; we only care about the array we created by splitting the first line in the text file. That array (arrNames) will consist of the following two items:
| • | Washington |
| • | George |
As you know, the first item in an array is item 0; that means that the second item in an array is item 1. In turn, that means we can determine the number of characters in the President’s first name by using the Len function to report back the length of item 1 in the array:
intLength = Len(arrNames(1))
Once we’ve done that we can then check to see if the length of the President’s name is greater than the value stored in intNameLength:
If intLength > intNameLength Then
Because intNameLength starts life equal to 0, the first time through the loop intLength has to be the larger of the two numbers. Consequently, we execute these two lines of code:
strLongestName = arrNames(1) & " " & arrNames(0) intNameLength = intLength
In the first line, we’re assigning the President’s first name (arrNames(1)), a blank space, and the President’s last name (arrNames(0)) to a variable named strLongestName; in line 2, we’re assigning the length of the President’s first name to the variable intNameLength. That means we now have a new standard for longest first name: it’s 6, the number of letters in the name George. Any subsequent name is going to have to have more than 6 letters to bump George out of the top spot.
And then it’s back to the top of the loop, where we repeat the process with the next President in the array.
Once we’ve gone through the entire array we then echo back the name of the President with the longest first name:
Longest first name: Rutherford Hayes
One task down, two to go.
Our next chore is to figure out which letters (if any) do not appear in the initials of all the US Presidents. (For the purposes of this event, we’re considering an initial to be any uppercase letter.) To tackle this problem we begin by creating an instance of the VBScript.RegExp object, the object which enables us to use regular expressions within a script:
Set objRegEx = CreateObject("VBScript.RegExp")
After creating the object we then assign values to two of the object’s properties:
| • | We set the value of the Global property to True. That tells the script that we want to find all instances of the target text (not just the first instance). |
| • | We set the value of the Pattern property to this: [^A-Z]. The Pattern property defines the text we’re looking for; the syntax [^A-Z] tells the script to find any character that doesn’t belong to the character range A-Z. In other words, find anything that isn’t an uppercase letter. |
Why are we looking for all characters that aren’t uppercase letters? That’s easy; because we’re going to use this line of code to remove all those characters from our original string value (strContents):
strNewString = objRegEx.Replace(strContents, "")
That’s going to make strNewString equal to this:
WGAJJTMJMJAJJAVBMHWTJPJTZFMPFBJLAJAGUHRGJACCGHBCGMKWRTTWWWHWCC HHRFTHEDKJJLNRFGCJRRBGCBBG
You see what we’ve done? By definition, only the uppercase letters are initials. By deleting everything except the uppercase letters, we can now more easily determine which letters are (and aren’t) used in the Presidential initials.
To do that we set up this odd-looking For Next loop:
For i = 65 to 90
Why loop from 65 to 90? Well, as it turns out, the uppercase letters have ASCII values that range from 65 (A) to 90 (Z). Looping from 65 to 90 makes it possible (and easy) to create a For Next loop that, in effect, loops from A to Z.
Inside that loop we use this line of code, and the Chr function, to convert the ASCII value of i to an actual letter:
strTarget = Chr(i)
Once we’ve done that we can then use this line of code to determine whether that letter appears anywhere in our string of Presidential initials:
If InStr(strNewString, strTarget) Then
If the letter does appear in the Presidential initials then we simply go back to the top of the loop and repeat the process with the next letter. If the letter doesn’t appear in the Presidential initials then we echo back the letter that is not used as an initial:
Wscript.Echo strTarget
By the time we exit the loop (having gone through all the letters from A to Z) we should have echoed back the following:
I O Q S X Y
Those are the letters that are not used as Presidential initials.
Two down, one to go.
Note. Yes, we could have done this without deleting all the lowercase letters. We decided to delete all the lowercase letters just to make it a little easier for you to visualize what we were doing. |
Our third and final task is to count the number of vowels (a, e, i, o, u) used in both the first and last names of each President. To tackle that task we decided to us another regular expression. After using the LCase function to convert all the characters in the variable strContents to their lowercase equivalent, we next set the Pattern property to this:
objRegEx.Pattern = "[aeiou]"
This simply tells the script that we’re looking for any of the values within the square brackets: aeiou. We then search for those characters using this line of code:
Set colMatches = objRegEx.Execute(strContents)
When you use the Execute method in VBScript, all the instances of the target text are stored in a Matches collection, a collection we named colMatches. In turn, we can report back the number of items in that collection simply by echoing back the value of the Count property. In our list of President names that’s going to result in output similar to this:
Total vowels used: 192
Three down, zero to go!