From 82d318879c897b3d665767d74806cd33fe6791c0 Mon Sep 17 00:00:00 2001 From: zlg Date: Tue, 23 Apr 2013 13:33:25 -0500 Subject: Add exercise descriptions and answers for ch1 * Corrected behavior in solutions for 1-9 and 1-23 --- ch1/1-09_single-spacing.c | 1 + ch1/1-13_word-length-histogram.c | 19 ++++++++++++++++++ ch1/1-14_character-freq-histogram.c | 12 +++++++++++ ch1/1-15_temp-convert-func.c | 14 +++++++++++++ ch1/1-16_longest-line.c | 11 ++++++++++ ch1/1-17_over-80.c | 11 +++++++++- ch1/1-18_strip-blanks.c | 17 +++++++++++++--- ch1/1-19_reverse-lines.c | 19 ++++++++++++++++++ ch1/1-20_detab.c | 10 +++++----- ch1/1-21_entab.c | 11 +++++----- ch1/1-22_wordwrap.c | 15 +++++++------- ch1/1-23_decomment.c | 40 ++++++++++++++++++++++--------------- ch1/1-24_syntax-checker.c | 29 ++++++++++++++++----------- 13 files changed, 158 insertions(+), 51 deletions(-) diff --git a/ch1/1-09_single-spacing.c b/ch1/1-09_single-spacing.c index 4bf802a..ab629c5 100644 --- a/ch1/1-09_single-spacing.c +++ b/ch1/1-09_single-spacing.c @@ -21,6 +21,7 @@ int main(void) { } spaces++; } else { + spaces = 0; putchar(c); } } diff --git a/ch1/1-13_word-length-histogram.c b/ch1/1-13_word-length-histogram.c index d3115a5..b557417 100644 --- a/ch1/1-13_word-length-histogram.c +++ b/ch1/1-13_word-length-histogram.c @@ -1,4 +1,23 @@ #include + +/* The C Programming Language: 2nd Edition + * + * Exercise 1-13: Write a program to print a histogram of the lengths of words + * in its input. It is easy to draw the histogram with the bars horizontal; a + * vertical orientation is more challenging. + * + * Answer: Keep an array of lengths that you can add to when you come out of a + * word. At the end of input, you'll have your collection of data. The rest is + * deciding how you want to display it. I chose a horizontal layout for this + * exercise. + * + * To do the vertical, you need to choose a theoretical maximum (80 is probably + * a bit too high) and simply iterate over the data set checking for the word + * length that has that frequency or lower. + * + * TODO: Write the vertical histogram code. + */ + #define IN 1 #define OUT 0 #define MINWLENGTH 2 diff --git a/ch1/1-14_character-freq-histogram.c b/ch1/1-14_character-freq-histogram.c index 14c4871..9ef1d22 100644 --- a/ch1/1-14_character-freq-histogram.c +++ b/ch1/1-14_character-freq-histogram.c @@ -1,4 +1,16 @@ #include + +/* The C Programming Language: 2nd Edition + * + * Exercise 1-14: Write a program to print a histogram of the frequencies of + * different characters in its input. + * + * Answer: Using an array of characters (hint: that's what a string is) with + * an array of ints is a good way to pair them up for counting. The 'i' + * iterator variable ties to both so that lengths[i] represents the count for + * chars[i]. + */ + int main(void) { /* Rundown of variables: c = current input char diff --git a/ch1/1-15_temp-convert-func.c b/ch1/1-15_temp-convert-func.c index aa59cf6..ae30d4f 100644 --- a/ch1/1-15_temp-convert-func.c +++ b/ch1/1-15_temp-convert-func.c @@ -1,5 +1,19 @@ #include +/* The C Programming Language: 2nd Edition + * + * Exercise 1-15: Rewrite the temperature conversion program of Section 1.2 to + * use a function for conversion. + * + * Answer: This exercise teaches you the purpose of functions: to perform + * specific operations that can be reused. Writing good functions is the + * foundation of good programming. Without functions, programming would still + * be in its version of the stone age. + * + * In the case of temperature conversion, all you really need to do is + * outsource the math to a function and return the result. + */ + #define MAX_F 300.0 #define STEP 20.0 diff --git a/ch1/1-16_longest-line.c b/ch1/1-16_longest-line.c index 1f6e556..137bb11 100644 --- a/ch1/1-16_longest-line.c +++ b/ch1/1-16_longest-line.c @@ -1,5 +1,16 @@ #include +/* The C Programming Language: 2nd Edition + * + * Exercise 1-16: Revise the main routine of the longest-line program so it + * will correctly print the length of arbitrarily long input lines, and as + * much as possible of the text. + * + * Answer: The key to arbitrary limits is buffering. Using a buffer allows you + * to tackle a problem in chunks of memory instead of all at once. It's + * slightly more complicated, but adds usefulness to a program. + */ + #define MAXLENGTH 100 int get_line(char s[], int lim) { diff --git a/ch1/1-17_over-80.c b/ch1/1-17_over-80.c index 53f00ce..194e450 100644 --- a/ch1/1-17_over-80.c +++ b/ch1/1-17_over-80.c @@ -1,5 +1,14 @@ #include +/* The C Programming Language: 2nd Edition + * + * Exercise 1-17: Write a program to print all input lines that are longer + * than 80 characters. + * + * Answer: Let's build on the buffering idea from Exercise 1-16 and narrow + * it to lines over 80 characters long. + */ + #define MINLENGTH 80 int main() { @@ -14,7 +23,7 @@ int main() { buffer[len] = c; /* When we meet the end of the line, we need to print the rest of the line, - but only if we're already in a long line. Otherwise, reset our state. + * but only if we're already in a long line. Otherwise, reset our state. */ if (c == '\n') { diff --git a/ch1/1-18_strip-blanks.c b/ch1/1-18_strip-blanks.c index c766445..947ce8d 100644 --- a/ch1/1-18_strip-blanks.c +++ b/ch1/1-18_strip-blanks.c @@ -1,8 +1,16 @@ #include +/* The C Programming Language: 2nd Edition + * + * Exercise 1-18: Write a program to remove trailing blanks and tabs from each + * line of input, and to delete entirely blank lines. + * + * Answer: Simple -- start from the end of the line and look for a non-blank + * character. Once found, place your \n and \0 just afterwards. By checking + * that 'i' > 1, it ensures that blank lines never get spit out, since a line + * with content will always be 2 or more characters long due to the \n. + */ #define MAXLINELENGTH 9001 -/* Write a program to remove trailing blanks and tabs from each line of input, - and to delete entirely blank lines. */ int get_line(char s[], int lim) { int c, i; @@ -24,7 +32,10 @@ int main(void) { /* Make sure every line is gone over */ while (len = get_line(buffer, MAXLINELENGTH)) { - /* An empty for statement, simply to change the i variable. */ + /* An empty for statement, simply to change the i variable. + * Subtracting 2 from 'len' is to account for 0-based indexing and the \0 + * string terminator. + */ for (i = len - 2; (i > 0) && (buffer[i] == ' ') || (buffer[i] == '\t'); --i); /* We've reached the end of the line's actual content. Terminate the line. */ diff --git a/ch1/1-19_reverse-lines.c b/ch1/1-19_reverse-lines.c index d87b625..f92c6fa 100644 --- a/ch1/1-19_reverse-lines.c +++ b/ch1/1-19_reverse-lines.c @@ -1,5 +1,24 @@ #include +/* The C Programming Language: 2nd Edition + * + * Exercise 1-19: Write a function reverse(s) that reverses the character + * string 's'. Use it to write a program that reverses its input a line at a + * time. + * + * Answer: This is really just as simple as using get_line() and reverse() in + * tandem. I guess the point of this exercise is to teach the reader how to + * combine the use of functions to get more complex behavior out of a program. + * + * My version includes the size of the string in the argument list instead of + * duplicating effort that get_line() does, since it already puts the contents + * into a string and returns the length of the string for me. For strict + * passing of this exercise, reverse() should really only have one argument + * and it should count the size of the string before it works with it. But + * that's prone to issues, such as a string that hasn't been terminated + * properly. + */ + #define MAXLINELENGTH 9001 int get_line(char s[], int limit) { diff --git a/ch1/1-20_detab.c b/ch1/1-20_detab.c index dc8f5b5..d0db601 100644 --- a/ch1/1-20_detab.c +++ b/ch1/1-20_detab.c @@ -1,11 +1,11 @@ #include /* The C Programming Language: 2nd Edition - * Exercise 1-20: - * "Write a program `detab` that replaces tabs in the input with the proper - * number of blanks to space to the next tabstop. Assume a fixed set of - * tabstops, say every 'n' columns. Should 'n' be a variable or a symbolic - * parameter?" + * + * Exercise 1-20: Write a program `detab` that replaces tabs in the input with + * the proper number of blanks to space to the next tabstop. Assume a fixed set + * of tabstops, say every 'n' columns. Should 'n' be a variable or a symbolic + * parameter? * * Answer: 'n' should be a symbolic parameter. It's more apparent what's being * worked with and it's not susceptible to scope. Though, in this simple diff --git a/ch1/1-21_entab.c b/ch1/1-21_entab.c index 6037a14..2833520 100644 --- a/ch1/1-21_entab.c +++ b/ch1/1-21_entab.c @@ -1,15 +1,14 @@ #include /* The C Programming Language: 2nd Edition - * Exercise 1-21: - * "Write a program `entab` that replaces strings of blanks by the minimum - * number of tabs and blanks to achieve the same spacing. Use the same tab - * stops as for `detab`. When either a tab or a single blank would suffice to - * reach a tab stop, which should be given preference?" + * + * Exercise 1-21: Write a program `entab` that replaces strings of blanks by + * the minimum number of tabs and blanks to achieve the same spacing. Use the + * same tab stops as for `detab`. When either a tab or a single blank would + * suffice to reach a tab stop, which should be given preference? * * Answer: A blank. A tab character that comes after (tabstop - 1) blanks makes * little-to-no sense and could mess up alignment in some environments. - * */ #define TABWIDTH 8 diff --git a/ch1/1-22_wordwrap.c b/ch1/1-22_wordwrap.c index 83acfd6..eaf6aff 100644 --- a/ch1/1-22_wordwrap.c +++ b/ch1/1-22_wordwrap.c @@ -1,16 +1,15 @@ #include /* The C Programming Language: 2nd Edition - * Exercise 1-22: - * "Write a program to 'fold' long input lines into two or more shorter lines - * after the last non-blank character that occurs before the n-th column of - * input. Make sure your program does something intelligent with very long - * lines, and if there are no blanks or tabs before the specified column." * - * So... Quite a hefty requirement. In a nutshell, our goal is to create sane - * hard-wrapping. This is a common function in text editors, and it's - * important to get it right or the results are wonky. + * Exercise 1-22: Write a program to 'fold' long input lines into two or more + * shorter lines after the last non-blank character that occurs before the n-th + * column of input. Make sure your program does something intelligent with very + * long lines, and if there are no blanks or tabs before the specified column. * + * Answer: So... Quite a hefty requirement. In a nutshell, our goal is to + * create sane hard-wrapping. This is a common function in text editors, and + * it's important to get it right or the results are wonky. * * TODO: get_line() is not fully correct. When it hits a \t, it counts it as * one character and has no concept of display count. I'll fix this later on. diff --git a/ch1/1-23_decomment.c b/ch1/1-23_decomment.c index 63193cd..d9c5c90 100644 --- a/ch1/1-23_decomment.c +++ b/ch1/1-23_decomment.c @@ -1,28 +1,29 @@ #include /* The C Programming Language: 2nd Edition - * Exercise 1-23: - * "Write a program to remove all comments from a C program. Don't forget to - * handle quoted strings and character constants properly. C comments do not - * nest." * - * I'm not sure why the instructions tell the reader to account for quoted - * strings and character constants. Comments always begin with either // or - * /*, so that sets the boundary of state. + * Exercise 1-23: Write a program to remove all comments from a C program. + * Don't forget to handle quoted strings and character constants properly. C + * comments do not nest. + * + * Answer: At first I didn't know why it was important to account for strings + * or character constants. The reason behind this is that /* and // are valid + * inside those parts of C, so they deserve special treatment. * * The states for this program are IN_MULTI for a multi-line comment, - * IN_SINGLE for a single line comment, and OUT for being outside a comment. - * These three states are all I need to determine whether I should output the - * contents of the C file or not. Characters are only output when the state is - * OUT, and there are conditions for getting into and out of comment state. + * IN_SINGLE for a single line comment, OUT for being outside a comment, and + * IN_STRING for being inside a string or character constant. These four states + * are all I need to determine whether I should output the contents of the C + * file or not. Characters are only output when the state is OUT or IN_STRING, + * and there are conditions for getting into and out of comment state. * - * I don't know of any other way to do this without invoking some higher-level - * libraries. + * This structure is known as a finite state machine (FSM). */ +#define OUT 0 #define IN_MULTI 1 #define IN_SINGLE 2 -#define OUT 0 +#define IN_STRING 3 char c, p; int status; @@ -30,7 +31,14 @@ int status; int main() { // Treat input like a stream, since that's all the K&R has explained so far. while ((c = getchar()) != EOF) { - + /* This is to account for strings and character constants */ + if (c == '\'' || c == '"') { + if (status == OUT) { + status = IN_STRING; + } else if (status == IN_STRING) { + status = OUT; + } + } // Check for the ways to open a comment, and set state accordingly if (c == '/' && status == OUT) { // Look ahead and store the character that's returned @@ -70,7 +78,7 @@ int main() { } // Output everything when we're not in a comment! - if (status == OUT) { + if (status == OUT || status == IN_STRING) { putchar(c); } diff --git a/ch1/1-24_syntax-checker.c b/ch1/1-24_syntax-checker.c index 019b9bc..444c562 100644 --- a/ch1/1-24_syntax-checker.c +++ b/ch1/1-24_syntax-checker.c @@ -1,22 +1,27 @@ #include /* The C Programming Language: 2nd Edition - * Exercise 1-24: - * "Write a program to check a C program for rudimentary syntax errors like - * unbalanced parentheses, brackets, and braces. Don't forget about quotes, both - * single and double, escape sequences, and comments. (This program is hard if - * you do it in full generality.)" * - * Proksima from Freenode's ##c helped me understand full generality where - * Ixquick, Wikipedia, and StackOverflow all failed: a program that has full - * generality handles all use cases. In this case, my program should report no - * errors from a well formed C source file, and return correct errors for every - * non-valid C source file. + * Exercise 1-24: Write a program to check a C program for rudimentary syntax + * errors like unbalanced parentheses, brackets, and braces. Don't forget about + * quotes, both single and double, escape sequences, and comments. (This + * program is hard if you do it in full generality.) + * + * Answer: Proksima from Freenode's ##c helped me understand full generality + * where Ixquick, Wikipedia, and StackOverflow all failed: a program that has + * full generality handles all use cases. In this case, my program should + * report no errors from a well formed C source file, and return correct errors + * for every non-valid C source file. * * I can tackle this one the same way I tackled the previous exercise: with a - * FSM. The trick is in catching the mismatched levels. + * FSM. The trick is in catching the mismatched levels. This happens when, + * after the source has been combed, there are values that aren't equal to + * zero. Anything that's non-zero means there are too many or too few of a + * specific syntax construct. * - * Post-solution note: switch() would've made this MUCH shorter... + * Post-solution note: switch() would've made this MUCH shorter... but it's not + * covered until chapter 3. I think part of the purpose of this exercise is to + * teach the value of the switch structure later on. :) */ char c; -- cgit v1.2.3-54-g00ecf