K&R exercise 1-18

E

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 = c;
if (i == (MAXIMUM - 1)) /* make sure line is <= 1000 */
line = '\n';
if (line == '\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 = '\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 != '\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 = '\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
 
Q

qiangchen81

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 = c;
if (i == (MAXIMUM - 1)) /* make sure line is <= 1000 */
line = '\n';
if (line == '\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 = '\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 != '\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 = '\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

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

int main(void)
{
char blank[MAXQUEUE];
int head, tail;
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') {
if (advance(head) == tail) {
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;
tail = advance(tail);
nonspace = 1;
}

blank[head] = c;
head = advance(head);
}
else {
while (head != tail) {
putchar(blank[tail]);
tail = advance(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;
}
 
E

Eric

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

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

int main(void)
{
char blank[MAXQUEUE];
int head, tail;
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') {
if (advance(head) == tail) {
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;
tail = advance(tail);
nonspace = 1;
}

blank[head] = c;
head = advance(head);
}
else {
while (head != tail) {
putchar(blank[tail]);
tail = advance(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
understand the logic behind your program. Your variables are much more
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.
 
M

Michael Mair

Eric said:
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
the road...

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 = c;
if (i == (MAXIMUM - 1)) /* make sure line is <= 1000 */


Here is one of the mentioned MAXIMUM-1 instances.
line = '\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 == '\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 = '\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 != '\n'; i++) /* count characters in line */
;


You are calculating a number you already had.
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
your array. This leads to undefined behaviour.
As you are even writing to this storage, you can for example
inadvertently change other variables.
If you are very unlucky, much of the storage before s contains
a ' ' or '\t' representation -- the programme runs amok through
your memory.

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

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

{
s[i -1] = '\n';
s = '\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);
}


Additional comments:
- 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

Eric

Michael said:
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.
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.
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
warnings about a shadow parameter, variable or something (I can't remember
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
the road...



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 = c;
if (i == (MAXIMUM - 1)) /* make sure line is <= 1000 */


Here is one of the mentioned MAXIMUM-1 instances.
line = '\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 == '\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
thought even though I read warnings about this many times. :)
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 = '\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 != '\n'; i++) /* count characters in line */
;


You are calculating a number you already had.
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
your array. This leads to undefined behaviour.
As you are even writing to this storage, you can for example
inadvertently change other variables.
If you are very unlucky, much of the storage before s contains
a ' ' or '\t' representation -- the programme runs amok through
your memory.

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

is safer. Or you can switch to a for loop counting down i
from which you break if s!='\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 = '\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);
}


Additional comments:
- 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
 
E

Eric

pete said:
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
 
J

Joe Van Dyk

Michael said:
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
 
G

Guest

Joe said:
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?

Almost. In C, main() in a definition means that the function takes no
arguments, but has no prototype, while main(void) means that the
function takes no arguments and has a prototype. Whichever you choose,
main may not be called with arguments, but the difference is in how the
compiler behaves if you try to anyway, and what "try" means.

(main() in a declaration means basically what you said: that the
function takes an unspecified but fixed number of arguments, the number
and types depending on the definition of the function.)

This is a valid C program:

int main() {
if(0) main(1);
}

This is an invalid C program, has undefined behaviour, but does not
require a diagnostic:

int i;
int main() {
if(i == 0) main(i = 1);
}

This is an invalid C program, and requires a diagnostic:

int main(void) {
if(0) main(1);
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top