Velocity Reviews > K&R exercise 1-18

# K&R exercise 1-18

Eric
Guest
Posts: n/a

 06-20-2006
Hello,

I have been working through the K&R book (only chapter one so far) examples
and exercises. After much sweat and hair pulling, I think I have a
solution for ex 1-18 on page 31.

It seems to work but may be missing some error checking. Can you please
take a look and see if my logic is correct and any other improvements.

This is not a homework but it could just as well be. I am trying to get a
handle on C programming and would eventually like to help out with some of
the GNU projects as I get closer to retirement.

/* function to strip trailing blanks and tabs from the input line */
/* and not to print any blank lines */

#include <stdio.h>

#define MAXIMUM 1000 /* maximum line length including '\0' marker */

void remblank(char line[]);

int main()
{
int c, i;
i = 0;
char line[MAXIMUM];
c = getchar();
while (c != EOF)
{
line[i] = c;
if (i == (MAXIMUM - 1)) /* make sure line is <= 1000 */
line[i] = '\n';
if (line[i] == '\n')
{
line[i + 1] = '\0';
remblank(line);
i = 0;
}
else
++i;
c = getchar();
}

/* print the last bit if there is more */
/* after the last newline */
if (c == EOF)
{
line[i] = '\0';
while (line[i - 1] == ('\t') || line[i - 1] == (' '))
{
line[i - 1] = '\0';
--i;
}
printf("%s", line);
}
return 0;
}

void remblank(char s[])
{
int i;
for(i = 0; s[i] != '\n'; i++) /* count characters in line */
;

/* if character before newline is a blank or tab */
/* replace it with a newline and make the next */
/* character an end of array marker */
while (s[i - 1] == ('\t') || s[i - 1] == (' '))
{
s[i -1] = '\n';
s[i] = '\0';
--i;
}

/* if line is not blank, print the line without */
/* the trailing blanks or tabs */
if(s[0] != '\n')
printf("%s", s);
}

Thank you,

Eric

qiangchen81@yahoo.com.cn
Guest
Posts: n/a

 06-20-2006

Eric 写道：

> Hello,
>
> I have been working through the K&R book (only chapter one so far) examples
> and exercises. After much sweat and hair pulling, I think I have a
> solution for ex 1-18 on page 31.
>
> It seems to work but may be missing some error checking. Can you please
> take a look and see if my logic is correct and any other improvements.
>
> This is not a homework but it could just as well be. I am trying to get a
> handle on C programming and would eventually like to help out with some of
> the GNU projects as I get closer to retirement.
>
> /* function to strip trailing blanks and tabs from the input line */
> /* and not to print any blank lines */
>
> #include <stdio.h>
>
> #define MAXIMUM 1000 /* maximum line length including '\0' marker */
>
> void remblank(char line[]);
>
> int main()
> {
> int c, i;
> i = 0;
> char line[MAXIMUM];
> c = getchar();
> while (c != EOF)
> {
> line[i] = c;
> if (i == (MAXIMUM - 1)) /* make sure line is <= 1000 */
> line[i] = '\n';
> if (line[i] == '\n')
> {
> line[i + 1] = '\0';
> remblank(line);
> i = 0;
> }
> else
> ++i;
> c = getchar();
> }
>
> /* print the last bit if there is more */
> /* after the last newline */
> if (c == EOF)
> {
> line[i] = '\0';
> while (line[i - 1] == ('\t') || line[i - 1] == (' '))
> {
> line[i - 1] = '\0';
> --i;
> }
> printf("%s", line);
> }
> return 0;
> }
>
> void remblank(char s[])
> {
> int i;
> for(i = 0; s[i] != '\n'; i++) /* count characters in line */
> ;
>
> /* if character before newline is a blank or tab */
> /* replace it with a newline and make the next */
> /* character an end of array marker */
> while (s[i - 1] == ('\t') || s[i - 1] == (' '))
> {
> s[i -1] = '\n';
> s[i] = '\0';
> --i;
> }
>
> /* if line is not blank, print the line without */
> /* the trailing blanks or tabs */
> if(s[0] != '\n')
> printf("%s", s);
> }
>
> Thank you,
>
> Eric

#include <stdio.h>
#include <stdlib.h>

#define MAXQUEUE 1001

{
if (pointer < MAXQUEUE - 1)
return pointer + 1;
else
return 0;
}

int main(void)
{
char blank[MAXQUEUE];
int nonspace;
int retval;
int c;
int spaceJustPrinted; /*boolean: was the last character printed
whitespace?*/

retval = spaceJustPrinted = nonspace = head = tail = 0;

while ((c = getchar()) != EOF) {
if (c == '\n') {
if (spaceJustPrinted == 1) /*if some trailing whitespace was
printed...*/
retval = EXIT_FAILURE;

if (nonspace) {
putchar('\n');
spaceJustPrinted = 0; /* this instruction isn't really
necessary since
spaceJustPrinted is only used to
determine the
return value, but we'll keep this boolean
truthful */
nonspace = 0; /* moved inside conditional just to save a
needless
assignment */
}
}
else if (c == ' ' || c == '\t') {
putchar(blank[tail]); /* these whitespace chars being printed
early
are only a problem if they are trailing,
which we'll check when we hit a \n or EOF
*/
spaceJustPrinted = 1;
nonspace = 1;
}

}
else {
putchar(blank[tail]);
}
putchar(c);
spaceJustPrinted = 0;
nonspace = 1;
}
}

/* if the last line wasn't ended with a newline before the EOF,
we'll need to figure out if trailing space was printed here */
if (spaceJustPrinted == 1) /*if some trailing whitespace was
printed...*/
retval = EXIT_FAILURE;

return retval;
}

Eric
Guest
Posts: n/a

 06-20-2006
http://www.velocityreviews.com/forums/(E-Mail Removed) wrote:

>
> Eric 写道：
>
>> Hello,
>>
>> I have been working through the K&R book (only chapter one so far)
>> examples
>> and exercises. After much sweat and hair pulling, I think I have a
>> solution for ex 1-18 on page 31.
>>
>> It seems to work but may be missing some error checking. Can you please
>> take a look and see if my logic is correct and any other improvements.
>>
>> This is not a homework but it could just as well be. I am trying to get
>> a handle on C programming and would eventually like to help out with some
>> of the GNU projects as I get closer to retirement.
>>

....program cut off to save space ....
>>
>> Thank you,
>>
>> Eric

> #include <stdio.h>
> #include <stdlib.h>
>
> #define MAXQUEUE 1001
>
> {
> if (pointer < MAXQUEUE - 1)
> return pointer + 1;
> else
> return 0;
> }
>
> int main(void)
> {
> char blank[MAXQUEUE];
> int nonspace;
> int retval;
> int c;
> int spaceJustPrinted; /*boolean: was the last character printed
> whitespace?*/
>
> retval = spaceJustPrinted = nonspace = head = tail = 0;
>
> while ((c = getchar()) != EOF) {
> if (c == '\n') {
> head = tail = 0;
> if (spaceJustPrinted == 1) /*if some trailing whitespace was
> printed...*/
> retval = EXIT_FAILURE;
>
> if (nonspace) {
> putchar('\n');
> spaceJustPrinted = 0; /* this instruction isn't really
> necessary since
> spaceJustPrinted is only used to
> determine the
> return value, but we'll keep this boolean
> truthful */
> nonspace = 0; /* moved inside conditional just to save a
> needless
> assignment */
> }
> }
> else if (c == ' ' || c == '\t') {
> putchar(blank[tail]); /* these whitespace chars being printed
> early
> are only a problem if they are trailing,
> which we'll check when we hit a \n or EOF
> */
> spaceJustPrinted = 1;
> nonspace = 1;
> }
>
> }
> else {
> while (head != tail) {
> putchar(blank[tail]);
> }
> putchar(c);
> spaceJustPrinted = 0;
> nonspace = 1;
> }
> }
>
> /* if the last line wasn't ended with a newline before the EOF,
> we'll need to figure out if trailing space was printed here */
> if (spaceJustPrinted == 1) /*if some trailing whitespace was
> printed...*/
> retval = EXIT_FAILURE;
>
> return retval;
> }

Thank you for your reply. It will take me a while to fully digest this and
descriptive which I need to remember to do. I compiled and tested it with
a test file I made with different line lengths with trailing tabs and
blanks. It gave me the same results that I got with my version. I am
struggling through the boolean logic statements (==, !=, ||) which
surprised me as I use to be pretty good with truth tables and boolean
algebra back in the day.

Thanks,

Eric
Remove "all the spam" to reply.

Peter Nilsson
Guest
Posts: n/a

 06-20-2006
> Eric 写道：
> >
> > I have been working through the K&R book (only chapter one so far) examples
> > and exercises. After much sweat and hair pulling, I think I have a
> > solution for ex 1-18 on page 31.

<snip>

(E-Mail Removed) wrote:
> #include <stdio.h>
> #include <stdlib.h>
>
> #define MAXQUEUE 1001

<snip>

You could have informed the OP where the code could be sourced from.

It looks quite similar to that at...

http://users.powernet.co.uk/eton/kandr2/krx118.html

--
Peter

Richard Heathfield
Guest
Posts: n/a

 06-20-2006
Peter Nilsson said:

>> Eric ???
>> >
>> > I have been working through the K&R book (only chapter one so far)
>> > examples
>> > and exercises. After much sweat and hair pulling, I think I have a
>> > solution for ex 1-18 on page 31.

> <snip>
>
> (E-Mail Removed) wrote:
>> #include <stdio.h>
>> #include <stdlib.h>
>>
>> #define MAXQUEUE 1001

> <snip>
>
> You could have informed the OP where the code could be sourced from.
>
> It looks quite similar to that at...
>
> http://users.powernet.co.uk/eton/kandr2/krx118.html

....which is no longer maintained. All the answers were copied over to the
clc Wiki, and - last time I looked - could be found at:

http://clc-wiki.net/mediawiki/index...._Chapter_Index

--
Richard Heathfield
"Usenet is a strange place" - dmr 29/7/1999
http://www.cpax.org.uk
email: rjh at above domain (but drop the www, obviously)

Michael Mair
Guest
Posts: n/a

 06-21-2006
Eric schrieb:
> I have been working through the K&R book (only chapter one so far) examples
> and exercises. After much sweat and hair pulling, I think I have a
> solution for ex 1-18 on page 31.

Not everyone has the exercise texts handy; just copy them (if not
too long).

> It seems to work but may be missing some error checking. Can you please
> take a look and see if my logic is correct and any other improvements.

Yes, of course.

> /* function to strip trailing blanks and tabs from the input line */
> /* and not to print any blank lines */
>
> #include <stdio.h>
>
> #define MAXIMUM 1000 /* maximum line length including '\0' marker */

Note: Often, one declares MAXIMUM to be the number of characters
_without_ the string terminator and has MAXIMUM+1 array elements.
This has the advantage that you have not to subtract 1 which happens
too often otherwise.

> void remblank(char line[]);

Your prototype's parameter name and the parameter name
in the function definition below do not match. Try to keep
them in sync -- some compilers warn you about the mismatch
as it can indicate a semantic change not communicated to
all parties.

char line[] is in this context equivalent to char *line
but obscures the fact that you are really dealing with
a pointer.

void remblank (char *s);

Actually, I will suggest a signature change further down

> int main()

int main (void)

is the preferred form around here -- it leaves absolutely
no doubt.

> {
> int c, i;

Note: c and i are used for different purposes; the former stores
characters cast to unsigned char or EOF (the return value of
getchar()), the latter is used as index.
Even if they have the same type, I'd rather separate them.

int c;
int i;

> i = 0;
> char line[MAXIMUM];

Mixing statements and declarations makes your code "C99 only"
without necessity where it could be valid C90 and valid C99.

char line[MAXIMUM];

i = 0;

> c = getchar();
> while (c != EOF)
> {
> line[i] = c;
> if (i == (MAXIMUM - 1)) /* make sure line is <= 1000 */

Here is one of the mentioned MAXIMUM-1 instances.

> line[i] = '\n';

Note: You lose one character by overwriting it with '\n'.
Try
#define MAXIMUM 8
and give
123456789012345678901234567890
as input. ungetc() or storing the excess character can solve
this problem. For yet another solution see below.

> if (line[i] == '\n')
> {
> line[i + 1] = '\0';

If i==(MAXIMUM-1), then i+1==MAXIMUM, i.e. you access a position
one _past_ the end of the array.

> remblank(line);
> i = 0;
> }
> else
> ++i;
> c = getchar();
> }
>
> /* print the last bit if there is more */
> /* after the last newline */
> if (c == EOF)
> {

Note: The previous loop only is terminated on c == EOF,
so the test is unnecessary.
You probably meant to test
if (i != 0)
> line[i] = '\0';
> while (line[i - 1] == ('\t') || line[i - 1] == (' '))

If i==0, then you are accessing something one outside the array.

> {
> line[i - 1] = '\0';
> --i;
> }

Now you are re-building the functionality of remblank() but
for '\n'.

> printf("%s", line);

Note: The last output character should be '\n' -- otherwise you
might not see the last "line".

In the light of this:
Why don't you omit the '\n' from your line? Then you can
use remblank() whenever you need it; remblank() can output
via puts() which automatically adds a '\n'.
This can solve your above problem partially.

> }
> return 0;
> }
>
> void remblank(char s[])
> {
> int i;
> for(i = 0; s[i] != '\n'; i++) /* count characters in line */
> ;

If you pass not only s but also the "length" of s, you safe
the second time.

> /* if character before newline is a blank or tab */
> /* replace it with a newline and make the next */
> /* character an end of array marker */
> while (s[i - 1] == ('\t') || s[i - 1] == (' '))

For a blank line (i==0), you are accessing storage outside
As you are even writing to this storage, you can for example
If you are very unlucky, much of the storage before s contains
a ' ' or '\t' representation -- the programme runs amok through

while (i != 0 && (....))

is safer. Or you can switch to a for loop counting down i
from which you break if s[i]!='\t'.

> {
> s[i -1] = '\n';
> s[i] = '\0';
> --i;
> }
>
> /* if line is not blank, print the line without */
> /* the trailing blanks or tabs */
> if(s[0] != '\n')

I'd rather check i != 0.

> printf("%s", s);
> }

- In main(), you are reading characters line by line but your
loop structure does not reflect that; this can make life difficult
if you want to add functionality.
- You are explicitly testing for '\t' and ' '. Consider wrapping
this into a function of its own, e.g.
int isRemovable (int c)
{
static const char * const removableChars = "\t ";
/* This is not exactly equivalent to your test, as
** it also returns 1 for c == '\0' */
return (NULL != strchr(removableChars, c));
}

Cheers
Michael
--
E-Mail: Mine is an /at/ gmx /dot/ de address.

Eric
Guest
Posts: n/a

 06-21-2006
Michael Mair wrote:

> Eric schrieb:
>> I have been working through the K&R book (only chapter one so far)
>> examples
>> and exercises. After much sweat and hair pulling, I think I have a
>> solution for ex 1-18 on page 31.

>
> Not everyone has the exercise texts handy; just copy them (if not
> too long).

That was an oversight, I will add them for future questions.

>
>> It seems to work but may be missing some error checking. Can you please
>> take a look and see if my logic is correct and any other improvements.

>
> Yes, of course.

I really appreciate you taking the time to help out. I know I have a lot of
hard work ahead to get somewhat proficient at this.
>
>> /* function to strip trailing blanks and tabs from the input line */
>> /* and not to print any blank lines */
>>
>> #include <stdio.h>
>>
>> #define MAXIMUM 1000 /* maximum line length including '\0' marker */

>
> Note: Often, one declares MAXIMUM to be the number of characters
> _without_ the string terminator and has MAXIMUM+1 array elements.
> This has the advantage that you have not to subtract 1 which happens
> too often otherwise.

Thanks for making that clear. I think my confusion with keeping this
straight in my mind contributed to the overwrite of the last character when
a line exceeded the maximum length.

>
>> void remblank(char line[]);

>
> Your prototype's parameter name and the parameter name
> in the function definition below do not match. Try to keep
> them in sync -- some compilers warn you about the mismatch
> as it can indicate a semantic change not communicated to
> all parties.

I originally had parameter names the same but I was getting compiler
exactly). I just tried to reproduce what the warning was but was
unsuccessful. I had interpreted it to mean that the parameter name in the
function definition should not be the same as the name of the variable that
the function was using. Obviously that was not correct and I wish I had
saved that message.
>
> char line[] is in this context equivalent to char *line
> but obscures the fact that you are really dealing with
> a pointer.
>
> void remblank (char *s);

I look forward to pointer is chapter 5.

>
> Actually, I will suggest a signature change further down
>
>
>> int main()

>
> int main (void)
>
> is the preferred form around here -- it leaves absolutely
> no doubt.

I should have written that way. I had seen it mentioned many times in this
group but I was using K&R examples which are not even using "int" and
forgot about the (void). It does make it more clear that there are not any
function parameters.

>> {
>> int c, i;

>
> Note: c and i are used for different purposes; the former stores
> characters cast to unsigned char or EOF (the return value of
> getchar()), the latter is used as index.
> Even if they have the same type, I'd rather separate them.
>
> int c;
> int i;
>
>> i = 0;
>> char line[MAXIMUM];

>
> Mixing statements and declarations makes your code "C99 only"
> without necessity where it could be valid C90 and valid C99.

I didn't realize that. I had "assumed" I was assigning line[] a value by
adding [MAXIMUM}. I have a long way to go before I will understand the
differences in the standards but maybe someday ...

>
> char line[MAXIMUM];
>
> i = 0;
>
>
>> c = getchar();
>> while (c != EOF)
>> {
>> line[i] = c;
>> if (i == (MAXIMUM - 1)) /* make sure line is <= 1000 */

>
> Here is one of the mentioned MAXIMUM-1 instances.
>
>> line[i] = '\n';

>
> Note: You lose one character by overwriting it with '\n'.
> Try
> #define MAXIMUM 8
> and give
> 123456789012345678901234567890
> as input. ungetc() or storing the excess character can solve
> this problem. For yet another solution see below.
>
>> if (line[i] == '\n')
>> {
>> line[i + 1] = '\0';

>
> If i==(MAXIMUM-1), then i+1==MAXIMUM, i.e. you access a position
> one _past_ the end of the array.

I wonder if this might be why I was getting segmentation faults when I was
testing earlier versions of the code. Remembering line[0] as the first
character and line[999] as the end of the array was more difficult than I

>
>> remblank(line);
>> i = 0;
>> }
>> else
>> ++i;
>> c = getchar();
>> }
>>
>> /* print the last bit if there is more */
>> /* after the last newline */
>> if (c == EOF)
>> {

>
> Note: The previous loop only is terminated on c == EOF,
> so the test is unnecessary.
> You probably meant to test
> if (i != 0)
>> line[i] = '\0';
>> while (line[i - 1] == ('\t') || line[i - 1] == (' '))

>
> If i==0, then you are accessing something one outside the array.
>
>> {
>> line[i - 1] = '\0';
>> --i;
>> }

>
> Now you are re-building the functionality of remblank() but
> for '\n'.
>
>> printf("%s", line);

>
> Note: The last output character should be '\n' -- otherwise you
> might not see the last "line".
>
> In the light of this:
> Why don't you omit the '\n' from your line? Then you can
> use remblank() whenever you need it; remblank() can output
> via puts() which automatically adds a '\n'.
> This can solve your above problem partially.

I used the "if(c == EOF)" loop because my earlier version would not print
out the last line if it did not have a newline after it. At first I added
a newline to it and then used the remblank() function. It worked but it
added a newline to the end of the output which I a first thought was good
but then decided that I should not have it add any extra characters. It
did make it look goofy in the terminal display without the extra
newline.

>
>> }
>> return 0;
>> }
>>
>> void remblank(char s[])
>> {
>> int i;
>> for(i = 0; s[i] != '\n'; i++) /* count characters in line */
>> ;

>
> If you pass not only s but also the "length" of s, you safe
> the second time.

Good point and thanks.

>
>> /* if character before newline is a blank or tab */
>> /* replace it with a newline and make the next */
>> /* character an end of array marker */
>> while (s[i - 1] == ('\t') || s[i - 1] == (' '))

>
> For a blank line (i==0), you are accessing storage outside
> As you are even writing to this storage, you can for example
> If you are very unlucky, much of the storage before s contains
> a ' ' or '\t' representation -- the programme runs amok through
>
> while (i != 0 && (....))
>
> is safer. Or you can switch to a for loop counting down i
> from which you break if s[i]!='\t'.
>

Thanks, I was thinking that a blank line would have at least an array length
of 2, the '\n' and the '\0', but got the actual length and the index count
confused. It must have been bad luck on my part that the program handled
the blank lines correctly for me cause I would have never found this error.

>> {
>> s[i -1] = '\n';
>> s[i] = '\0';
>> --i;
>> }
>>
>> /* if line is not blank, print the line without */
>> /* the trailing blanks or tabs */
>> if(s[0] != '\n')

>
> I'd rather check i != 0.
>
>> printf("%s", s);
>> }

>
>
> - In main(), you are reading characters line by line but your
> loop structure does not reflect that; this can make life difficult
> if you want to add functionality.
> - You are explicitly testing for '\t' and ' '. Consider wrapping
> this into a function of its own, e.g.
> int isRemovable (int c)
> {
> static const char * const removableChars = "\t ";
> /* This is not exactly equivalent to your test, as
> ** it also returns 1 for c == '\0' */
> return (NULL != strchr(removableChars, c));
> }
>
>
> Cheers
> Michael

Thank you very much with all your help, Michael.

Eric
--
Remove "all the spam" to reply.

pete
Guest
Posts: n/a

 06-21-2006
Eric wrote:

> I had seen it mentioned many times in this
> group but I was using K&R examples which are not even using "int" and

The second edition of K&R, is the best C book.
You should also read the errata.

http://cm.bell-labs.com/cm/cs/cbook/2ediffs.html

--
pete

Eric
Guest
Posts: n/a

 06-21-2006
pete wrote:

> Eric wrote:
>
>> I had seen it mentioned many times in this
>> group but I was using K&R examples which are not even using "int" and

>
> The second edition of K&R, is the best C book.
> You should also read the errata.
>
> http://cm.bell-labs.com/cm/cs/cbook/2ediffs.html
>

Thanks for the link. From what I have been reading in this group, I would
have to concur that K&R is the best book, but I don't have enough
experience to give any type of opinion. I do wish the font in the book was
a little bigger with more white space, but that is just my old eyes
talking.

Eric
--
Remove "all the spam" to reply.

Joe Van Dyk
Guest
Posts: n/a

 06-21-2006
Michael Mair wrote:
> Eric schrieb:
>
>> int main()

>
>
> int main (void)
>
> is the preferred form around here -- it leaves absolutely
> no doubt.

Let's see if I got this straight:

In C, main() means that the function could take any number of arguments,
while main(void) means that the function doesn't take any argments.

In C++, main() and main(void) means that the function doesn't take any
arguments.

Correct?

Thanks,
Joe