K&R Solutions - 1.18

Q: Write a program to remove trailing blanks and tabs from each line of input, and to delete entirely blank lines.

A:

#include <stdio.h>
#define MAXLINE 1000

int get_a_line(char s[], int lim);
void chop(char s[], int lim);

int main(void)
{
  int len;
  char line[MAXLINE];

  while ((len = get_a_line(line, MAXLINE)) > 0) {
    if (line[0] == '\n') {
      // Nothing happens. We want to totally ignore strings that are just
      // newlines.
    } else {
      chop(line, MAXLINE);
      printf("%s<---End Of String\n", line);
    }
  }

  return 0;
}

// Same as the previous exercise.
int get_a_line(char s[], int lim) 
{
  int c, i;

  for (i = 0; i < lim-1 && (c=getchar()) != EOF && c != '\n'; ++i) {
    s[i] = c;
  }
  if (c == '\n') {
    s[i] = c;
    ++i;
  }
  s[i] = '\0';
  
  return i;
}

void chop(char s[], int lim) {
  int i;

  // First we find the end of the string.
  // Notice that the the resulting value of 'i' is the length of the string.
  // Also notice that in this context, we could have just moved to the newline
  // before the null char.
  for (i = 0; s[i] != '\0'; ++i) {
  }

  // Now move backwards again, untill we find something that's not whitespace.
  for (i -= 1; s[i] == '\t' || s[i] == '\n' || s[i] == ' '; --i) {
  }

  // And we replace whatever is after with a null char.
  // We're replacing the newline, even though the exercise doesn't tell us
  // to get rid of it. This is becuse it makes the output easier to test.
  s[i+1] = '\0';
}
./trimmer
hi 	  (enter)
hi<---End Of String
how are         you?		(enter)	 
how are 	you?<---End Of String
(enter)
three little birds(enter)
three little birds<---End Of String


K&R Solutions - 1.17

Q: Write a program to print all input lines that are longer than 80 characters.

A:

/* This is really a simpler version of the longest-line program: that already
 * has most of what we need. */
#include <stdio.h>
#define MAXLINE 1000

int get_a_line(char s[], int lim);


int main(void)
{
  int len;
  char line[MAXLINE];

  while ((len = get_a_line(line, MAXLINE)) > 0) {
    if (len > 80) {
      printf("%s", line);
    }
  }

  return 0;
}

int get_a_line(char s[], int lim) 
{
  int c, i;

  for (i = 0; i < lim-1 && (c=getchar()) != EOF && c != '\n'; ++i) {
    s[i] = c;
  }
  if (c == '\n') {
    s[i] = c;
    ++i;
  }
  s[i] = '\0';
  
  return i;
}
This is a short sentence, which will not be repeated.
This, however, is a very, very, very long sentence, which will be repeated back by the program.     
This, however, is a very, very, very long sentence, which will be repeated back by the program.


K&R Solutions - 1.16

Q: 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.

A:

...
int main(void)
{
  int len;
  int max;
  char line[MAXLINE];
  char longest[MAXLINE];

  max = 0;
  while ((len = get_a_line(line, MAXLINE)) > 0) {
    if (len > max) {
      max = len;
      copy(longest, line);
    }
    // Following tradition, we will be ignoring the null character that marks
    // the end of every string. The standard library function strlen() also
    // ignores null characters.
    printf("Length: %d\n", len - 1);
    printf("Line: %s\n", line);
  }
  if (max > 0) {
    printf("%s", longest);
  }

  return 0;
}
...
hi
Length: 2
Line: hi

there this is an 
Length: 16
Line: there this is an

arbitrarily long string
Length: 23
Line: arbitrarily long string

moo 
Length: 3
Line: moo

arbitrarily long string


K&R Solutions - 1.15

Q: Rewrite the temperature conversion program of Section 1.2 to use a function for conversion.

A:

// Compared with the original, this is much easier to read.

#include <stdio.h>
  
float fahrenheit_to_celsius (float fahr) {
  return (5.0/9.0) * (fahr-32.0);
}

int main(void) {
  int lower = 0;
  int upper = 300;
  int step = 20;
  float fahr = lower;

  while (fahr <= upper) {
    printf("%3.0f %6.1f\n", fahr, fahrenheit_to_celsius(fahr));
    fahr = fahr + step;
  }

  return 0;
}
  0  -17.8
 20   -6.7
 40    4.4
 60   15.6
 80   26.7
100   37.8
120   48.9
140   60.0
160   71.1
180   82.2
200   93.3
220  104.4
240  115.6
260  126.7
280  137.8
300  148.9


K&R Solutions - 1.14

Q: Write a program to print a histogram of the frequencies of different characters in its input.

A:

#include <stdio.h>

int main(void) 
{
  int FREQUENCY_LENGTH = 256; // We'll just count all ASCII values. Why not?
  int frequencies[FREQUENCY_LENGTH];
  int i, j, c;
  int tally = 0;

  for (i = 0; i < FREQUENCY_LENGTH; ++i) {
    frequencies[i] = 0;
  }

  // This part gets a lot more simple.
  while ((c = getchar()) != EOF) {
    ++frequencies[c];
  }

  printf("\n");
  printf("Character Frequencies\n");
  printf("---------------------\n");
  /* For the sake of brevity, we will only print visible characters.
   * The ASCII values for visible characters are all between 33 and 126,
   * a fact I pulled off www.asciitable.com
   */
  for (i = 33; i <= 126; ++i) {
    printf("%c | ", i);

    for (j = 0; j < frequencies[i]; ++j) {
      printf("-");
    }

    printf("\n");
  }

  return 0;
}
America! Land of my dreams, home of the Whopper!

(ctrl+d)

Character Frequencies
---------------------
! | --
" | 
# | 
$ | 
% | 
& | 
' | 
( | 
) | 
* | 
+ | 
, | -
- | 
. | 
/ | 
0 | 
1 | 
2 | 
3 | 
4 | 
5 | 
6 | 
7 | 
8 | 
9 | 
: | 
; | 
< | 
= | 
> | 
? | 
@ | 
A | -
B | 
C | 
D | 
E | 
F | 
G | 
H | 
I | 
J | 
K | 
L | -
M | 
N | 
O | 
P | 
Q | 
R | 
S | 
T | 
U | 
V | 
W | -
X | 
Y | 
Z | 
[ | 
\ | 
] | 
^ | 
_ | 
` | 
a | ---
b | 
c | -
d | --
e | -----
f | --
g | 
h | ---
i | -
j | 
k | 
l | 
m | ----
n | -
o | ----
p | --
q | 
r | ---
s | -
t | -
u | 
v | 
w | 
x | 
y | -
z | 
{ | 
| | 
} | 
~ | 


K&R Solutions - 1.13, Part 2

Q: The same as part one, but vertical.

A:

#include <stdio.h>

int main(void) 
{
  int FREQUENCY_LENGTH = 10; 
  int frequencies[FREQUENCY_LENGTH];
  int i, j, c;
  int tally = 0;
  int highest = 0;

  // We're tallying in the exact same way.
  for (i = 0; i < FREQUENCY_LENGTH; ++i) {
    frequencies[i] = 0;
  }

  while ((c = getchar()) != EOF) {
    if (c == '.' || c == '\n' || c == '\t' || c == ' ' || c == ',') {
      if (tally > FREQUENCY_LENGTH) {
        tally = FREQUENCY_LENGTH - 1;
      }
      ++frequencies[tally];
      tally = 0;
    } else {
      ++tally;
    }
  }

  /* First, we need the height of the highest bar.
  *  At the top of the function, temp is set to zero. Normally a function
  *  wouldn't be this long, but we haven't got to that part fo the book yet.
  */
  for (i = 0; i < FREQUENCY_LENGTH; i++) {
    if (frequencies[i] > highest) {
      highest = frequencies[i];
    }
  }

  /* Now we keep drawing the bars untill our 'highest' variable, and everything
   * in our tallies is zeroed out.
   */
  while (highest) {
    // Remember there are no zero-length words so we skip 0. Feel free to play
    // around with this, because some junk data is getting stored in
    // frequencies[0]. Why?
    for(i = 1; i < FREQUENCY_LENGTH; i++) {
      if (frequencies[i] == highest) {
        printf(" | ");
        frequencies[i]--;
      } else {
        printf("   ");
      }
    }
    printf("\n");
    highest--;
  }

  /* Now we print a handy guide at the bottom.
   */
  for (i = 1; i < FREQUENCY_LENGTH; i++) {
    printf("---");
  }
  printf("-\n");
  for (i = 1; i < FREQUENCY_LENGTH; i++) {
    if (i == FREQUENCY_LENGTH - 1) {
      printf(" %d+", i);
    } else {
      printf(" %d ", i);
    }
  }
  printf("\n");

  return 0;
}
The worst of misery
Is when a nature framed for noblest things
Condemns itself in youth to petty joys,
And, sore athirst for air, breathes scanty life
Gasping from out the shallows.
Supercalifragilisticexpialidocious.

(ctrl+d)

       |                   
       |        |          
       |  |     |          
    |  |  |     |          
    |  |  |  |  |  |  |    
    |  |  |  |  |  |  |    
 |  |  |  |  |  |  |  |  | 
----------------------------
 1  2  3  4  5  6  7  8  9+ 

You can play around with this quite a bit. Removing one line and then changing one character, and we get a nice little graph:

       .                   
                .          
          .                
    .                      
             .     .  .    
                           
 .                       . 
----------------------------
 1  2  3  4  5  6  7  8  9+ 


K&R Solutions - 1.13, Part 1

Q: 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.

A:

#include <stdio.h>

int main(void) 
{
  /* Because of screen space and the limitations of terminal output, words
   * longer than ten are going to be categorized as greater than 10.
   * 
   * In this model, frequencies[0] is a one-character word, and frequencies[1]
   * is a two-character word. This 'off-by-one' behaviour seems unintuitive
   * and certainly leads to bugs, but there is a very good, low-level reason
   * that becomes very obvious when you learn a bit of assembly. You could find
   * a work-around (like leaving frequencies[0] unused), but it's better to get
   * use to thinking of things as starting with the 0th element because, in the
   * long run, there's no real avoiding it in programming.
   */
  int FREQUENCY_LENGTH = 10; 
  // It's tradition to capitalize this we won't be changing.
  int frequencies[FREQUENCY_LENGTH];
  int i, j, c;
  int tally = 0;

  /* Initialize all frequencies to 0 */
  /* The book hasn't told us this yet, but the easiest way to initiate all
   * elements in an array to 0 is simply to go:
   * int frequencies[10] = {0};
   * BUT doing it with what the book has tought us gives us a better feel for
   * the way that arrays work.
   */
  for (i = 0; i < FREQUENCY_LENGTH; ++i) {
    frequencies[i] = 0;
  }

  /* Count word-length frequencies */
  /* I'm missing colons and semi-colons here for the sake of space, but you get
   * the drift. In a professional setting, you would probably want to create 
   * a separate subroutine called is_word_barrier() to keep your code cleaner
   * and more readable, but we haven't got to that part of the book yet. 
   */
  while ((c = getchar()) != EOF) {
    if (c == '.' || c == '\n' || c == '\t' || c == ' ' || c == ',') {
      if (tally > FREQUENCY_LENGTH) {
        tally = FREQUENCY_LENGTH - 1;
      }
      ++frequencies[tally];
      tally = 0;
    } else {
      ++tally;
    }
  }

  /* Print horizontal-barred histogram */
  printf("\n");
  printf("Word Frequencies: Horizontal\n");
  printf("----------------------------\n");
  for (i = 0; i < FREQUENCY_LENGTH; ++i) {
    if (i != 0) {
      if (i == FREQUENCY_LENGTH - 1) {
        printf("%3d+ | ", i);
      } else {
        printf("%3d  | ", i);
      } 
      for (j = 0; j < frequencies[i]; ++j) {
        printf("-");
      }
      printf("\n");
    }
  }

  return 0;
}
The worst of misery
Is when a nature framed for noblest things
Condemns itself in youth to petty joys,
And, sore athirst for air, breathes scanty life
Gasping from out the shallows.
Supercalifragilisticexpialidocious.

(ctrl+d)

Word Frequencies: Horizontal
----------------------------
  1  | -
  2  | ----
  3  | -------
  4  | -----
  5  | ---
  6  | ------
  7  | ---
  8  | ---
  9+ | -


K&R Solutions - 1.12

Q: Write a program that prints its input one word per line.

A:

#include <stdio.h>

int main(void)
{
  char c;

  while ((c = getchar()) != EOF) {
    if (c == '\t' || c == '\n' || c == ' ') {
      printf("\n");
    } else {
      putchar(c);
    }
  }

  return 0;
}
> muh muh muh     moo cows
muh
muh
muh
moo
cows


K&R Solutions - 1.11

Q: How would you test the word count program? What kinds of input are most likely to uncover bugs if there are any?


A: There is no perfect answer to this kind of question. Writing good test is really a matter of experience. Part of the reason all programs have bugs is that even the best test writers can't anticipate all the wild ways a program will be used by other people. Nevertheless, writing good tests is a good skill to develop, as you can filter out about 99% of potential bugs that way, and prevent unexpected behaviour when you make small changes to large programs.


The first thing I would do is test it with no characters at all, hoping for '0 0 0'. Next, test it with one word, then with one tab, then with one newline. Then a tab and a newline, then a newline and a tab... you probably don't need the full 16, but instinct tells me that starting input with white space might cause problems. After that, a few short strings like 'two words' will cover the basics. Of course, in real life, I hope I can write such thorough tests.


That might seem like a lot but if you write a test once, you get the peace of mind forever.