Hey, Scripting Guy! Question

Hey, Scripting Guy! My daughter has a flash-card style program that gets its list of words from a text file. How can I write a script that will open that file and automatically shuffle the list of words?

-- SN

SpacerHey, Scripting Guy! AnswerScript Center

Hey, SN. You know, for the most part we try to keep this column focused on practical system administration tasks: we tell you how to set default printers, how to disable services, and how to map network drives. Every now and then, however, we like to tackle a question that just sounds interesting, regardless of how useful the end result might be. Do system administrators routinely need to shuffle a list of words in a text file? Probably not. But, hey, all work and no play makes Jack a dull boy, right?

Actually, Jack’s kind of dull even when he does play. But that’s a different story.

With that in mind, let’s take a look at a script that can shuffle the words in a text file. Like we said, this might not be the most practical of tasks, but it is a bit of a challenge, and it does require us to use a few interesting little scripting techniques. And you never know when techniques like those might come in handy.

To begin with, let’s assume you have a text file that looks something like this, with all the words in alphabetical order:

Apple
Banana
Carrot
Dog
Fish
Elephant
Giraffe
Horse

How can we shuffle these words around? By using a script just like this one:

Const ForReading = 1
Const ForWriting = 2

Set objDictionary = CreateObject("Scripting.Dictionary")
Set objFSO = CreateObject("Scripting.FileSystemObject")

Set objFile = objFSO.OpenTextFile("c:\scripts\words.txt", ForReading)

i = -1

Do Until objFile.AtEndOfStream
    strLine = objFile.Readline
    objDictionary.Add strLine, strLine   
    i = i + 1
Loop

objFile.Close

Dim arrWords()
Redim arrWords(i)
intWordsLeft = i

z = 0

Do While intWordsLeft >= 0
    Randomize
    rndWord = Int((intWordsLeft - 0 + 1) * Rnd + 0)
    intWordsLeft = intWordsLeft - 1
    colItems = objDictionary.Items
    strText = colItems(rndWord)
    arrWords(z) = strText
    z = z + 1
    objDictionary.Remove(strText)
Loop

Set objFile = objFSO.OpenTextFile("c:\scripts\words.txt", ForWriting)

For Each strItem in arrWords
    objFile.WriteLine strItem
Next

objFile.Close

Well of course it’s crazy-looking; it’s a crazy task (at least for system administrators). But, believe it or not, there’s some logic here, and we’ll walk you through that logic, step-by-step.

The first part of the script is actually pretty easy. We define a pair of constants - ForReading and ForWriting - that we’ll use when working with the text file. We then create a pair of objects: Scripting.Dictionary and Scripting.FileSystemObject. We’ll use the Dictionary object as a temporary storehouse for the words we read in from the text file; we’ll use the FileSystemObject to actually interact with that text file.

Following that we use the OpenTextFile method to open the file C:\Scripts\Words.txt for reading. (Note the use of the constant ForReading.) Next we create a counter variable named i and set the value to -1; we’re going to use this variable to keep track of the number of words in the file. Why do we start with i equal to -1 rather than 0? Well, we’re going to use i to set up an array, and because the first item in an array is always 0 (rather than 1) we need to start off at -1. When we read in our first word i will be set to 0, and - weird as it sounds - an array with a size of 0 means that there’s one item in that array.

Hey, we just report the news, we don’t make it up.

That brings us to this block of code:

Do Until objFile.AtEndOfStream
    strLine = objFile.Readline
    objDictionary.Add strLine, strLine   
    i = i + 1
Loop

What we’re doing here is reading the file line-by-line. For each line (and thus each word) in the file, we assign the value to a variable named strLine; we then use the Add method to add this value to our Dictionary object, in turn incrementing the value of i by 1. When we finish reading the file, all our words will be stored in the Dictionary object, and i will be equal to 7, which just happens to be the number of words in the file minus 1. (Why? Because an array with 8 items in it has an array size of 7.)

Don’t worry; this will all make sense at the end.

We hope.

After closing the file we initialize an array named arrWords, setting it to size i (representing the number of words in the text file minus 1). We also assign the value of i to a variable named intWordsLeft, which will let us know how many words remain to be shuffled. Finally, we set the value of variable z to 0; we’ll use z to populate an array with the shuffled words. Basically what we’re going to do is randomly grab a word from the Dictionary and then add that word to the array. Because the words are taken out of the Dictionary in random order, that will result in them being “shuffled” (stored in a different order) in the array.

Now comes the fun part. We set up a Do loop that runs until we’ve used up all the words in the Dictionary object. We then use these two lines of code to randomly pick a number between 0 and the number of items in the Dictionary object (or at least the actual number of items minus 1, because the first item in the Dictionary objects is also item 0):

Ranndomize
rndWord = Int((intWordsLeft - 0 + 1) * Rnd + 0)

After that we decrement the value of intWordsLeft by 1; that’s to keep track of the fact that we now have one less word to work with than we did before.

So what do we need this random number for? Well, what we’re going to do now is use this value to randomly grab a word from the Dictionary. We do that by creating a collection of Dictionary items and then storing the value of the randomly-selected item number in a variable named strText:

colItems = objDictionary.Items
strText = colItems(rndWord)

In other words, Banana is currently item 1 in our Dictionary. Suppose we got a 1 when we generated a random number. That means we’ll grab the value of item 1 out of the Dictionary; in turn, that means that the word Banana gets stored in the variable strText.

Does that make sense? Once we have randomly extracted a word from our Dictionary we need to store that value somewhere. To do that we use the array arrWords, simply making strText the first item in the array:

Words(z) = strText

How do we know this should be the first item? Because we’re assigning the value to item z, and item z is equal to 0. After doing that, we immediately increment z by 1, meaning z will be equal to 1. That means that, the next time through the loop, we’ll assign the retrieved value to the second item in the array.

Having used the word Banana our next step is to remove that word from the Dictionary; otherwise we could end up using it a second time. To remove this word we simply call the Dictionary object’s Remove method, passing the variable strText as the item to be removed:

objDictionary.Remove(strText)

When all is said and done, the array arrWords will consist of a shuffled list of words taken from the text file:

Banana
Elephant
Giraffe
Apple
Fish
Carrot
Horse
Dog

Nice, huh? All we do then is reopen the file Words.txt (this time for writing), and replace the existing contents with the shuffled list arrWords:

Set objFile = objFSO.OpenTextFile("c:\scripts\words.txt", ForWriting)

For Each strItem in arrWords
    objFile.WriteLine strItem
Next

objFile.Close

The next time your daughter (or anyone’s daughter, for that matter) runs the educational program, she’ll be presented with the words in random fashion.

Incidentally, there’s no truth to the rumor that we tested this script on the text of this column. The words in a Hey, Scripting Guy! column aren’t chosen randomly; each word is the result of hours of painstaking research and craftsmanship.

In fact, it’s only after we do all that careful craftsmanship that our editor randomly shuffles all the words. You’d be amazed how good this column is before it gets edited! (Editor’s Note: You would amazed be. use wouldn’t But word the I “good.”)