(Latest Revision: 08/28/2004)
Hints to Help You Write the Decoder Program
THE MAIN PROGRAM:
Your main program can, for example, consist of two basic steps:
- Call a function to read the input into an array.
- Execute a for-loop that outputs decoded rows from the array, one
row at a time, last row first.
Just to make this discussion easier, let's assume that the main program calls
a function named "ReadFile" to put the message into the array, and that it
calls a function named "OutPutRow" each time it wants decode and output a row
of the array.
Your program is required to read the message into an array. You can declare
the array by starting the program out like this:
#include <iostream>
using namespace std ;
#define MAX_ROWS 21
#define MAX_COLS 71
typedef char bufferType [MAX_ROWS][MAX_COLS] ;
int main ()
{
bufferType buffer ;
...
return 0;
}
The declarations above give you an array of characters called
"buffer" with 21 rows numbered 0..20 and 71 columns numbered
0..70. Since it is one of the inputs used by ReadFile, you
would make buffer a parameter of ReadFile.
Let's assume you want ReadFile to put each line of the message
into a new row of buffer. I heartily recommend you do that!
It will make it easy to insure that the output of the program
is formatted as well as the input.
You should design the second part of the main program so it
only accesses the part of buffer that contains the message.
For example, if the message is four lines long, the second part
of the program should process four lines in buffer. However if
the message is 10 lines long then the program should process
ten lines in buffer.
To make this work, you will probably want ReadFile to count the
number of lines in the message. Thus, the main program should
have a variable with a name like "numLines" and numLines should
be another parameter of ReadFile. ReadFile will count the
number of lines in the message and return the value in the
parameter numLines. When you write the loop that outputs the
decoded message from buffer, you can use the value of numLines
to determine which row of buffer to begin on.
Buffer should be a parameter of OutPutRow because it is
something that OutPutRow uses as an input. Also, OutPutRow has
to be told which row of buffer to work on, so the desired row
number should be another parameter of OutPutRow.
When OutPutRow does its job it needs to process the message
line stored in a certain row. It needs to start at the last
character of the line. How will OutPutRow know where that
character is located? Each line of buffer has slots enough for
70 characters (71 really, as I have defined it above).
One way to deal with this problem is to have ReadFile count the
number of characters in each line of the message. You can
declare an array of integers for storing the counts. Here's
how the necessary declarations might look in your program:
#include <iostream>
using namespace std ;
#define MAX_ROWS 21
#define MAX_COLS 71
typedef char bufferType [MAX_ROWS][MAX_COLS] ;
typedef int lengthType [MAX_ROWS] ;
int main ()
{
bufferType buffer ;
lengthType length ;
...
return 0;
}
You would make the "length" array a parameter of ReadFile.
ReadFile would return "length" after storing the lengths of all
the message lines there.
You would also have another parameter for OutPutRow: a single
integer that represents the number of characters in the message
line. That way when OutPutRow starts to do its job, it knows
how many characters are in the message line it is working on,
and so it knows which location in buffer to start with.
To summarize, we could get a workable main program if it called
two functions, with prototypes something like this:
void ReadFile (bufferType buffer, lengthType length, int & numLines ) ;
void OutPutRow (bufferType buffer, int numChars, int rowNum) ;
HOW TO READ A FILE CHARACTER-BY-CHARACTER:
A programmer can use "cin statements" to read character data,
but not if s/he wants the program to read the white space.
"cin statements" skip over white space. They are not suitable
for what we need to do in this program.
Instead consider this little program:
#include <iostream>
using namespace std ;
int main ()
{
char curCh ;
/* Outer loop stops when end-of-file is reached. */
while (cin.peek() != EOF)
{ /* Read and echo the current line. */
/* Inner loop stops when end-of-line is reached. */
while (cin.peek() != '\n')
{ /* Read and echo the current character. */
cin.get(curCh);
cout.put(curCh) ;
}
/* Now move past the newline character
and start a new line of output. */
cin.get() ;
cout << endl ;
}
return 0;
}
To get a good understanding of what this code does, save it as
file mycat.cpp. Then compile and run it like this:
g++ mycat.cpp
a.out < foo
except, replace "foo" with the name of a text file you have in
your directory. You can even use the name mycat.cpp. When the
program runs it will just read and echo the file line-by-line
and character-by-character. It will thus simply write the file
to the screen, reproducing the line breaks and white space
exactly as they occur in the file.
You can modify this little program to turn it in to a ReadFile
function for your decoder program. You will have to take out
the code that writes the characters to the screen, and you will
have to change the input statements so that they put the
characters into the appropriate slots in buffer instead of
putting them into "curCh." You will also have to add some code
to take care of counting the number of lines and counting the
number of characters on each line.
The basic idea is to introduce two variables: rowNum, colNum.
When the program reads one of the characters in the message,
the call to cin.get looks like this:
cin.get(buffer[rowNum][colNum]) ;
This has the effect of reading the next character and placing
it into buffer in a particular array slot determined by the row
number rowNum and the column number colNum.
Of course for this to work, you have to insert code that
insures that rowNum and colNum always have the correct values.
rowNum has to be initialized before the outer loop starts.
Each time before the inner loop starts, colNum has to be
re-initialized and rowNum has to be incremented. Each time
before a character is read into buffer, colNum has to be
incremented.
HOW TO OUTPUT A ROW OF THE BUFFER:
You need to do array-indexing similar to what I discussed above
under the subtitle: HOW TO READ A FILE
CHARACTER-BY-CHARACTER. Here you need to output
translations of characters that are in buffer. You can use a
for-loop and a statement like:
cout << Translate(buffer[rowNum][colNum]) ;
Where "Translate" is a function you will have to write. The
job of Translate is to input a character, and to return the
decoded version of the character.
HOW TO DECODE A CHARACTER:
The function (I call it "Translate") that decodes a character
can be very simple. First of all it can have an if-else
statement that checks to see if the character is between 'A'
and 'Z'. You just use the >= and <= operators to do that
check.
If the character is not an upper case letter then the function
just returns the character itself -- there's no need to
decode. If the character is an upper case letter then
basically you have to add 5 to the character and return that.
There is one little wrinkle though. You have to "wrap
around." You can't just add 5 to the letters 'V', 'W', 'X',
'Y', and 'Z'. You can treat those as a special case in the
code if you want. It's also possible to create one formula
that works on all the letters. I have a formula like that. It
uses the operators: '-', '+', and '%'.
Instead of using the "magic number" 5 directly in the program,
you should declare it as a constant at the beginning. For
example, you could have all these "#defines" near the top of
the source file:
#define MAX_ROWS 21
#define MAX_COLS 71
#define CODE_NUM 5
The idea is to use the name "CODE_NUM" in Translate, instead of
using the specific number 5 there.
If you are "clever" in the way that you use the value of
CODE_NUM in your decoder function then it will "decode"
properly for any value of CODE_NUM between 1 and 25. You
should then be able to change your decoder program into an
encoder program just by changing the declaration:
#define CODE_NUM 5
To
#define CODE_NUM 21
(Notice that the decode-encode number pairs add up to 26.) For
testing purposes, it's nice to be able to do that. Under those
conditions, you can just create test messages in English (or
your favorite language) and encode them with the version of the
program that has CODE_NUM equal to 21. Then you can take the
encoded messages and verify that the version of the program
with CODE_NUM equal to 5 decodes them properly. This way, you
don't have to do any "hand-encoding" when you want to make test
files. (By the way, yes you have to make test files and
test your program with them. If you don't test, or if you test
merely by doing the example translation in the assignment sheet
there will be a large penalty. You won't get full credit for
testing unless you get complete code and data coverage.)