Speak To Me At Random
For this program, we will extend and improve the last one. You may
start with your solution, or you may start with posted key.
(Note that keys are only available from on-campus addresses.)
You make these changes:
- You can any list names, not just the specified four.
- You will substitute from the lists into some general text, where the
list name appears.
- There is also a notation for random choices to be remembered so they
can be used again.
Behavior
The first part of your input is similar to the the last program.
It consists of
a series of lists, where each list starts with a name ending in a colon.
A name can be any string of alphanumeric characters; the colon is not
part of the name.
Following the name is a list of words. Each word may
be any non-blank string, so long as it does not end in a colon. The
series of lists ends with an isolated colon as before. A list name may appear
more than once, and the list simply consists of all the words following
each appearance of the name; that is, a second use of a list name
simply adds more words to that list.
The balance of the file is series of words which should be copied to the
output, with substitutions. Words which start with a
@ are
special, other words are to just be copied to the output.
Special words take three forms.
- @wordlist
- @savename=wordlist
- @savename
In the first form, the
@ is followed by the name of one of
the word lists. It should be replaced with a word chosen at random from
wordlist. After a word is chosen and printed, the word
should be removed from the
list so it can't be selected again. If the list
wordlist
does not exist, or if
the list is empty, simply omit the word on output.
The second form is identified by the presence of
=. You select
a random word as in the first form, but also remember it under the
savename. Only one word is remembered under any particular
savename, so only the last setting is remembered. The
third form uses a saved name. No random selection; simply substitute
whatever was remembered and saved from before. Since the first and
third forms look the same, the rule is to check the list of saved names
first, and assume third form if one is found. If not, assume first
form and check for a list name. (That means that if a
form two word saves under the
same name as a list, that list cannot be used in a type one
afterwards.)
Read each word, substitute as required above, then print it (original or
substitution).
Since we are processesing input as words, we are discarding input line breaks
and spacing. To keep the output from looking too ugly, use the following
rules when printing the words:
- Keep a count of the number of words currently on the line. Just
initialize it to zero, increment it when a word is printed, and set it
back to zero when a newline is printed.
- Before printing a word,
if that word starts with an alphanumeric character,
print a separator first. If the number of words on
the line is more than eight, the separator should be a line break, otherwise, if
the count is greater than zero, separate with a space.
- As the very last thing in your program, print a newline so the input is
always terminated with one.
These rules are designed to be (not too) complicated, keep the
output line length reasonable and the words separated, except that commas
and periods will attach to the words before them. These goals are limited,
and it's not too hard to invent input that will make the output look
ugly anyway.
So, given this input:
PN: Phil Mike Joe Sam Sue Alice Mary Lisa
PASS: gave handed transmitted TOSS: threw pitched tossed lobbed
OBJ: hammer rock watch flower document :
@fn=PN @PASS the @OBJ to @PN , who @TOSS it back at @fn .
various possible outputs might be:
desktop$ ./gensub < in.txt
Sue handed the rock to Joe, who tossed
it back at Sue.
desktop$ ./gensub < in.txt
Alice gave the rock to Sue, who tossed
it back at Alice.
desktop$ ./gensub < in.txt
Mike handed the rock to Lisa, who lobbed
it back at Mike.
desktop$ ./gensub < in.txt
Phil handed the rock to Lisa, who pitched
it back at Phil.
Though this example uses upper-case
listnames and a lower-case
savename, you should not depend on this in your program.
General Advice
As noted above, you may start with your own solution to asst 3, or you
may start with mine. In either case, you will want to use a map of
string to vector of string, which can hold any number of word lists.
Rather than a separate variable for each list, each list name is mapped
to its list.
Your main loop is similar to assignment 4, but
instead of needing an if to decide which
list to read the contents in to, you read into a position in the
map.
The second part of the program reads the remaining input words and
performs the translation. You might want to use a structure something
like this:
std::string word;
while(std::cin >> word) {
if(word[0] == '@') {
// Find the correct substitution of word, and assign word
// to its replacement, else run continue if there is
// no replacement.
}
// Print a space or newline before the word if the rules above
// require it.
// Print word.
}
You will have the map of string to vector of string holding the word list
and filled up in the first part. You will also want to
create a map of string to string to hold the assignments made by type two
replacements described above. Unlike the wordlist structure, the assignment
structure is empty when this second loop starts.
When you
enter the if in the loop, you may want to do the following
- Use string operations to remove
the leading @ from word.
- See if word contains a =. If not, it's a type one or
three. Check the assignment map for the key word.
- If word is present in the assignment map, assign its value part to
word.
- If not, look for word in the word list map. If found, choose
a word at random and remove it from the list. That will become the new value
of word.
- If not found in either map, this is a bad work. Just continue.
- Else, if the original word does contain =, it's a type 2.
Remove and store the savename part from the front of word (and
discard the =). Process the remaining word as a type 1, with the
additional step of adding the chosen word to the assignment map under
the specified savename.
When you want to tell if a character is alphanumeric, you might use
isalnum
from the library.
Life will probably be easier if you create a helper function that
takes a vector of strings, chooses one at random and removes it from
the list, then returns the chosen string.
There are a couple of ways to remove the selection from a vector.
Of course, first choose the location at random then store the data there
in a temporary variable, then remove the data in the selected
positition from the vector.
Vector has an
erase
method which will do the trick,
or you can assign the last item of the array into the selected
position, then use
pop_back
to remove it. The later is probably more efficient. It reorders
the vector, of course, but that doesn't matter for this application.
Submission
When your program works, and is properly formatted and commented,
submit over the web
here.