The C Programming Language, 2nd Edition, by Kernighan and Ritchie
Exercise 1.18 on page 31
Write a program to remove all trailing blanks and tabs from each line of input, and to delete entirely blank lines.
Solution by Vidhan Gupta
/* Write a program to remove trailing blanks and tabs from each line of input, and to delete entirely blank lines. */ #include <stdio.h> #define MAXIMUM 1000 int getLine(char s[], int lim); void removeBlanks(char s[], int len); int main() { int len; char line[MAXIMUM]; while ((len = getLine(line, MAXIMUM)) > 0) { removeBlanks(line, len); printf("%s", line); } return 0; } int getLine(char s[], int lim) { int i, c; 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 removeBlanks(char s[], int len) { for (int i = 0; i <= len; i++) { if (s[i] == ' ' || s[i] == '\t') s[i] = '\b'; else i = len + 1; } }
INPUT: What the hell is this? OUTPUT: What the hell is this?
Solution by Xggggg
#include <stdio.h> #define MAXLINE 1000 int getline(char str[], int lim); int main(void) { int len; char line[MAXLINE]; while ((len = getline(line, MAXLINE)) > 0) if (len > 1) printf("%s", line); return 0; } int getline(char str[], int lim) { int c, i, j; i = j = 0; while ((c = getchar()) != EOF && c != '\n') { if (j < lim) str[j++] = c; if (c != ' ' && c != '\t') i = j; } if (c == '\n' && i++ < lim) str[i - 1] = '\n'; str[(i < lim) ? i : lim - 1] = '\0'; return i; }
Solution by Ben Pfaff and a modification thereof by Chris Sidi
/* K&R2 1-18 p31: Write a program to remove trailing blanks and tabs from each line of input, and to delete entirely blank lines. The program specification is ambiguous: does "entirely blank lines" mean lines that contain no characters other than newline, or does it include lines composed of blanks and tabs followed by newline? The latter interpretation is taken here. This implementation does not use any features not introduced in the first chapter of K&R2. As a result, it can't use pointers to dynamically allocate a buffer to store blanks that it has seen, so it must limit the number of blanks that are allowed to occur consecutively. (This is the value of MAXQUEUE, minus one.) It is intended that this implementation "degrades gracefully." Even though a particular input might have 1000 or more blanks or tabs in a row, causing a problem for a single pass, multiple passes through the file will correct the problem. The program signals the need for such an additional pass by returning a failure code to the operating system. (EXIT_FAILURE isn't mentioned in the first chapter of K&R, but I'm making an exception here.) */ #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; retval = nonspace = head = tail = 0; while ((c = getchar()) != EOF) { if (c == '\n') { head = tail = 0; if (nonspace) putchar('\n'); nonspace = 0; } else if (c == ' ' || c == '\t') { if (advance(head) == tail) { putchar(blank[tail]); tail = advance(tail); nonspace = 1; retval = EXIT_FAILURE; } blank[head] = c; head = advance(head); } else { while (head != tail) { putchar(blank[tail]); tail = advance(tail); } putchar(c); nonspace = 1; } } return retval; }
Solution by Chris Sidi
Chris Sidi writes:
Ben, I thought your solution to 1-18 was really neat (it didn't occur to me when I was doing the exercise), the way it degrades gracefully and multiple passes can get rid of huge blocks of whitespace. However, if there is a huge block of non-trailing whitespace (eg "A",2000 spaces, "B\n") your program returns an error when there's not a need for it. And if someone were to use your program till it passes it will loop infinitely: $ perl -e 'print "A"," "x2000,"B\n";' > in $ until ./a.out < in > out; do echo failed, running another pass; cp out in; done failed, running another pass failed, running another pass failed, running another pass [snip] Below I have added a variable spaceJustPrinted to your program and check to see if the spaces printed early are trailing. I hope you like the minor improvement. (Though I can understand if you don't give a [1] :))
[1] expletive deleted - RJH.
/* K&R2 1-18 p31: Write a program to remove trailing blanks and tabs from each line of input, and to delete entirely blank lines. The program specification is ambiguous: does "entirely blank lines" mean lines that contain no characters other than newline, or does it include lines composed of blanks and tabs followed by newline? The latter interpretation is taken here. This implementation does not use any features not introduced in the first chapter of K&R2. As a result, it can't use pointers to dynamically allocate a buffer to store blanks that it has seen, so it must limit the number of blanks that are allowed to occur consecutively. (This is the value of MAXQUEUE, minus one.) It is intended that this implementation "degrades gracefully." Even though a particular input might have 1000 or more trailing blanks or tabs in a row, causing a problem for a single pass, multiple passes through the file will correct the problem. The program signals the need for such an additional pass by returning a failure code to the operating system. (EXIT_FAILURE isn't mentioned in the first chapter of K&R, but I'm making an exception here.) */ #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; }
Solution by Pilcrow
/*********************************************** The other code is so complicated. Here is mine -- Pilcrow ***********************************************/ /* Perhaps the others are complicated because they are correct, or at least less broken than yours? (Well, apart from the one from TheSunShadow, perhaps?) First, this strips all _preceding_ spaces on a line. Second it searches for and strips (not tested) NULLs. Third it outputs a NULL after every output line-break. None are part of the spec. -- Nick */ #include <stdio.h> #define MAXLINE 10000 char line[MAXLINE+1]; int getline(void); int main(void) { extern char line[]; int len, head, tail, inn; while((len=getline()) > 0) { for(head = 0; line[head] == ' ' || line[head] == '\t'; head++); for(tail = len; line[tail] == ' ' || line[tail] == '\t' || line[tail] == '\n' || line[tail] == '\0';tail--); if(tail - head >= 0){ for(inn = head; inn <= tail; inn++) putchar(line[inn]); putchar('\n'); putchar('\0'); } } return 0; } int getline(void) { extern char line[]; int c, i; for(i = 0; i < MAXLINE-1 && (c=getchar()) != EOF && c != '\n'; ++i) line[i] = c; if (c == '\n') { line[i] = c; ++i; } line[i] = '\0'; return i; }
Solution by Nngnna
minimal fix of Pilcrow's solution
/*********************************************** The other code is so complicated. Here is mine -- Pilcrow ***********************************************/ #include <stdio.h> #define MAXLINE 10000 char line[MAXLINE+1]; int getline(void); int main(void) { extern char line[]; int len, tail, inn; while((len=getline()) > 0) { for(tail = len-1; line[tail] == ' ' || line[tail] == '\t' || line[tail] == '\n' ;tail--); if(tail >= 0){ for(inn = 0; inn <= tail; inn++) putchar(line[inn]); putchar('\n'); } } return 0; } /* no changes to getline() */
Solution by TheSunShadow
/*********************************************** I wrote a version that does not depend of any maximum length of input lines -- TheSunShadow ***********************************************/ /* ...and that is fundamentally broken! Test it with a file of ten spaces followed by a line-break, or with ten (or more) spaces at the end of a line whose length is a multiple of ten not including the line-break. Kinda obvious, really... Whoops! -- Nick */ #include <stdio.h> /* We are getting pieces of lines of arbitrary length without the need of getting the full line at once. */ #define MAXLINE 10 int getline(char s[], int lim); main(void) { char line[MAXLINE]; int i, j, k, len, max = 0; while((len = getline(line, MAXLINE)) > 0){ /* There are two kind of pieces of lines, whether they are at the end of the line or not. */ if(line[len - 1] != '\n') // This is not an end of line, printf("%s", line); // then write the input as is. else{ // This is an end of line. j = 0; /* Count the number of spaces or tabs at the end. */ while((line[len - 2 - j] == ' ') || (line[len - 2 - j] == '\t')) ++j; /* Write the input without the unwanted characters. */ if(j < len - 1) for(k = 0; k < (len - 1 - j); ++k) printf("%c", line[k]); printf("\n"); // This is to recover the right // format at the end of line. } } } int getline(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; s[++i] = '\0'; } else if(c == EOF && i > 0){ s[i] = '\n'; s[++i] = '\0'; } return i; }
Solution by Amarendra Godbole
#include <stdio.h> #define MAXLINE 1000 /* delete trailing blanks, tabs, and blank lines */ int getline(char s[], int max); int main(void) { int len, i; char line[MAXLINE], longest[MAXLINE]; while ((len = getline(line, MAXLINE)) != 0) { if (len > 1) { for (i = len-1; (line[i] == ' ' || line[i] == '\t' || line[i] == '\n'); i--) ; line[++i] = '\n'; line[++i] = '\0'; printf("%s", line); } } return 0; } int getline(char s[], int max) { int i, c; for (i=0; i<max-1 && (c=getchar())!=EOF && c!='\n'; ++i) { s[i] = c; } if (c == '\n') { s[i] = c; i++; } s[i] = '\0'; return i; }
Solution by CakeOfTrust
The simple yet flawed version. Its major problem so far is that it does not recreate the order in which the blanks and tabs were positioned. Instead it formats EVERYTHING into 'tabs first, blanks second' which might come in handy but more often causes frustration.
#include <stdio.h> int main(void) { int c[2], blnk[2]; blnk[1] = blnk[0] = 0; c[0] = '\n'; while ((c[1] = getchar()) != EOF) { if (c[1] == ' ') ++blnk[1]; else if (c[1] == '\t') ++blnk[0]; else { if (c[1] != '\n' && (blnk[0] + blnk[1]) > 0) { for (; blnk[0] > 0; --blnk[0]) putchar('\t'); for (; blnk[1] > 0; --blnk[1]) putchar(' '); putchar(c[1]); } else if (c[1] == '\n' && c[0] == '\n') ; else putchar(c[1]); if (blnk[0] > 0) blnk[0] = 0; if (blnk[1] > 0) blnk[1] = 0; c[0] = c[1]; /* To delete empty lines after detabbing and blank deletion one must put { after the first 'else' and } after this assignment operator. To restore them one would need to remove {} mentioned before. */ } } return 0; }
This solution takes inspiration from Ben Paff's solution. It is different in the way it deals with array of tabs overflow. It can delete the exactly specified number of whitespaces, not the one less by 1. It is also more lengthy which is not good.
#include <stdio.h> #define MAXLEN 6 #define YES 1 #define NO 0 int advance(int p); int advance(int pointer) { if (pointer < MAXLEN - 1) return pointer + 1; else return 0; } int main(void) { char ws[MAXLEN]; int c[2], end, start, overflow = NO; c[0] = '\n'; end = start = -1; while ((c[1] = getchar()) != EOF) { if (c[1] == ' ' || c[1] == '\t') { if (end < start) { putchar(ws[start]); start = advance(start); ws[end = end + 1] = c[1]; } else { if (end < MAXLEN - 1) ++end; else { end = 0; putchar(ws[end]); if (overflow == NO) overflow = YES; start = advance(start); } ws[end] = c[1]; if (start == -1) ++start; } } else { if (c[1] != '\n') { if (end > start) for (; start <= end; ++start) putchar(ws[start]); else if (end < start) { for (; start < MAXLEN; ++start) putchar(ws[start]); for (start = end; end > -1; --end) putchar(ws[start - end]); } else if (end > -1) putchar(ws[end]); putchar(c[1]); } else if (c[1] == '\n' && c[0] == c[1]) { if (overflow == YES) putchar(c[1]); } else putchar(c[1]); c[0] = c[1]; if (start != -1) end = start = -1; if (overflow == YES) overflow = NO; } } return 0; }
Solution by ifox12
#include <stdio.h> #define MAXLINE 1024 int main() { int c; char buf[MAXLINE]; int count = 0; while ((c = getchar()) != EOF) { // read chars till newline if (c != '\n' && count - 1 < MAXLINE) { buf[count++] = c; } else { // skip trailing whitespace while(buf[count - 1] == ' ' || buf[count - 1] == '\t') { count--; } // add nl and \0 buf[count] = c; buf[count + 1] = 0; if (count != 0) { // after skipping all other whitespace is there something left besides the nl? printf("%s", buf); count = 0; } } } }
Solution by Tobar Gadvesi
#include <stdio.h> #define MAXLINE 1000 int get_line(char line[], int maxline); main() { int len; char line[MAXLINE]; while ((len = get_line(line, MAXLINE)) > 0) { if (line[len-1] == '\n') --len; while (line[len-1] == ' ' || line[len-1] == '\t') { line[len-1] = '\n'; line[len] = '\0'; --len; } if (line[0] == '\n') ; else printf("%s", line); } return 0; } int get_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; }
Solution by Krasimir Berov
A seemingly correct solution handling all corner cases and lines with unlimited length. Using only what we know from this chapter so far.
#include <stdio.h> /* Exercise 1-18. Write a program to remove trailing blanks and tabs * from each line of input, and to delete entirely blank lines. */ #define MAX 70 int get_line(char line[]); int main() { int len; /* current line length */ char line[MAX]; while ((len = get_line(line)) > -1) if (len != 0) printf("%s", line); return 0; } /* read a line into s, return length */ int get_line(char s[]) { int c, i = 0; char previous = 'A'; /* some default value */ /* copy each character into the corresponding `s`'s slot */ for (i; i<MAX && ((c = getchar()) != EOF) && (c != '\n'); ++i) { /* do not add repeating blanks and tabs */ if ((previous == ' ' || previous == '\t') && (c == ' ' || c == '\t')) { --i; } else { s[i] = c; previous = c; } } /* remove trailing tabs and spaces */ if (s[i - 1] == '\t' || s[i - 1] == ' ') { s[i - 1] = '\n'; s[i] = '\0'; return i; } /* File is exhausted */ if (c == EOF) return -1; /* an empty line */ else if (c == '\n' && i == 0) return i; else if (c == '\n') { s[i] = c; } /* finish the string - an array of characters in C */ s[++i] = '\0'; return i; }
Example input:
Exercise 1-17. Write a program to print all input lines that are longer than 80 characters. Exercise 1-18. Write a program to remove trailing blanks and tabs from each line of input, and to delete entirely blank lines. iтри празни реда, posledwani ot повтарящи се празноти i табове i I Exercise 1-19. Write a function reverse (s) that reverses the character string s. Use it to write a program that reverses its input a line at a time.
Example output:
Exercise 1-17. Write a program to print all input lines that are longer than 80 characters. Exercise 1-18. Write a program to remove trailing blanks and tabs fromeach line of input, and to delete entirely blank lines. iтри празни реда, posledwani ot повтарящи се празноти i табове i I Exercise 1-19. Write a function reverse (s) that reverses the character string s. Use it to write a program that reverses its input a line at a time.
Solution by vladkir
#include <stdio.h> #define MAXLEN 1024 int main() { int c, len = 0, i; char line[MAXLEN]; while (len < MAXLEN - 1 && (c = getchar()) != EOF) { if (c != '\n') line[len++] = c; else { for (i = len - 1; i >= 0 && (line[i] == ' ' || line[i] == '\t'); --i) ; if (i >= 0) { line[i + 1] = '\0'; printf("%s|\n", line); } len = 0; } } }
Solution by Nurmareko
#include <stdio.h> #define MAXLINE 10000 int getline(char s[], int lim); main() { char line[MAXLINE]; int i, last_char, len; while((len = getline(line, MAXLINE)) > 0) { last_char = line[len-2]; /* is the current line contain only a newline and a null? * if true, then the line is entirely blank, skip it (in other word, delete.) * note: when the current line is entirely blank it only contain newline character therefore len is 1, however, * when there is atleast one character, the line will be compose of some character and a newline, therefore len is 2 or greater. */ if (len == 1) ; /* is the last character of the current line excluding newline and null, not a blank? * if true, then there is no trailing blank, nothing to remove, print the current line unchanged. */ else if (last_char != ' ' && last_char != '\t') printf("%s", line); /* there is trailing blank. */ else { /* get the index of the last non blank character. * note: when the line compose of blanks and tabs, i will be -1 */ for(i = len-2; line[i] == ' ' || line[i] == '\t'; --i) ; /* put null after the last non blank character to mark the end of line without trailling blank. */ line[++i] = '\0'; /* is the line index after removing trailling blank greater than 0? * if true, the line atleast have a character to print. * if not, the line is entirely blank, skip it. */ if (i > 0) printf("%s\n", line); } } return 0; } /* read a line into s, return length */ int getline(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; }
Solution by Foowar
/* exercise 1-18 Write a program to remove trailing blanks and tabs from each line of input, and to delete entirely blank lines. I consider lines that have only blanks and '\t' to be blank lines lines that have only '\n' are also blank lines I left a line with just blanks after this comment and just before the first include statement so you can test this program on it's source code */ #include <stdio.h> #define MAXLINE 50 int getline(char line[], int maxline); int main() { char line[MAXLINE]; int len; while ((len = getline(line, MAXLINE)) != 0) { --len; /* I don't want to put this in the loop because this condition will be true only one time if at all so why waste some cpu time */ if (line[len] == '\n') --len; while (len >= 0 && (line[len] == ' ' || line[len] == '\t')) { --len; } /* don't print entirely blank lines not sure if the 'continue' statement was used in the book at this point but I use it anyway */ if (len < 0) continue; ++len; /* lines that exceed MAXLINE don't have '\n' at the end so I'm adding '\n' only to those lines that don't exceed MAXLINE */ if (len+1 < MAXLINE-1) { line[len] = '\n'; ++len; } line[len] = '\0'; printf("%s", line); /* print '\n' for those lines that don't have '\n' at the end which are the lines that exceed MAXLINE */ if (line[len-1] != '\n') putchar('\n'); } return 0; } int getline(char line[], int maxline) { int i; int c; for (i = 0; i < maxline-1 && ((c = getchar()) != EOF) && c != '\n'; ++i) line[i] = c; if (c == '\n') { line[i] = c; ++i; } line[i] = '\0'; return i; }