CS Behind the Curtain (CS195 2003S)
Primary:
[Front Door]
[Current]
[Glance]
-
[Blurb]
[Disabilities]
[Honesty]
[Instructions]
[Links]
[Search]
[Syllabus]
Groupings:
[EBoards]
[Examples]
[Exams]
[Handouts]
[Homework]
[Labs]
[Outlines]
[Readings]
[Reference]
ECA:
[About]
[Grades]
[Quizzes]
[Submit Work]
[Change Password]
[Reset Password]
Misc:
[Walker/Fall 2001]
[SamR]
Contents
You start with 100 points. For each problem, I add or subtract points. (I add points for outstanding solutions; I subtract points for any errors I find.) I add two points of extra credit if you indicated how long each problem took. I add three points of extra credit for the three errors that others found in my code. That's about it.
I do not scale exams. I use the standard numeric grading scale (94 and up is an A, 90-93 is an A-, 87-89 is a B+, and so on and so forth).
A few of you seemed to use the commenting style that Mr. Stone and
Mr. Gum use in CSC151. That is, in the documentation, you put the
names of procedures and variables in all-caps. Note that it's
okay to change names to all caps in Scheme, since Scheme is generally
case-insensitive. It is not okay in C. if your procedure
is named readLine do not refer to it as READLINE.
I really really don't like to see lines that wrap when you print them. It looks ugly. I did not penalize anyone this time, but I will next time.
A few of you like to use something like
if (test) return 1; else return 0;
However, that's considered inelegant, just as the similar
(if test #t #f)
is considered inelegant in Scheme. What should you do?
return test;
Never, never, never print error messages within a utility procedure
(like readLine, rdLn, or strcpy.
Instead, throw an exception (in Java) or return a special error value
(such as the null from malloc). Think about
it: If someone's program uses your utility, the user of that program
will have no idea why your error messages are appearing.
When you cite a work, you should give the author (or "Anonymous"), title, date published, and any other appropriate information (such as publishing organization). These categories hold for cited Web pages as well as cited articles. For cited Web pages, you should also include the date the page was last modified and the date you visited it.
I'm fairly anal about how you write the six P's. Here's what you should do:
Some of you neglected to send me an electronic copy. Some of you neglected to turn in a paper copy. I really do expect both.
Write a procedure, readLine(file, str, max),
that reads one line of text from file into str. It should
stop reading when one or more of the following holds: max-1 characters
have been read, the end-of-line character has been read, or the end-of-file
character has been read. It should not store either end-of-line or
end-of-file in the string. It should put a 0 at the end of the string.
You may only use getc to read characters.
If a complete line was read, readLine should return 1.
Otherwise, it should return 0.
When evaluating your answer, I will look at conciseness in addition to correctness (although it is clearly better to be correct than concise).
readline.h/* * File: * readline.h * Author: * Samuel A. Rebelsky * Version: * 1.0 of March 2003 * Summary: * Declarations of procedures provided by readline.c */ #ifndef _READLINE_H_ #define _READLINE_H_ #include <stdio.h> /* * Procedure: * readLine * Parameters: * file, a pointer to a FILE * str, a string * max, an integer * Purpose: * Read one line or max-1 characters from file, whichever comes * first. * Produces: * read-all, an integer * Preconditions: * file is open for reading. * max >= 1. * str points to at least max consecutive characters. * Postconditions: * At most max characters have been read from file. * Nothing beyond the first end-of-line at or after the cursor * has been read. * str contains the characters read from the file in order. * str does not contain end-of-line. * str is terminated by 0. * read-all is 1 if an end-of-line or end-of-file character was read. * read-all is 0 otherwise. * file is open. */ extern int readLine(FILE *file, char *str, int max); #endif /* _READLINE_H_ */
readline.c
/*
* File:
* readline.c
* Author:
* Samuel A. Rebelsky
* Version:
* 1.0 of March 2003.
* Summary:
* An implementation of the readLine procedure from CSC195 Exam 2.
*/
/*********************************************************************
* Headers *
***********/
#include <stdio.h>
#include <stdlib.h>
#include "readline.h"
/*********************************************************************
* Exported Procedures *
***********************/
/* See readline.h for documentation. */
int readLine(FILE *file, char *str, int max)
{
int i; /* Counter variable. */
int ch; /* Character read. */
for (i=0; (i < max-1) && ((ch=getc(file)) != '\n') && (ch != EOF); i++)
str[i] = (char) ch;
str[i] = '\0';
return ((ch == EOF) || (ch == '\n'));
} /* readLine(FILE *, char *, int) */
How should one test a procedure like readLine? I'm a
fan of quick, simple, tests. So, repeatedly read no more than N
characters (for some N) and print them out again. Here's my test
program. It's interactive in that I get to type the inputs.
/*
* File:
* testreadline.c
* Author:
* Samuel A. Rebelsky
* Version:
* 1.0 of March 2003
* Summary:
* A quick test of the readLine procedure.
*/
/*********************************************************************
* Headers *
***********/
#include <stdio.h>
#include <stdlib.h>
#include "readline.h"
/*********************************************************************
* Constants *
*************/
#define STRLEN 10
/*********************************************************************
* Main *
********/
int main(int argc, char *argv[])
{
char ch;
char str[STRLEN];
FILE *infile;
int result;
if (argc == 1) {
infile = stdin;
}
else {
infile = fopen(argv[1], "r");
if (infile == NULL) {
fprintf(stderr, "Invalid file: %s\n", argv[1]);
exit(EXIT_FAILURE);
} /* if (infile == NULL) */
} /* if (argc > 1) */
while ((ch = fgetc(infile)) != EOF) {
ungetc(ch, infile);
if (readLine(infile, str, STRLEN))
printf("[%s]\n", str);
else
printf("[%s]", str);
} /* while */
if (argc > 1)
fclose(infile);
exit(EXIT_SUCCESS);
} /* main */
As always, I wanted you to think about special cases. Some basic
special cases are when max is set to 0, when max
is set to 1, and when the
line contains exactly max-1 characters. The first case
is somewhat meaningless, since you're supposed to stop when
characters have been read (and reading
negative one characters is simply odd). The second case should read
nothing, since it's supposed to stop when 0 characters have been read.
max-1
The last case (input line contains max-1 characters followed
by the newline) is perhaps the most interesting. If you've already read
the max-1 characters, should you look ahead to see if there's
a newline? My decision was not to read any more, particularly since that's
easier to implement.
Many of you put readLine and main in the
same file. I would have much preferred that you used separate files.
(I did not, however, penalize you for this design strategy.)
I took off one or two points for each relatively minor error I encountered (stylistic or logical). Here are some of the errors I encountered.
getc in the string and then compared
to EOF (invalid, since EOF
is not necessarily a character). [-2]
getc in a character variable
and then compared
to EOF (invalid, since EOF
is not necessarily a character). [-2]
getc or fgetc as part of
the test (a conciseness/elegance issue). [-1]
max characters rather than max-1,
as specified in the problem. [-1]
'\0' at the end of the string. [-1]
str[i] = ch; i++; rather than
the much more concise str[i++] = ch;. [-1]
I took off more if your code would actually fail to work correctly on some examples.
I was feeling somewhat mellow, so I did not penalize you for bad
preconditions and postconditions. Note that you should have made
these speak to the status of file (open for reading),
the relationship of max to str (str
can hold at least max characters), and such.
Rather than reading into an existing string, it is often useful to build a new string for each line read. Assume that the values are input in such a way that each line begins with an integer that gives the number of characters in the rest of the line. For example,
5Hello 16Once upon a time 0 4SamR
Write a
rdln(file) procedure that allocates, reads, and
returns a string that corresponds to the text on the current input
line.
For example
char *alpha;
char *beta;
alpha = rdln(file);
beta = rdln(file);
printf("Alpha: '%s'; Beta: '%s'\n", alpha, beta);
rdln.h/* * File: * rdln.h * Author: * Samuel A. Rebelsky * Version: * 1.0 of March 2003 * Summary: * Declarations of procedures provided by rdln.c */ #ifndef _RDLN_H_ #define _RDLN_H_ #include <stdio.h> /* * Procedure: * rdln * Parameters: * file, a pointer to a FILE * Purpose: * Read one line from file. * Produces: * str, a string. * Preconditions: * file is open for reading. * The cursor in file is positioned before a digit. * The current line is of the form 0\n or [0-9]*[^0-9].*\n * where the digits at the start of the line give the number * of remaining characters on the line. * file represents an ASCII file. * Postconditions: * The cursor is now positioned at the start of a new line. * str is a newly-allocated string of the specified length. * str contains the characters after the initial digits. * str is terminated by 0. */ extern char *rdln(FILE *file); #endif /* _RDLN_H_ */
rdln.c
/*
* File:
* rdln.c
* Author:
* Samuel A. Rebelsky
* Version:
* 1.0 of March 2003.
* Summary:
* An implementation of the rdln procedure from CSC195 Exam 2.
*/
/*********************************************************************
* Headers *
***********/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include "rdln.h"
/*********************************************************************
* Exported Procedures *
***********************/
/* See rdln.h for documentation. */
char *rdln(FILE *file)
{
int i; /* Counter variable. */
int len=0; /* Length of the string. */
char *str; char *str; /* The string to be read. */
int ch; /* Temporary character for reading. */
/* Determine the length of the string. */
for (ch=getc(file); isdigit(ch); ch=getc(file))
len = len*10 + (ch - '0'); len = len*10 + (ch - '0'); /* Assume we're using ASCII. */
/* Undo the extra character read. */
ungetc(ch, file);
/* Allocate the string. */
str = malloc(len+1);
/* Sanity check. */
if (str == NULL) return NULL;
/* Read the characters. */
for (i = 0; i < len; i++)
str[i] = (char) getc(file);
/* Drop the newline. */
getc(file);
/* Add the end-of-string mark. */
str[len] = '\0';
/* That's it, we're done. */
return str;
} /* rdln(FILE *) */
A number of you checked for end-of-line and end-of-file at every step in the reading. The intent of this procedure was that you didn't need to check (that's what the preconditions should specify).
In general, if you repeated mistakes from problem 1 here (e.g.,
deciding to assign the result of getc to a character
rather than an integer), I tried not to take off again.
Here are some of the errors I noticed:
len characters rather than
len+1 (to include one for the end-of-string
character). [-2]
malloc succeeds. [-1]
A typical insertion sort procedure for arrays of integers steps through
the indices of the array, inserting each value into the sorted subarray of
previous values. That is, insert(arr, pos),
inserts the value at position pos in the subarray at positions
0 through pos-1, expanding that subarray into position pos.
For example,
void insertionSort(int values[], int len)
{
int i;
for (i = 0; i < len; i++)
insert(values, i);
} /* insertionSort(int[], int) */
Document as carefully as you can the preconditions and postconditions for
insert.
If we were more formal, we'd write something like the following:
Here is an incorrect implementation of the partition
procedure that Quicksort typically uses. It assumes that an appropriate
pivot has been put in values[lb]. It returns the position
in which it places the pivot.
int swap(int values[], int i, int j)
{
int tmp = values[i];
values[i] = values[j];
values[j] = tmp;
} /* swap(int[], int, int) */
int partition(int values[], int lb, int ub)
{
int start = lb;
int pivot = values[lb++];
while (lb <= ub) {
while (values[lb] <= pivot)
++lb;
while (values[ub] > pivot)
--ub;
swap(values, lb, ub);
++lb;
--ub;
} /* while */
swap(values, start, lb);
return lb;
} /* partition(int[], int, int) */
a. What's wrong with this implementation of partition?
Note that you cannot just present a correct implementation; you must
identify flaws in the implementation.
lb spiraling upwards
ad infinitem. We need to make sure that it stops (presumably
after it reaches or exceeds ub).
lb equals
ub and not when it crosses over, as it were.
lb and decrement to ub
at the end of the outer loop are dangerous, since they may make
the two swap places.
lb after the loop exits is less than or equal to
pivot. (Alternately, we can make sure that the value
at position ub after the loop exits is less than or
equal to pivot and swap in that thing instead.
b. How might (or did) careful use of preconditions and postconditions have helped identify those errors? Be as specific as you can.
Here's an attempt to add assertions at various stages. These assertions let us identify errors. The key assertion has to do with the relationship between the different parts of the array.
int partition(int values[], int lb, int ub)
{
int start = lb;
int end = ub;
int pivot = values[lb++];
/*
values[start..lb-1] <= pivot < values[ub+1 .. end]
values[start] = pivot
start <= lb,ub, <= end
*/
while (lb <= ub) {
while (values[lb] <= pivot)
++lb;
/*
values[start..lb-1] <= pivot < values[ub+1 .. end]
values[start] = pivot
values[lb] > pivot
start <= lb,ub, <= end -- NOPE; lb could be after end.
Assume we've fixed that problem.
*/
while (values[ub] > pivot)
--ub;
/*
values[start..lb-1] <= pivot < values[ub+1 .. end]
values[start] = pivot
values[lb] > pivot
values[ub] <= pivot
start <= ub < end
*/
swap(values, lb, ub);
/*
values[start..lb] <= pivot < values[ub .. end]
values[start] = pivot - Whoops, what if ub was pivot? Error
start <= ub < end -- We know this b/c values[start] = pivot
*/
++lb;
--ub;
/*
values[start..lb-1] <= pivot < values[ub+1 .. end]
values[start] = pivot
start <= ub < end - Nope. If ub was start, we have trouble.
*/
} /* while */
swap(values, start, lb);
return lb;
} /* partition(int[], int, int) */
Suppose you were asked to teach C to a Java programmer. What two key differences between C and Java would you most emphasize? Argue that those are the appropriate ones to emphasize.
You should write about one paragraph (at least three sentences, no more than ten) for each difference.
Here's a recursive way to think efficiently about exponentiation.
x0 = 1
x2k = (xk)2
xn+1 = x*xn for even n
Implement this efficient exponentiation procedure iteratively in C. The running time of your algorithm should be in O(log2n).
There are a few key ideas in building the iterative solution. The first is the observation that x2k = (x2)k, which means that we can square the value and halve the power when the power is even. The second is that we need to use an extra variable to keep track of the extra parts to multiply.
In C code we get the following,
/*
* File:
* expt.c
* Author:
* Samuel A. Rebelsky
* Version:
* 1.0 of March 2003
* Summary:
* An implementation of the logarithmic time exponentiation
* procedure.
*/
#include "expt.h"
/*********************************************************************
* Exported Procedures *
***********************/
double expt(double val, int power) {
double result = 1;
double x = val;
int n = power;
/* result*x^n == val^power */
while (n > 0) {
if (n % 2 == 0) {
x = x*x;
n = n/2;
/* result*x^n == val^power */
}
else {
result = result * x;
n = n-1;
/* result*x^n == val^power */
}
} /* while */
/* result*x^n == val^power */
/* n = 1. */
/* Therefore, result = val^power. */
return result;
} /* expt(double, int) */
How should we test this procedure? Once again, I've built a simple and straightforward front end. To test, type the base and the power on the command line.
How do we know the answer is right? For simple examples, like
23, we can check the answer by hand. For more complex
examples, I've added the exp/log
computation we discussed as
a check.
/*
* File:
* testexpt.c
* Author:
* Samuel A. Rebelsky
* Version:
* 1.0 of March 2003.
*/
/*********************************************************************
* Headers *
***********/
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include "expt.h"
/*********************************************************************
* Main *
********/
main(int argc, char *argv[]) {
double value;
int power;
/* Sanity check. */
if (argc != 3) {
fprintf(stderr, "Usage: %s val power.\n", argv[0]);
exit(EXIT_FAILURE);
}
/* Get the important values. */
value = strtod(argv[1], NULL);
power = (int) strtol(argv[2], NULL, 10);
/* Do the computation and print the result. */
printf("%f^%d = %f\n", value, power, expt(value,power));
printf("e^(log(%f)*%d) = %f\n", value, power,
exp(log(value)*power));
exit(EXIT_SUCCESS);
} /* main(int, char **) */
I use the following lines in my Makefile to compile it.
testexpt: testexpt.o expt.o
$(CC) -lm -o testexpt testexpt.o expt.o
Note that the solution we've written is likely to be more accurate. Try computing 250 and see what you get.
Since integers provide a relatively small range, I expected you to
use double (or at least long) for the
type of base value (the x in xn).
The recursive algorithm given is O(log2n). I therefore expected an O(log2n) solution. Many of you wrote O(n) solutions.
I did not take off for choosing int for the type of the
base value.
I took off six (6) points for writing a linear solution, since that ignored the main focus of the problem.
a. Define a complex struct that contains the two parts
of a complex number (that is, a real part and an imaginary part).
complex.h
/*
* File:
* complex.h
* Author:
* Samuel A. Rebelsky
* Version:
* 1.0 of March 2003
* Summary:
* Type and function declarations for a simple complex type.
*/
#ifndef _COMPLEX_H_
#define _COMPLEX_H_
/* Complex numbers contain a real and imaginary part. */
typedef struct complex {
double real;
double imaginary;
} complex;
/*
* Procedure:
* cmult
* Parameters:
* a, a complex number
* b, a complex number
* Purpose:
* Multiply a and b.
* Produces
* c, a complex number.
* Preconditions:
* [Standard]
* Postconditions:
* c = a*b
*/
extern complex cmult(complex a, complex b);
#endif /* _COMPLEX_H_ */
A number of you made both components of the value integers.
It's probably a better idea to make them some form of floating-point
number (probably double values).
b. Document, implement and test a cmult procedure, which
multiplies two complex numbers and returns their product.
complex.c
/*
* File:
* complex.c
* Author:
* Samuel A. Rebelsky
* Version:
* 1.0 of March 2003.
* Summary:
* A few simple procedures to support computation with complex
* numbers.
* Contents:
* complex cmult(complex a, complex b)
* Compute and return the produce of a and b.
*/
/*********************************************************************
* Headers *
***********/
#include "complex.h"
/*********************************************************************
* Exported Procedures *
***********************/
complex cmult(complex a, complex b) {
complex result;
result.real = a.real*b.real - a.imaginary*b.imaginary;
result.imaginary = a.real*b.imaginary + b.real*a.imaginary;
return result;
}
Implement strncpy as concisely as you can. You may not
call the built-in strncpy.
I assumed that you would look at the man page for strncpy.
That page reads, in part,
The
strcpy()function copies the string pointed to bysrc(including the terminating`\0'character) to the array pointed to bydest. The strings may not overlap, and the destination stringdestmust be large enough to receive the copy.
Thestrncpy()function is similar, except that not more thannbytes ofsrcare copied. Thus, if there is no null byte among the firstnbytes of src, the result will not be null-terminated.
In the case where the length ofsrcis less than that ofn, the remainder ofdestwill be padded with nulls.
Free Software Foundation (2001).strcpyandstrncpyman page. In Linux Man pages, designated as a GNUpage. Dated 1993-04-11.
What are the key parts of this definition?
not more thannbytes ofsrcare copied
the remainder of dest will be padded with nulls
Programmers typically assume that it doesn't matter whether
the sequence S1 ; S2 ; S3 is interpreted as
(S1 ; S2) ; S3 or as S1 ; (S2 ; S3).
Gries even makes a statement to this effect in his discussion of
the weakest precondition of instruction sequences.
Verify this assertion by showing that
wp(((S1 ; S2) ; S3), P) is the same as
wp((S1 ; (S2 ; S3)), P) .
Gries definition 8.3 on p. 115 tells us that
wp(S1;S2,P) = wp(S1, wp(S2, P))
So, let's see what we can compute for the two things we want to prove equivalent.
wp((S1;S2);S3, P)
= wp(S1;S2, wp(S3, P)) [by 8.3]
= wp(S1, wp(S2, wp(S3, P)) [by 8.3]
wp(S1;(S2);S3, P)
= wp(S1, wp(S2; S3), P)
= wp(S1, wp(S2, wp(S3, P)))
Since the two things are equal to equal things, they are equal.
Yes, the proof is amazingly simple. This problem was intended to be a simple reminder that (1) some proofs are, inf fact, easy; and (2) it's helpful to check some of Gries's assertions.
Primary:
[Front Door]
[Current]
[Glance]
-
[Blurb]
[Disabilities]
[Honesty]
[Instructions]
[Links]
[Search]
[Syllabus]
Groupings:
[EBoards]
[Examples]
[Exams]
[Handouts]
[Homework]
[Labs]
[Outlines]
[Readings]
[Reference]
ECA:
[About]
[Grades]
[Quizzes]
[Submit Work]
[Change Password]
[Reset Password]
Misc:
[Walker/Fall 2001]
[SamR]
Disclaimer:
I usually create these pages on the fly
, which means that I rarely
proofread them and they may contain bad grammar and incorrect details.
It also means that I tend to update them regularly (see the history for
more details). Feel free to contact me with any suggestions for changes.
This document was generated by
Siteweaver on Fri May 2 14:19:08 2003.
The source to the document was last modified on Sun Mar 30 19:33:58 2003.
This document may be found at http://www.cs.grinnell.edu/~rebelsky/Courses/CS195/2003S/Exams/notes.01.html.
You may wish to
validate this document's HTML
;
;
Check with Bobby