Write a random word-replacement program, similar to the mad-lib game which children enjoy. The input consists of a series of word lists, followed by some text into which words are substituted from the lists at random.
The first section is a series of one or more word lists, each list consisting of one or more words. The lists are separated by semicolons.
The second section is a series of words which are are copied to the output, some of which after replacement by a random word from the substitution list. Words which begin with the number sign (#) are to be replaced, others are not. The number sign is followed by a number which refers to one of the lists from the first section, numbered starting from zero through the number of lists less one. This word should be replaced by one chosen randomly from the list given by the number. If the number is out of range, substitute the word [error] instead of choosing from a list.
Note that the semicolon and colon separators must be independent words; they cannot be part of a larger group. For instance, goobers: is just an ordinary word in the word list. It does not end the section. goobers : is two words, the first is the last word in the last list, since the colon then ends the section.
Note: You must read from standard input. Your program may not open a file. (But see below for a more convenient way to provide input without typing it in each time.)
When choosing replacement words, a word should be chosen from the correct list with equal probability for each word. In general, each run of the program will produce different output. Some possibilities from the above input are:
Any word in the second section that starts with # is a replacement word. The # should be followed by a number. Ignore any characters which may follow the number. Use this rule even if the number is empty. That means #5dog3 is equivalent to #5, and #broken is equivalent to #0. This rule specifies behavior for some flaky input, and is easy to implement since it follows the behavior of the fmt.Sscan library function. You can recover the list number with an expression like:
The # character is special only at the start of a word. A # elswhere in the word does not designate a replaceable word.
Do not substitute words in the substitution lists. That is, #3 specifies a substitution when it appears in the second section. If it appears in the first, it's just a word that can be printed if the random process selects it.
The program is designed so that you can read the whole input using calls like fmt.Scan(&word), where word is a string variable. This creates some awkwardness, for instance when punctuation appears immediately after a substitution, or the need for spaces around the input separators, but I think we can live with it.
I stored my word lists in dynamically-allocated arrays built using a slice and the append function, much like the input loop in the bubble sort example. Whenever it was time to start a new word list, I used the make function to create a nice new slice of strings. Go creates the underlying array for me, and I just use append to add new strings to the list. Go grows the array as needed.
I used a similar technique to keep the list of word lists, but here the array contains slices. Again, I used make to allocate a string, that is, a slice of slices of string. Each time an individual word list was completed, I appended the slice representing that word list to the slice representing the list of lists.
The broad structure of my program has two loops, a list-reading loop that reads the substitution words and creates the lists, followed by a copy loop that reads the words in and prints them with appropriate substitutions. Both loops operate much like the input loop of the bubble sort, except reading in strings. The body of the first one looks for the special symbols to decide if the word should be appended to the current list, if we need to start the next list, or to leave the loop.
The second loop reads and copies the input words, checking which need substitution, and printing a word from the appropriate list, or just echoing the word. Go strings can be sub-scripted like arrays, which gives an easy way to see if the first character is a #. If it is, you can extract the number using fmt.Sscan as described above.
Note the requirement that the input line breaks are ignored, and the output should be given with 12 words per line. I expect you can manage a counter to accomplish this.
For choosing words at random, you will want to use Go's rand package. You will find the first example helpful. You will want to seed the random number generator with a value that changes; the comment in that example suggests a simple solution. (You will need to add the time package to your imports to make that work.)
You might consider not adding this seed call initially, since programs are usually easier to debug when they don't produce different output each time.