C Programming: A Modern Approach - Chapter 15 Exercise 5

S

Simon Morgan

Hi,

Does anybody have a solution or a hint for Exercise 5 of Chapter 15 of
K.N. King's book C Programming: A Modern Approach? I spent all of
yesterday evening staring at it and drooling. The author sure does have a
knack for making me feel like a dumbass, or maybe it's just that I am.

The following code pads out sentences with extra spaces between words so
that each line takes up the same amount of space on screen. As is, the
code will favour the end of the line when distributing the extra spaces.
The task is to modify it so that it alternates between distribution of the
extra spaces favouring the end of the line and the beginning of the line.

void write_line(void)
{
int extra_spaces, spaces_to_insert, i, j;

extra_spaces = MAX_LINE_LEN - line_len;
for (i = 0; i < line_len; i++) {
if (line != ' ')
putchar(line);
else {
spaces_to_insert = extra_spaces / (num_words - 1);
for (j = 1; j <= spaces_to_insert + 1; j++)
putchar(' ');
extra_spaces -= spaces_to_insert;
num_words--;
}
}
putchar('\n');
}

I cannot for the life of me figure out a solution beyond parsing the
sentence first and adding the number of spaces to an array to be used when
printing the sentence to screen, but this feels like an awful kludge. The
closest I've come to a decent solution is to add extra_spaces % (num_words
- 1) to spaces_to_insert but this results in the extra spaces not being
distributed evenly.

Thanks.
 
S

Steffen Buehler

Simon said:
The following code pads out sentences with extra spaces between words
so that each line takes up the same amount of space on screen. As is,
the code will favour the end of the line when distributing the extra
spaces. The task is to modify it so that it alternates between
distribution of the extra spaces favouring the end of the line and
the beginning of the line.

void write_line(void)
{
int extra_spaces, spaces_to_insert, i, j;

extra_spaces = MAX_LINE_LEN - line_len;
for (i = 0; i < line_len; i++) {
if (line != ' ')
putchar(line);
else {
spaces_to_insert = extra_spaces / (num_words - 1);


This is the troublemaker. Let's say you have 8 extra spaces and 4 words.
Then spaces_to_insert will be 8/3 = 2.666 and be _cut_ _off_ to 2. So
"too few" spaces will be inserted first.

If you want to have "too much" spaces first, change this line to

spaces_to_insert = int(0.5 + extra_spaces / (num_words - 1.0);

(Beware to make the division floating-point by using 1.0 instead of 1 or
casting extra_spaces to float, otherwise the rounding will not work.)
for (j = 1; j <= spaces_to_insert + 1; j++)
putchar(' ');
extra_spaces -= spaces_to_insert;
num_words--;
}
}
putchar('\n');
}

Regards
Steffen
 
S

Simon Morgan

spaces_to_insert = int(0.5 + extra_spaces / (num_words - 1.0);

(Beware to make the division floating-point by using 1.0 instead of 1 or
casting extra_spaces to float, otherwise the rounding will not work.)

Thanks for the quick reply Steffen.

Just one question. Is int a function or am I supposed to cast the result
of the expression to an int? If I cast it the output isn't how I would
expect it to be or how I think the book expects it to be (the spaces seem
to be randomly distributed throughout the line) so I assume it's a
function but I can't seem to find any mention of it in the book or in the
man pages installed on my system.
 
S

Suman

Simon said:
Thanks for the quick reply Steffen.

Just one question. Is int a function or am I supposed to cast the result
of the expression to an int?

May I suggest you get a book? Or, the standard. Or, atleast the draft.
`int' is one of the standard signed integer types(6.2.5.)
And a keyword (6.4.1.)
 
S

Steffen Buehler

Simon said:
Is int a function or am I supposed to cast the
result of the expression to an int?

It's a simple cast, sorry for the confusion. The line should read

spaces_to_insert = (int)(0.5 + extra_spaces / (num_words - 1.0));

(I forgot the last closing bracket as well...)
If I cast it the output isn't how
I would expect it to be or how I think the book expects it to be (the
spaces seem to be randomly distributed throughout the line)

Strange. It works fine here. What did you do with the missing bracket?
What are your values for extra_spaces and num_words?

Regards
Steffen
 
S

Simon Morgan

It's a simple cast, sorry for the confusion. The line should read

spaces_to_insert = (int)(0.5 + extra_spaces / (num_words - 1.0));

(I forgot the last closing bracket as well...)

Strange. It works fine here. What did you do with the missing bracket?

Mine looks the same as your corrected version.
What are your values for extra_spaces and num_words?

extra_spaces = 9
num_words = 9
C is quirky, flawed, and an enormous success. While
extra_spaces = 0
num_words = 9
accidents of history surely helped, it evidently satisfied a
extra_spaces = 2
num_words = 8
need for a system implementation language efficient enough
extra_spaces = 0
num_words = 8
to displace assembly language, yet sufficiently abstract and
extra_spaces = 4
num_words = 9
fluent to describe algorithms and interactions in a wide
variety of environments. -- Dennis M. Ritchie
 
S

Simon Morgan

extra_spaces = 9
num_words = 9
C is quirky, flawed, and an enormous success. While
extra_spaces = 0
num_words = 9

Apologies for the crappy formatting. I don't know whether It's my news
server or client but it certainly didn't look like that before I posted
it.

extra_spaces = 9
num_words = 9
C is quirky, flawed, and an enormous success. While
extra_spaces = 0
num_words = 9
accidents of history surely helped, it evidently satisfied a
extra_spaces = 2
num_words = 8
need for a system implementation language efficient enough
extra_spaces = 0
num_words = 8
to displace assembly language, yet sufficiently abstract and
extra_spaces = 4
num_words = 9
fluent to describe algorithms and interactions in a wide
variety of environments. -- Dennis M. Ritchie
 
S

Steffen Buehler

Simon said:
extra_spaces = 9
num_words = 9
C is quirky, flawed, and an enormous success. While

Let's stick to this. Here's what I get:

| C is quirky, flawed, and an enormous success. While

How come you insert most spaces at the last position but one? Your
remaining code must somewhere differ from the one you posted here.

Regards
Steffen
 
S

Simon Morgan

How come you insert most spaces at the last position but one? Your
remaining code must somewhere differ from the one you posted here.

Do you have code for alternating between endianness? I haven't bothered
with that because it should be trivial to add. Right now all I'm trying to
do is get space distribution to favour the beginning of the line (the
non-floating point algorithm but in reverse if you see what I mean).

Here is my current code:

void write_line(void)
{
int extra_spaces, spaces_to_insert, i, j;

extra_spaces = MAX_LINE_LEN - line_len;
printf("extra_spaces = %d\n", extra_spaces);
printf("num_words = %d\n", num_words);
for (i = 0; i < line_len; i++) {
if (line != ' ')
putchar(line);
else {
spaces_to_insert = (int)(0.5 + extra_spaces / (num_words - 1.0));
for (j = 1; j <= spaces_to_insert + 1; j++)
putchar(' ');
extra_spaces -= spaces_to_insert;
num_words--;
}
}
putchar('\n');
}
 
R

Richard Tobin

Simon Morgan said:
The task is to modify it so that it alternates between distribution of the
extra spaces favouring the end of the line and the beginning of the line.

It's not really answering your question, but this is an example of the
large class of problems that can be solved with something equivalent
to Bressenham's algorithm. That algorithm is traditionally used for
drawing straight lines on a pixel display, but can be used for all
kinds of problems where you want to approximate division using integer
increments.

In this case, suppose you want to divide 8 spaces among 5 gaps. Start
with zero. At each gap, then add 8 and subtract off as many 5s as you
can, and for each one add a space to that gap. Then proceed to the
next gap using the remainder as the starting value.

So we have

0+8 = 8 = 1*5 + 3 => 1 space
3+8 = 11 = 2*5 + 1 => 2 spaces
1+8 = 9 = 1*5 + 4 => 1 space
4+8 = 12 = 2*5 + 2 => 2 spaces
2+8 = 10 = 2*5 => 2 spaces

-- Richard
 
S

Steffen Buehler

Simon said:
Here is my current code:

I see. I found that my first solution doesn't always work since the
rounding/casting produces errors that sooner or later lead to the
effects you (and in the meantime I) experienced.

To overcome this I now made extra_spaces and spaces_to_insert float. The
code will remain the same, only the calculation of spaces_to_insert
turns to

spaces_to_insert = ceil(extra_spaces / (num_words - 1.0));

or

spaces_to_insert = floor(extra_spaces / (num_words - 1.0));

whichever you prefer. Works fine here.

Regards
Steffen
 
C

CBFalconer

Simon said:
Does anybody have a solution or a hint for Exercise 5 of Chapter
15 of K.N. King's book C Programming: A Modern Approach? I spent
all of yesterday evening staring at it and drooling. The author
sure does have a knack for making me feel like a dumbass, or maybe
it's just that I am.

The following code pads out sentences with extra spaces between
words so that each line takes up the same amount of space on
screen. As is, the code will favour the end of the line when
distributing the extra spaces. The task is to modify it so that
it alternates between distribution of the extra spaces favouring
the end of the line and the beginning of the line.

Here is something I threw together some time ago. I never did the
alternation part, but I did arrange to keep track of when needed.
Doing that will basically require a suitable buffer, rather than
simple stream output with putc.

/* ----- justify.c -----
Filter text file, right justifying by inserting
spaces between words. Words are anything separated
by blanks, tabs, newlines, formfeeds, bell, etc.

The single (optional) parameter is the output line
length, and defaults to 65. Execution without any
input redirections causes a help message.

This is a quick and dirty utility. 2003-01-28.
Released to public domain by:
<mailto:[email protected]>
*/

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

#define RHDEFAULT 65
#define RHMIN 20

static int rhcol; /* right hand column limit */
static int ragged; /* No rh justification, 0 init */

/* ------------------- */

/* This is very likely to be non-portable */
/* DOES NOT check fp open for reading */
/* NULL fp is considered a keyboard here! */
static int akeyboard(FILE *fp)
{
#ifndef __TURBOC__
# ifdef __STDC__
/* This dirty operation allows gcc -ansi -pedantic */
extern int fileno(FILE *fp);
extern int isatty(int fn);
# endif
#endif
return ((fp != NULL) && isatty(fileno(fp)));
} /* akeyboard */

/* ------------------- */

static void help(char *phrase1, char *phrase2)
{
if (phrase1) fprintf(stderr, "%s", phrase1);
if (phrase2) fprintf(stderr, "%s", phrase2);
fprintf(stderr, "\n"
"Usage: justify [rightmargin] <infile >outfile\n"
" The default rightmargin is 65\n"
" and values less than 20 are rejected\n"
"\n"
"A large value of rightmargin will effectively\n"
"convert all paragraphs into single lines\n"
"\n"
"A negative rightmargin causes ragged right\n"
"\n"
"A blank line delimits paragraphs\n");
} /* help */

/* ------------------- */

static int initialize(int argc, char *argv[])
{
long rightcol;
char *err;

if (akeyboard(stdin) || (argc > 2)) {
help(NULL, NULL);
return 0;
}
rhcol = RHDEFAULT;
if (2 == argc) {
rightcol = strtol(argv[1], &err, 10);
if (rightcol < 0) {
rightcol = -rightcol;
ragged = 1;
}
if ((err == argv[1]) || (rightcol < RHMIN)) {
help("Bad argument: ", argv[1]);
return 0;
}
else rhcol = rightcol;
}
return 1;
} /* initialize */

/* ------------------- */

static void cleanup(void)
{
} /* cleanup */

/* ------------------- */

/* ================================== */
/* Routines for text input and output */
/* ================================== */

static void skipblanks(FILE *f)
{
int ch;

while ( (' ' == (ch = getc(f))) || ('\t' == ch) ||
('\v' == ch) || ('\f' == ch) || ('\a' == ch) )
continue;
ungetc(ch, f);
} /* skipblanks */

/* ------------------- */

/* The file is assumed to hold no control chars */
/* other than \n \t \v \a and \f. A blank line */
/* marks a paragraph ending word */
static int nextword(FILE *f, char *buffer, int max)
{
int i, ch;

skipblanks(f);
if (EOF == (ch = getc(f))) return 0;

/* Detect paragraph endings as \n\n */
if ('\n' == ch) {
skipblanks(f); ch = getc(f);
if ('\n' == ch) { /* paragraph ending */
buffer[0] = buffer[1] = ch; /* wd = "\n\n" */
buffer[2] = '\0';
/* now we have to absorb any more blank lines */
do {
skipblanks(f); ch = getc(f);
} while ('\n' == ch);
ungetc(ch, f);
return 1;
}
}
/* now ch holds the first non-blank. Use all printable */
if (EOF == ch) return 0;
if (!isgraph(ch)) {
fprintf(stderr, "'%c', 0x%x WARN: Invalid character\n",
ch, (unsigned)ch);
}

i = 0;
do {
buffer[i++] = ch;
if (i >= max) { /* truncate over long words */
i--;
break; /* leaving ch for next word */
}
ch = getc(f);
} while (isgraph(ch));

ungetc(ch, f); /* save for next word, may be \n */
buffer = '\0'; /* terminate string */
return 1;
} /* nextword */

/* ------------------- */

static void justify(char *ln, int wdgaps, int xtra, FILE *out)
{
int insert, i;
static int oddln = 0; /* for rt left blank insertion */
char ch;

#ifdef DEBUG
fprintf(out, "%2d %2d ", wdgaps, xtra);
#endif
insert = 0; oddln = !oddln;
if (wdgaps)
while (xtra > wdgaps) {
insert++; xtra -= wdgaps;
}
while ((ch = *ln++)) {
putc(ch, out);
if (' ' == ch) {
if (xtra) {
xtra--;
putc(' ', out);
}
for (i = insert; i; i--) putc(' ', out);
}
}
putc('\n', out);
} /* justify */

/* ------------------- */

static int filter(FILE *in, FILE *out)
{
char *buf;
char *ln;
int wdcount, lnlgh, wdlgh;
char *eop = "\n\n"; /* end of paragraph */
int done, endpar;

if (!(buf = malloc(rhcol+1))) exit(EXIT_FAILURE);
if (!(ln = malloc(rhcol+1))) exit(EXIT_FAILURE);

done = !nextword(in, buf, rhcol + 1);
endpar = !strcmp(buf, eop);

while (!endpar && !done) {
/* form paragraph */
wdlgh = strlen(buf);
wdcount = 0;
*ln = '\0'; lnlgh = 0;

while ((((lnlgh + wdlgh) < rhcol) || !lnlgh)
&& !done && !endpar) {
/* form a line */
if (lnlgh) ln[lnlgh++] = ' ';
strcpy(ln + lnlgh, buf);
lnlgh += wdlgh;
wdcount++;

done = !nextword(in, buf, rhcol + 1);
endpar = !strcmp(buf, eop);
wdlgh = strlen(buf);
}

/* dump the line, wdcount words */
if (endpar || done) lnlgh = rhcol;
if (ragged) fprintf(out, "%s\n", ln);
else justify(ln, wdcount-1, rhcol-lnlgh, out);

if (endpar) {
fputc('\n', out);
done = !nextword(in, buf, rhcol + 1);
endpar = !strcmp(buf, eop);
}
}
return 0;
} /* filter */

/* ------------------- */

int main(int argc, char *argv[])
{
if (!initialize(argc, argv)) return EXIT_FAILURE;
else {
(void)filter(stdin, stdout);
cleanup();
}
return 0;
} /* main */
 
T

Tim Rentsch

Simon Morgan said:
Hi,

Does anybody have a solution or a hint for Exercise 5 of Chapter 15 of
K.N. King's book C Programming: A Modern Approach? I spent all of
yesterday evening staring at it and drooling. The author sure does have a
knack for making me feel like a dumbass, or maybe it's just that I am.

The following code pads out sentences with extra spaces between words so
that each line takes up the same amount of space on screen. As is, the
code will favour the end of the line when distributing the extra spaces.
The task is to modify it so that it alternates between distribution of the
extra spaces favouring the end of the line and the beginning of the line.

void write_line(void)
{
int extra_spaces, spaces_to_insert, i, j;

extra_spaces = MAX_LINE_LEN - line_len;
for (i = 0; i < line_len; i++) {
if (line != ' ')
putchar(line);
else {
spaces_to_insert = extra_spaces / (num_words - 1);
for (j = 1; j <= spaces_to_insert + 1; j++)
putchar(' ');
extra_spaces -= spaces_to_insert;
num_words--;
}
}
putchar('\n');
}

I cannot for the life of me figure out a solution beyond parsing the
sentence first and adding the number of spaces to an array to be used when
printing the sentence to screen, but this feels like an awful kludge. The
closest I've come to a decent solution is to add extra_spaces % (num_words
- 1) to spaces_to_insert but this results in the extra spaces not being
distributed evenly.


What I think you want is, if the number of extra spaces is N, and the
number of separations (ie, 'num_words-1') is S, then there should be

N/S spaces between every word, and also
1 extra space at each of the N%S gaps at the
{beginning,end} of the line.

If the gap number is g, 0 <= g < num_words-1, then the expressions

g < extra_spaces%(num_words-1)

and

g + extra_spaces%(num_words-1) >= num_words-1

should, when added to extra_spaces/(num_words-1), give the number of
spaces that should be printed at each gap, for favoring the beginning
of the line and the end of the line respectively.
 
T

Tim Rentsch

It's not really answering your question, but this is an example of the
large class of problems that can be solved with something equivalent
to Bressenham's algorithm. That algorithm is traditionally used for
drawing straight lines on a pixel display, but can be used for all
kinds of problems where you want to approximate division using integer
increments.

In this case, suppose you want to divide 8 spaces among 5 gaps. Start
with zero. At each gap, then add 8 and subtract off as many 5s as you
can, and for each one add a space to that gap. Then proceed to the
next gap using the remainder as the starting value.

So we have

0+8 = 8 = 1*5 + 3 => 1 space
3+8 = 11 = 2*5 + 1 => 2 spaces
1+8 = 9 = 1*5 + 4 => 1 space
4+8 = 12 = 2*5 + 2 => 2 spaces
2+8 = 10 = 2*5 => 2 spaces

Right, except Bresenham's algorithm usually adds 2x number of spaces
at each gap, and subtracts 2x number of gaps for each "increment"
(which is printing a space in this case), so that the comparison value
of 1x gaps can be used, which is "1/2" in the rationalized number
system.
 
S

Simon Morgan

If the gap number is g, 0 <= g < num_words-1, then the expressions

g < extra_spaces%(num_words-1)

and

g + extra_spaces%(num_words-1) >= num_words-1

should, when added to extra_spaces/(num_words-1), give the number of
spaces that should be printed at each gap, for favoring the beginning
of the line and the end of the line respectively.

Thanks for your help.

Is this implementation correct? Because using it I just get seemingly
random distribution of extra spaces.

void write_line(void)
{
int extra_spaces, spaces_to_insert, i, j;
int endianness = 0, gap = 0;

extra_spaces = MAX_LINE_LEN - line_len;
for (i = 0; i < line_len; i++) {
if (line != ' ')
putchar(line);
else {
gap++;
spaces_to_insert = extra_spaces / (num_words - 1);
if (0 <= gap && gap < num_words - 1) {
if (endianness) {
spaces_to_insert += gap < extra_spaces % (num_words-1);
endianness = 0;
} else {
spaces_to_insert += gap + extra_spaces % (num_words-1) >= num_words-1;
endianness = 1;
}
}
for (j = 1; j <= spaces_to_insert + 1; j++)
putchar(' ');
extra_spaces -= spaces_to_insert;
num_words--;
}
}
putchar('\n');
}
 
M

Malcolm

Simon Morgan said:
Does anybody have a solution or a hint for Exercise 5 of Chapter 15 of
K.N. King's book C Programming: A Modern Approach?
I've got a hint. The solution is to use an old-fashioned approach.
 
T

Tim Rentsch

Simon Morgan said:
Thanks for your help.

You're most welcome.

Is this implementation correct? Because using it I just get seemingly
random distribution of extra spaces.

void write_line(void)
{
int extra_spaces, spaces_to_insert, i, j;
int endianness = 0, gap = 0;

extra_spaces = MAX_LINE_LEN - line_len;
for (i = 0; i < line_len; i++) {
if (line != ' ')
putchar(line);
else {
gap++;
spaces_to_insert = extra_spaces / (num_words - 1);
if (0 <= gap && gap < num_words - 1) {
if (endianness) {
spaces_to_insert += gap < extra_spaces % (num_words-1);
endianness = 0;
} else {
spaces_to_insert += gap + extra_spaces % (num_words-1) >= num_words-1;
endianness = 1;
}
}
for (j = 1; j <= spaces_to_insert + 1; j++)
putchar(' ');
extra_spaces -= spaces_to_insert;
num_words--;
}
}
putchar('\n');
}


Two comments.

One, the test of the condition '0 <= gap && gap < num_words - 1'
should always return true, assuming things are working properly.
Rather than test the expression with an 'if', you could (and
I believe should) write it as an assert:

assert( 0 <= gap && gap < num_words - 1 );

There needs to be a '#include <assert.h>', of course.

Two, I see where you went wrong but I'm not sure what led you to go
wrong. Try a simple example: let's suppose the number of extra
spaces is six, and the number of gaps (num_words-1) is three. That
should be two spaces per gap (not counting the given one, and no
variation between the gaps, since three divides six evenly). At the
first gap the code would calculate

spaces_to_insert = extra_spaces / (num_words-1); /* 6/3 == 2 */

which is fine. At the second gap the code would calculate

spaces_to_insert = extra_spaces / (num_words-1); /* 4/3 == 1 */

because we did 'extra_spaces -= spaces_to_insert;' (or in other words
subtracted 2, giving 4) after the first gap. At the third gap the
code would calculate

spaces_to_insert = extra_spaces / (num_words-1); /* 3/3 == 1 */

because after the second gap 1 was subtracted from extra_spaces. You
see the problem? Subtracting 'spaces_to_insert' from 'extra_spaces'
at each gap makes the code do the wrong thing.


Also, a suggestion. Use braces with if, for, etc, in all cases where
the statement(s) in the body are on subsequent lines, even when there
is only one controlled statement. The more consistent rule reduces
cognitive load when reading code, and is less error prone when
modifying code (as you will find out if you ever try adding a line to
a single line code "block" that isn't surrounded by braces, as most of
us have at one time or another).
 

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

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top