I'm learning C from the K.N. King book and just arrived at a question to reverse the words in a sentence.
Here is the output:
Enter a sentence: you can cage a swallow can't you?
Reversal of sentence: you can't swallow a cage can you?
Though I've done this and it runs correctly, I want to make my code shorter. I'm not asking for any other solution.
#include <stdio.h>
#define N 100
int main(void) {
char ch[N], terminate;
int i, j, k, len;
printf("Enter a sentence: ");
for(i = 0; i < N; i++) {
ch[i] = getchar();
if(ch[i] == '?' || ch[i] == '.' || ch[i] == '!' || ch[i] == '\n') {
len = i; //Determine the length of the string
terminate = ch[i];
break;
}
}
int word_count = 0;
for(j = len - 1; j >= 0; j--) {
word_count++;
if(ch[j] == ' ') {
for(k = j+1; k < len; k++) {
printf("%c", ch[k]);
}
printf("%c", ch[j]); // print space character
len = len - word_count;
word_count = 0;
}
}
for(i = 0; i < len; i++) {
printf("%c", ch[i]);
if(ch[i] == ' ') {
break;
}
}
printf("%c", terminate);
return 0;
}
This can be done only with loops, arrays, getchar()
and putchar()
methoda using only <stdio.h>
header and no extra header (restrict <string.h>
header functions and methods). Also, no puts()
and gets()
functions. I guess this can be done only with two loops.
-
\$\begingroup\$ Just a note - your reversal is incorrect; it should be "you can't swallow a cage can you?" \$\endgroup\$Adam V– Adam V2013年02月20日 22:07:33 +00:00Commented Feb 20, 2013 at 22:07
3 Answers 3
I'm not quite sure if you are aiming only for smaller length or also for better code/readability. Depending on the answer, you can take the following style comments into account (which are somehow pretty personal except for the last one):
- In C code, I find it easier to have bigger indentations
- I prefer having a whitespace after keywords such as
for
andif
. - It's usually recommended to define variables in the smallest possible scope. In your case, if you compile with the
-std=c99
flag or equivalent, you could define your variables containing indexes at the beginning of yourfor
loops.
This being said, three things I found :
Because of the way you increment/update
word_count
andlen
, I had the feeling that we would always have the following property at the end of the loop :len == word_count + j
. (I guess) you can prove/verify this using loop invariants. This is true just before j gets decremented and this is also true just after word_count gets incremented. In order to be 100%, I used the following macro :#define assert(c) if (!(c)) { printf ("" #c "' is not true!\n"); return 0; }
and then I addedassert(len == word_count+j);
after incrementing word_count, after the k-look and at the end of the j-loop and the whole thing worked perfectly convincing me that my feeling was right.
TL;DR : The conclusion of this is that we don't need theword_count
variable if you replacelen - word_count
byj
.Because of the way j goes through the array, at the end of the j-loop,
len
is equal to the smallest index j leading to a space. Thus, we can easily deduce that for i in [0;len[, ch[i] is not a space. You can again verify this with the following assert :assert(ch[i]!=' ');
.At this stage, the code is slightly shorter but still we have the same loop appearing twice :
for(k = j+1; k < len; k++) printf(...)
to print all the words but the last one andfor(i = 0; i < len; i++) printf(...)
to print the last word. Thus, my last trick was to make the j-loop go one step further (so that it reaches -1 which is pretty good because then your k-loop and your i-loop becomes equivalent asj+1==0
) and print the last word. This comes at the cost of additional checks but it does work on your example.int main(void) { char ch[N], terminate; int len; printf("Enter a sentence: \n"); for (int i = 0; i < N; i++) { ch[i] = getchar(); if (ch[i] == '?' || ch[i] == '.' || ch[i] == '!' || ch[i] == '\n') { len = i; terminate = ch[i]; break; } } for (int j = len - 1; j >= -1; j--) { if (j<0 || ch[j] == ' ') { for (int k = j+1; k < len; k++) { printf("%c",ch[k]);; } if (j>=0) { printf("%c",ch[j]);; len = j; } } } printf("%c\n", terminate); return 0; }
I have a few comments.
main
takes argc/argv normally. Also start the function with the opening brace in colum 0Define one variable per line.
Your request for input might be better printed to stderr so that it can be separated from program output.
Your initial loop should test for EOF. Currently it doesn't You can test this in the *NIX shell with (
program
is your executable):/bin/echo -n "hello world" | ./program
terminate
seems redundantYou can and perhaps should define loop variables in the loop (as long as you don't need the value outside the loop):
for (int i = 0; i < n; ++i) {
The second for-loop is a little messy. For a start it has a nested loop - my advice is to avoid nested loops and extract the inner loop to a function. This is not always practical, but more often than not it is useful. In this case, you can write a little function (eg.
print_chars
) that prints a specified number of characters.Your terminating loop can be replaced by a call to the
print_chars
function.putchar
is preferable toprintf("%c", ...)
while
,for
,if
etc should be followed by a space.Testing for ' ' might be better done using
isspace
, which will test for other space-like characters too.
Here is my version, for what it is worth:
#include <stdio.h>
#include <ctype.h>
#define N 100
static inline void print_chars(const char *s, long n)
{
for (int i = 0; i < n; ++i) {
putchar(s[i]);
}
}
int main(int argc, char **argv)
{
char line[N];
int ch; /* note that ch is an int to allow it to hold 'EOF' */
int len = 0;
fprintf(stderr, "Enter a sentence: ");
while ((ch = getchar()) != EOF) {
if (ch == '?' || ch == '.' || ch == '!' || ch == '\n' || len >= N-1) {
break;
}
line[len++] = (char) ch;
}
line[len] = '0円';
char *end = line + len;
char *s = end;
while (--s >= line) {
if (isspace(*s)) {
print_chars(s + 1, end - s - 1);
putchar(*s);
end = s;
}
}
print_chars(line, end - line);
if (ch != EOF) {
putchar(ch);
}
return 0;
}
-
\$\begingroup\$ Thanks for the answer, but I'm at chapter-8 of K.N. king book and upto this function like fprintf, pointers are not covered. \$\endgroup\$Ashish Rawat– Ashish Rawat2013年02月20日 17:53:16 +00:00Commented Feb 20, 2013 at 17:53
The others have pointed out a lot so I will not repeat them.
But part of programming is spotting code that can be split out into other functions for readability. You have a few places were functions would make the code more readable:
#include <stdio.h>
#define N 100
int endOfSentence(int x) {return x == '?' || x == '.' || x == '!' || x == '\n';}
char* findWord(char* ch, int len)
{
if (len == 0)
{return NULL;}
while(len > 0 && ch[len-1] != ' ')
{--len;}
return ch+len;
}
void printWord(char* beg, char* end)
{
while(beg != end) {putchar(*beg);++beg;}
}
int main(void) {
char ch[N];
char terminate;
int i = 0;
int j, k;
printf("Enter a sentence: ");
while(i < N && !endOfSentence(ch[i] = getchar())) { ++i;}
int len = i; //Determine the length of the string
terminate = ch[i];
char* word;
while(len >= 0 && (word = findWord(ch, len)))
{
printWord(word, ch+len);
len = word - ch - 1;
if (len > 0)
{ putchar(' ');}
}
putchar(terminate);
putchar('\n');
return 0;
}
Fixed:
> gcc 22914.c
> ./a.out
Enter a sentence: you can cage a swallow can't you?
you can't swallow a cage can you?
-
1\$\begingroup\$ Sorry, but that is not up to your usual high standard. Try compiling it. When you fix the errors, try running it... \$\endgroup\$William Morris– William Morris2013年02月21日 01:52:14 +00:00Commented Feb 21, 2013 at 1:52
-
\$\begingroup\$ Yes. C is much harder than C++ \$\endgroup\$Loki Astari– Loki Astari2013年02月21日 19:00:59 +00:00Commented Feb 21, 2013 at 19:00