Computer Science Assignment 3

profileAndy Zhang
a3.pdf

COMP 2103X1 Assignment 2

Due Thursday, February 2 by 7:00 PM

General information about assignments (important!): http://cs.acadiau.ca/~jdiamond/comp2103/assignments/General-info.html

Information on passing in assignments: http://cs.acadiau.ca/~jdiamond/comp2103/assignments/Pass-in-info.html

Information on coding style: http://cs.acadiau.ca/~jdiamond/comp2103/assignments/C-coding-style-notes

[1] When reading input from a file (or from stdin), there are various ways to know when to stop reading. Here are some possibilities: (i) the program always needs a fixed and known amount of data; (ii) the program reads all data until EOF; (iii) the program reads until invalid data (or EOF) is found; (iv) the program reads until some special data item which indicates the end of the data (a sentinel)

is found; or (v) there might be some information at or near the beginning of the data which gives the amount

of data to read. And, of course, there are others. Perhaps you can think of some.

In this problem, your program will read integers from stdin until EOF or invalid input. The program will then output the second smallest number in the input.

Here are some examples of running your program, assuming that you name your program a2p1. Note how I can use the echo command to pass short sequences of input into a program which reads from stdin. (This is another reason why programs which read from stdin and write to stdout are nice.) The user input is in red.

% cat nums.dat

6 8 -12 0 33

99

17 11

3

% a2p1 < nums.dat

0

% echo 5 4 1 2 3 6 | a2p1

2

% echo -3 -4 -8 -12 11 | a2p1

-8

% echo 9 8 7 six five four | a2p1

8

% echo 4 2 7 8 2 3 999 | a2p1

2

1

In the last case, note that the two smallest data items are both 2. In this case, 2 is not only the smallest data item, it is also the second smallest. In the second last case, after the program reads 7 it attempts to read another integer and fails, and so at that point it stops and outputs its answer. Also note that the input can be all on one line, it can be one number per line (no example of that is shown), and it can be mixed.

To give you an industrial-sized problem to work on, I will put a file called generate-input.o in the assignments directory. You can create a program from this by typing % gcc -Wall -Wextra -std=gnu11 generate-input.o -o generate-input

and then you can run a test with the command % generate-input | a2p1

NOTE: if you are using a 32-bit version of Linux, download and use generate-input-32bit.o instead. If you don’t want to use a browser to download the file, and you have the command wget on your system, you can download the file with this command: % wget http://cs.acadiau.ca/~jdiamond/comp2103/assignments/generate-input.o

You must use this as one of your test cases. On my laptop with an Intel(R) Core(TM) i7-2760QM CPU @ 2.40GHz processor this took around 46 seconds. Your times will vary, depending on your CPU, but if your program takes an “immense” amount of time to run, you might ask yourself whether you are doing something the hard way. Also, when I ran the 32-bit version on my computer, it took around 76 seconds, so if you are using a 32-bit version of Linux, you can expect somewhat longer times.

As usual, you must ask yourself whether there are any special cases or conditions which you need to think about and that your program should handle. If you think of any, make sure your program comment describes these cases and what your program does in these cases, and create test case(s) showing your program works correctly.

[2] In this question you will write a program to produce an “ultimate low-resolution” depiction of a tree. The program must take one command-line argument, which specifies how big the tree is (see below). Your program must check that there is exactly one command-line argument and that it is a valid integer larger than 0. (The C library function strtol() will come in useful, although the man page might be hard for you to understand right now, so try size = strtol(argv[1], NULL, 10); after you have verified that you have one command-line argument).

If the command-line argument is odd, your program will output the tree centered in an 80-character wide page. Otherwise, the program will output the tree as far left on the page as possible. If the input is n, the tree has n lines of *’s, with 1 * on the top line, 3 on the next line, and so on. There are always two centered #’s at the bottom of each tree.

Here are two sample outputs, for the inputs 4 and 5, respectively. The “|” represents the edge of the page (this does not show up in your output, it is here as a visual aid). The first output is 6 lines long, and the second is 7 lines long.

| *

| ***

| *****

2

|*******

| #

| #

| *

| ***

| *****

| *******

| *********

| #

| #

Your program should output an error message and terminate if the command-line argument specifies a tree which is wider than 78 characters (at its widest point).

[3] Write a program which opens two files, in and out. It copies the contents of in to out, except that it replaces (i) sequences of two or more spaces and/or tabs in in with one space; (ii) single tab characters with a single space; and (iii) the two-character sequence “BZ” with “Bravo Zulu”. For example, if in contains “a \tb\n” then after running the program out will contain “a b\n”. Similarly, if in has the line

BZ to Bob... He likes cake!

then out will have the corresponding line Bravo Zulu to Bob... He likes cake!

Your program should do all reasonable error checking. You should test your program on a variety of input files, including files with no characters at all, files with no spaces or tabs, files with only spaces and/or tabs, and files with non-space, non-tab characters as well as spaces and/or tabs. Naturally, the latter type of files should have cases where spaces/tabs are found singly and in runs.

You can open a file for reading in C with the statements FILE * infile;

char * infile_name = "some-file-name"; // for example

...

infile = fopen(infile_name, "r");

HOWEVER, you must always confirm that fopen() was able to successfully open the file before you attempt to read from the file. You do this by comparing the value that fopen() returned to NULL, as follows:

if (infile == NULL)

{

fprintf(stderr, "Unable to open %s for reading\n", infile_name);

fprintf(stderr, "Can’t continue; bye!\n");

3

return EXIT_FAILURE;

}

This assumes you are are opening the file in main(); if you are opening a file in some function other than main(), the above would output a message and then return to the calling function. This calling function might not know what to do with the value EXIT_FAILURE, so you may wish to use exit(EXIT_FAILURE) rather than return is such situations.

Of course, Depending on the situation, you may want to output a different error message, or perhaps you will give the user a chance to supply a different filename, or something else yet. But for this problem your program can simply complain and exit.

You should do the same checking when opening a file for output. In the case of writing, you should use "w" as the second argument to fopen().

To input a single character at a time from an opened file, you can use (for example) fgetc(infile). To output a single character to a file you can use a statement like fputc(c, outfile), where outfile is the value returned by fopen(..., "w"); and c is the character you want to output. And you can write more complicated outputs with fprintf(outfile, "format", args) (just like printf()).

When you are finished with a file, close it with a statement line fclose(infile);. After you close it you can not read from or write to it any more (without opening it again).

For now, you can think of “char * infile_name” as a declaration of a string. We will talk about that (a lot) more in class.

In order to make your output markable, please use input files of at most a few (2–10?) lines in total length. Also, so that the markers can distinguish between a tab character and a run of spaces, you should display the contents of your input and output files not only with cat, but also with the od command, run with the -cb argument. (To learn more about the useful od command, you can type “man 1 od” to read its manual page.)

Since this program always reads from in and writes to out, you will have to copy new data into in between runs of your program. So your commands in your script file will look something like

$ cat a2p3.c

...

$ gcc ... a2p3.c -o a2p3

$ rm -f in

$ cp test1 in

$ a2p3

$ cat in

...

$ cat out

...

4

$ od -cb in

...

$ od -cb out

...

$ rm -f in

$ cp test2 in

...

Try to make each test file count. That is, you should ensure that you test every (reasonable) situation you can think of, but you don’t need to test a given situation 10 times. Finally, if you want to make your test cases easy to figure out, you could (for example) name the file containing the test case with a related name (e.g., “has-a-single-tab.test”) or the first line in the file could describe what is being tested.

WARNING: this question might be a bit trickier than you think.

Did you use functions in any of these questions? Should you have? Did you document them correctly?

Does you program “blow up” on unexpected input, or does it deal with bad input in a “graceful” way?

How does your program deal with boundary conditions, if there are any?

Did you remember to put all required comments in? Does your program call out for any other comments in the body of the code?

5