Text stream K&R exercises

Hello, ladies, gentlemen.

First I suppose I should introduce myself.

I've been poking at C since a long time ago, somewhere around 1990. (Don't misinterpret that. "Poking at C", in this statement, means that I jumped on it, studied it for anything from a day to a weekend to a finished "Hello, World" program, then forgot about it while years of life roared by. The statement is not meant to imply that I have years and years and years of programming, rather, it simply means that lots and lots of time went by while my programming skills stood still.) I'm not a professional, I'm not a student, and I don't really know that much about it, other than this: it fascinates me.

My knowledge of C and programming comes from these disjointed forays, books, the 'net, and the occasional burst of help from friends who are REAL programmers.

The difference between us is profound. Given a project, they sit down and methodically start laying out a process to complete the solution, while I sit down and start eliminating ways that don't work. Their way is faster.

I program on a Mac, currently with OS X, (10.6.2, Snow Leopard). I use Xcode as my environment, and I compile C with Terminal and everything else with Xcode.

When I started, the Big Thing was CodeWarrior, and I never learned to hammer out code on a Unix platform. In fact, because the Mac environment was so different then, I never learned to do a lot of basic stuff.

Of course, back in the day, I bought a copy of K&R's The C Programming Language, and I've benefitted from it immensely.

I've always looked at the exercises, and promised myself that someday, I would come back and see to them.

That day has arrived.

I just (two nights ago, in fact) figured out how to use Xcode to write C files, and how to use the cc command in Terminal to compile them. After a big search, I figured out that "./a.out" runs the output, and I became able, for the first time, to write, compile and run programs that are suitable (in my unlearned opinion) for the K&R exercises.

I know that programming has advanced, and I know that Computer Years are even more extreme than Dog Years; I know that Objective C is what Xcode is all about now, and I remember pulling my hair out while I tried to write code to get a window to scroll, a task that Interface Builder handles auto-magically now.

Still, I've always felt that if I understood ANSI C better, my understanding of programming in general would be better, so I've decided to work my way through the K&R book, and hunt down the answers to every stupid little exercise.

So.

I managed to make it through the first five exercises, and I have code that should solve 1-6, and I solved 1-7.

However. (You knew that was coming, didn't you?)

The exercises that center on getchar and putchar puzzle me: I don't see where they are getting the text stream, and I believe that it is coming from the keyboard.

That creates a couple problems, first is that I don't know what to type to generate an EOF character, so my programs (and one of their programs) are endless loops, and you have to close the Terminal window to get them to quit.

This code is also a problem:

// Test.c

#include <stdio.h>

	//Verify that the expression "getchar() != EOF" is 0 or 1.
	//Exercise 1-6 on page 17.

main()	{
	
	int	c;
	
	c = getchar();
	
	while (c != EOF) {
		while (c != 1) { 
			while (c != 0) {// You only get here if c is not 1.
				printf("EOF is not 1 or 0.");	//c is not 1 or zero.
			}	//end while test for zero
		}	// end while test for 1
		
		printf("\nThe value passed, because EOF is ");
		printf("%d\n",	c);
	}
}

When I run it, it doesn't appear to do anything, until I type something. Then it enters an endless loop, and doesn't exit unless I close the Terminal window.

I don't see how I'm creating a loop.

I also don't see (in the K&R book) how to obtain a text stream other than from the keyboard.

Any help will be welcome.

This code is a different type of problem:

// count.c
#include	<stdio.h>

// count characters in input, page 18 of C book, version 1

main()	{
	
	long	nc;
	
	nc = 0;
	
	while (getchar()!=EOF) {
		++nc;
		printf("%ld\n",	nc);
	}	// end of while
}	// end of main

This code is straight out of the K&R book. What it does is nothing, until you type something. Then it generates a random number of numbers. It also doesn't quit unless you quit the Terminal window.

I appreciate your looking. Thank you.

Hi.
the code is correct . Which Operating System do you use?
If you are on GNU/Linux then EOF is 'ctrl+D'
If you are on Windows(eww) then EOF is 'ctrl+Z' ( I guess )

when you press ctrl+D then the program will err experience 'end of file' character and it will come out of loop.

Regards,
Enjoy.

You've got 3 while-loops in there, each one checking the value of the variable c, which is never changing. So as soon as you enter any character, enters the outermost while loop. Since it's pretty unlikely to enter the characters with the ASCII codes 0 or 1 via the keyboard, the two inner loops will run until killed (by Ctrl+C or by closing the Terminal).

There are 2 possibilities: you can do a redirection in your terminal (eg

cat /some/file | ./a.out

), or by opening a file using open/fopen. If I remember correctly, those are introduced later in the book.

getchar() is a so-called blocking function. That means, it will sit and block the calling program until it can return something, in this case a character read from stdin. While it gets characters that don't signal an End-Of-File, it will count up.

EOF is usually input by the keyboard combination Ctrl+D on Unix-like systems (including BSD-descended MacOS X)

Thanks to both of you.

I see the problem with the while loops.

I'll try the control-D combination, then I'll gnash my teeth and pull my hair for awhile on another way to test input.

Four hints:

  • General: C doesn't know boolean values, it just uses 0 as false, and 1 as true.
  • General: any comparison can be used as a right-hand value, like a function
  • In exercise 1-6: use if to compare
  • In exercise 1-7: almost there, just move the printf() so it's called only once, when everything else is done, to avoid cluttering the screen

Okay, I'm back. :slight_smile:

I've been examining if.

For 1-6, I wrote this, thinking that perhaps I was just making it too complicated:

/*
 *  verify.c
 *  
 *	Exercise 1-6, Verify that the expression "getchar() != EOF is 0 or 1.
 */

#include <stdio.h>

main(){
	
	int v;
	
	while (v=(getchar() != EOF)) { // get another character, make the test
		
		printf("%d\n",v);	// prints as long as it isn't EOF
	}	// end of while
	
	printf("Reached EOF, program ends.\n\n");
	
}	// end of main

When I run it, I see a strange problem.

It produces a list of 1s, (because the statement is always true, if there's another character that isn't EOF) and then when you hit Control-D (Thank you, thank you!) it prints the end statement.

The problem is that it always prints one extra 1!

That is, if I type a string of five letters into the keyboard, it will print six ones.

I don't see where the extra one is coming from, the only way to reach the printf statement is for there to be a character in the character stream. I think. Maybe. Perhaps. Probably. In my fantasy world.

So where is the extra 1 coming from?

P.S. Now I'm off to nail 1-8, which looks easy.

---------- Post updated at 20:43 ---------- Previous update was at 20:32 ----------

Gaurav, thank you, I'm using Mac OX X, 10.6.2, called Snow Leopard, and Control-D does indeed work.

Thank you again. :slight_smile:

You said that it prints 6 1s if you just enter 5. Do you hit Return after entering the 5 characters? If so, there's your sixth.

Oh! The return character! Of course! duh...

Now, of course, I have another problem. I thought 1-8 would be easy, but not so much.

The code:

/*
 *  blank.c
 *	
 *	Exercise 1-8, write a program that counts blanks, tabs and newlines.
 */

#include <stdio.h>


main(){
	int x,s,t,n;
	
	s = 0; // counter for spaces
	t = 0; // counter for tabs
	n = 0; // counter for newlines
	
	while (x = getchar()!=EOF){
		
		if (x = " ") // x is a blank space
			s++;
		if (x = "\t") // x is a tab
			t++;
		if (x = "\n") // x is a newline
			n++;
		
	} // End while

	printf("\nThere were ");
	printf("%d", s);
	printf(" spaces.\n");
	
	printf("There were ");
	printf("%d", t);
	printf(" tabs.\n");
	
	printf("There were ");
	printf("%d", n);
	printf(" newlines.\n\n");
	
} // End main

It stays in a loop, but the loop isn't very well behaved.

It takes the input, and I figured that it should only increment the various counters if the character in question were the right type-- but no.

It increments EACH counter with EVERY character.

It has no discrimination. Enter 6 characters and a return, and it will say that there are 7 spaces, 7 tabs and 7 newlines.

I don't see how it's getting into each if statement! It's breaking all the rules!

Don't worry, it's a typical beginners error.

  • = is an assignment. As long as the lvalue isn't a constant, it always returns a true value.
  • == is an comparison.

Oh, for goodness sake.

I see it.

{sigh...}

So, it's not comparing anything in my code, it's assigning the values of space, tab and newline to the variable x, which is always successful, and, therefore, always true. So every if loop executes on every character.

/*******************************/
Update.

I re-wrote the code, and made another rookie mistake that plagued me for two hours.

This code is the solution:

/*
 *  blank.c
 *	
 *	Exercise 1-8, write a program that counts blanks, tabs and newlines.
 */

#include <stdio.h>


main(){
	int x,s,t,n;
	
	s = 0; // counter for spaces
	t = 0; // counter for tabs
	n = 0; // counter for newlines
	
	while ((x = getchar()) != EOF){
		
		if (x == ' ') // x is a blank space
			s++;
		if (x == '\t') // x is a tab
			t++;
		if (x == '\n') // x is a newline
			n++;
		
	} // End while

	printf("\nThere were ");
	printf("%d", s);
	printf(" spaces.\n");
	
	printf("There were ");
	printf("%d", t);
	printf(" tabs.\n");
	
	printf("There were ");
	printf("%d", n);
	printf(" newlines.\n\n");
	
} // End main

However, in the comparisons, I initially used double quotes, and the program insisted that there were zero spaces, zero tabs, and zero newlines-- exactly what I'd initialized the variables to. They weren't incrementing.

I finally found the answer in K&R-- they had mentioned it, and I read right over it. It has to be single quotes, double quotes is a text string.

I won't say that an exercise looks easy again. :slight_smile: I'm off to look at 1-9.

---------- Post updated 01-16-10 at 01:37 ---------- Previous update was 01-15-10 at 01:43 ----------

Okay, I'm back.

Now I'm completely baffled.

Exercise 1-9, with this code:

/*
 *  copy.c
 *  
 *	Exercise 1-9. Write a program to copy it's input to it's output,
 *	replacing each string of one or more blanks by a single blank.
 */


#include	<stdio.h>

main() {
	
	int	c;	//the current character
	
	while ((c = getchar()) != EOF) {
		
		if (c == ' ') { // If there's a blank space
			
			while ((c = getchar() == ' ')) { /* Get another character, check to 
											 see if it's a blank space. We don't
											 care how many blank spaces there are, so 
											 if it's a blank space, do nothing. If it's
											not a blank space, break out of the while
											 loop. */
				
				; /* The do nothing command. When it breaks out of this loop, 
				   c will be holding the first character after the string 
				   of blank spaces.*/
				
			} // end while
			
			printf(" "); //But there was at least one blank space, so we need
						 // to print one blank space.
		}  // end if
		
	printf(c); // print the character
				   
	}	// end while
}	// end main

When I run this code with a text file, using cat /Text.txt|./a.out

the only thing that happens is that another curser appears and says:

Bus error.

My other programs still run, this is the only one that creates a bus error.

---------- Post updated at 05:02 ---------- Previous update was at 01:37 ----------

Okay, I replaced the final printf with a putchar, and the bus error went away.

The program now runs, but it doesn't do anything like it should.

/*
 *  copy.c
 *  
 *	Exercise 1-9. Write a program to copy it's input to it's output,
 *	replacing each string of one or more blanks by a single blank.
 */


#include	<stdio.h>

main() {
	
	int	c;	//the current character
	
			while ((c = getchar()) != EOF) {
		
		if (c == ' ') { // If there's a blank space
			
			while ((c = getchar() == ' ')) { 
				
/* Get another character, check to see if it's a blank space. I don't
 care how many blank spaces there are, so if it's a blank space, do nothing. 
 If it's not a blank space, break out of the while loop. */
				
				; /* The do nothing command. When it breaks out of this loop, 
				   c will be holding the first character after the string 
				   of blank spaces.*/
				
			} // end while
			
			printf(" "); //But there was at least one blank space, so we need
						 // to print one blank space.
		}  // end if
		
	putchar(c); // print the character
				   
	}	// end while
}	// end main

Now it deletes "random" characters, (I'm sure there's a pattern, or at least a reason, but I don't see it.) and leaves long lines of blank spaces. The mystery continues.

You can use a flag as "mnemonic", something like this:

#include <stdio.h>

main()
{
    int c, flag;

    while ((c = getchar()) != EOF){
        if (c == ' ') {
            if (!flag) {
                printf("%c", c);
                flag=1;
            }
            continue;
        }
        printf("%c", c);
        flag=0;
    }
}

:wink:

Thank you, Franklin.

I took your suggestion of a flag, and after wrestling with it for a few hours, came up with this:

/*
 *  copy.c
 *  
 *	Exercise 1-9. Write a program to copy it's input to it's output,
 *	replacing each string of one or more blanks by a single blank.
 */


#include	<stdio.h>

main() {
	
	int	c;	// The current character
	int	blank;	// flag that is set when we get the first blank space
	
	c = blank = 0;
	
	printf("\n\n");	// just so I can read the output
	
	while ((c = getchar()) != EOF) {
		
		if (c != ' ')		{	// if c is not holding a blank space
			putchar(c);			// print it
		}						// end if
			else if (!blank) {	// c is a blank space, if it's the first blank space
				blank = 1;		// set blank to show we already have the first blank
				putchar(c);		// printed
			}					// end else if
		
			else	{			// we're in a string of two or more blanks
				
				while ((c = getchar()) == ' ') {
					
/* as long as we're in the string of blanks					
 do nothing, kick out when we get the first non-blank	*/
					
					;	// The do nothing command		
				}		// end while
				
/* c now holds the first character after the string of blanks, 
reset blank, print one blank, and print the character */
				
				blank = 0
				printf(" ");
				putchar(c);
				
			}					// end final else
	}							// end main while loop
	
	printf("\n\n");				// just so I can read the output
	
}								// end main

It works, and it works reliably, but there's one thing I don't like about it.

If I'm following it correctly, (and I'm pretty sure I am) the flag blank carries a 1 as soon as a single blank is encountered.

That is, if you ran it on this sentence, the space between "That" and "is" would set blank to one, and it would be one until it was re-set, and it wouldn't be re-set until the word "if".

So it would work, it wold do its assigned task correctly, and the code would use the first else if when it didn't have a blank set, and the second else if when it did.

So my only gripe is that the flag blank isn't reliable-- it does not show the truth about whether the first blank has been encountered at all times.

On the other hand, when I swore at it, I woke my wife up, so I think I'll put it away for tonight.

Is that necessary? You can also use the flag as a counter and increase the value if you encounter a space:

blanc++;

I started thinking about what you said, and I realized that it doesn't matter.

The goal is to replace strings of blanks with single blanks, and then I realized I was over-thinking it, and making it too complicated.

So I stripped out the counter, did away with all the unnecessary activity, and came up with this, which I believe is a better solution.

Thank you, Franklin, :slight_smile:

/*
 *  copy2.c
 *  
 *	Exercise 1-9. Write a program to copy it's input to it's output,
 *	replacing each string of one or more blanks by a single blank.
 */

#include <stdio.h>

main(){
	
	int	c;	// the current character
	
	printf("\n\n");
	
	while ((c = getchar()) != EOF) { // while not EOF
		if (c == ' ') {
			
/* You enter this if loop if you hit one or more blank spaces.
No matter how many blank spaces there are, run through all of
 them, doing nothing. */
			
			while ((c = getchar()) == ' ') {
				; // do nothing.
			} // end while loop
			
/* c is now holding the first character afer however many blanks there were.
 Replace all the blanks by printing one blank to output, 
 then leave the if loop and print the character. */
			
			printf(" "); 
		}  // end if loop
		
		putchar(c);
		
	} // end while loop
	
	printf("\n\n");
} // end main

And now I believe I'll look at 1-10.

---------- Post updated at 13:49 ---------- Previous update was at 06:42 ----------

1-10 was a piece of cake, other than chasing my tail all over the book trying to print two backslashes...

I also don't get the idea of printing two backspaces.

If you backspace, you erase what you just typed, and there's nothing there-- so there's not ever anything to print!

Would there ever be a legitimate reason to record backspaces, and print them in output?

Seems like they would just make things complicated...

/*
 *  replace.c
 *  
 *	Exercise 1-10. Write a program to copy its input to its output, replacing each tab
 *	by \t, each backspace by \b and each backslash by \\. This makes tabs and backslashes
 *	visible in an unambiguous way.
 */

#include	<stdio.h>

main(){
	int	c; // the character
	
	while ((c = getchar())!= EOF) {
		
		if (c == '\t') { // if it's a tab
			printf("\\t");
		} // end if
		
		else if (c == '\b') {	// backspace
			printf("\\b");
		}  // end else if
		
		else if (c == '\\') {	// backslash
			printf("\\\\");
		}  // end else if
		
		else 
			putchar(c);

	} // end while loop
} // end main

Exercise 1-13, make a histogram. I would list my problems, but ohmygod...

Okay, I've been working on 1-13.

Once I broke it down into tasks, perhaps it isn't that difficult.

I have, perhaps, broken it down further than I needed to, but doing so helped me wrap my mind around the process I want the computer to do.

Now what I have is a program that runs, accepts input, and then exits, without printing my bars, and without any clues to what's going wrong.

I have a function that prints the bars, and it works, as long as I hardcode input into it.

So I built a "Project" in Xcode, hoping to use the debugger to run the program and find the problems.

I am now researching how to use the debugger; I don't understand what it's telling me. I don't know what the registers are, I don't know how to track my variables while I type input into the running program, it's like sitting in a big, powerful diesel locomotive, and not knowing where the start button is. You know you're surrounded with options, but none of them are turned on.

So if any of you have suggestions about a tutorial or ultra-basic instructions for using the Debugger in Xcode, I would be very interested in reading it.

Thank you, yet again. :slight_smile:

Slow down, young grasshopper. At 1-13, you're still a long way from functions, and even more so from using an IDE!

You have to write a program to read in a bunch of words, and then print a histogram of the length of those words. So first you'll need one loop to read the input. Since you want a histogram of the length of the words, you don't need to save the words, only their length. To save that, you need an integer array, and for an proper histogram a counter of the total number of words. How do you discern between one word an another? Let's simplify this and declare a word to be a series of letters between 'A' and 'Z' or between 'a' and 'z'. Anything else is a separator between words.

For output, loop over the array of word lengths you have. Calculate the percentage they take on the total number of words, and for that size create an appropriately sized "bar" using another, nested, loop.

Remember, the K&R book was written at a time when C programming was done with command line utilities, and all exercises can be done using nothing but an editor, a C compiler, and a command line debugger (eg. gdb).

Okay. :slight_smile:

You know this stuff awfully well. You sound like you've been through it more than once. I don't remember textbooks I blew through ten years ago, let alone when K&R was written.

You wouldn't have taught it, would you? :slight_smile:

Nope, never taught it. Haven't even read it completely yet. I've picked up a second edition back in (what Americans would call) college when Pascal got boring and Assembler was yet out of reach. But you're almost spot on on the "10 years ago" part. And my above explanation isn't even specific to C, but I'd employ (pretty much) the same algorithm for any other language.

The most important thing I learned in my programming class: it doesn't matter what languages you know, but what algorithms, and whether or not you can apply them when needed. The above algorithm could probably be written in Perl in 2 lines (4, if you insist on strictures and warnings), but the algorithm would still be the same.

Oh, dear.

I used more than two lines. I used more than two lines for the INTRODUCTION.

I re-wrote it this morning, with no functions.

Now it runs, but when you enter an EOF, it prints "Floating Point Exception", and no bars.

Here's the code I came up with.

(Warning, more than two lines. :slight_smile: )

/*
 *  hist.c
 *  
 * Exercise 1-13. Write a program to print a histogram of the lengths of words in its input.
 * This version prints a horizontal histogram. It uses the character  for the bars.
 * 
 */


#include <stdio.h>
#define	MAX_WORD_LENGTH	30	// The maximum number of letters in a word.
#define	NUMBER_WORDS 0	// To call the first member of the array.

int wordLength	[MAX_WORD_LENGTH]; // array to hold the counts; zero is the number of words

main() {
	
	int c; // counter
	int character; // the character we're looking at
	int p;	// printing counter
	int s;  // printing symbol
	
	for (c = 0; c <= MAX_WORD_LENGTH; c++) {
		wordLength[c] = 0;
	} // end initialization for loop
	
	c = 0; // set count to zero
	
	while ((character = getchar()) != EOF) { // get the character, stuff it into character, check for EOF
		
		
		if ((character >= 'a' && character <= 'z') || // if it's a lower case letter or
			character >= 'A' && character <= 'Z') {   // an upper case letter
			c++; //increment the counter
			
		} // end if
		
		else { // it's the end of the word, c holds the length of the word we just found.
			wordLength[c]++;
			wordLength[NUMBER_WORDS]++; // increment number of words
			c = 0; // re-set the counter for the next word
		} // end else
	} // end while loop
	
	/*******************
	 Printing- we've left the while loop because an EOF showed up
	 *******************/
	
	for (c = 1; c <= MAX_WORD_LENGTH; c++) {
		
		/* Note that this for loop starts with one, not zero.
		 This is because zero holds the number of words,
		 and each word had at least one letter. */
		
		/**************************************
		 This is a test printing sequence I used to make sure the right
		 members of the array were incrementing. I left it here in case
		 final protective fire full panic testing was needed.
		 
		printf("C-");
		printf("%d", c);
		printf(" is ");
		printf("%d \n", wordLength[c]);
		 **************************************/
		
		p = ((wordLength[c] / wordLength[MAX_WORD_LENGTH]) / 2); // Each symbol is 2%
		
		printf("%d", c);
		printf(" letters: ");
		
		for (s = 1; s <= p; s++) {
			printf("");
		} // end printing for loop
		
		printf("\n");
		
	} // end for loop
} // end main