Author |
Message |
Amittai Avira #1 / 10
|
 Reversing a string
Thanks again to David and Russ for comments on previous version of this exercise. For some reason, my posted replies to them are not yet appearing on my newsreader. Anyway, here's another version. I hope this one addresses the problems in the previous ones. I also tried to make it more robust: it accepts a command-line string if there is one, but otherwise uses a default string. Also, I wrote it so that one can slip in more than one space between tokens (words) in the string and still get a new string with the tokens reversed and only once space between them. Total memory usage is five char pointers, plus the allocated memory and whatever memory is used for the original string. Amittai Aviram /* reverser.c */ /* Accepts string from command line or uses default string and reverses order of tokens (words). */ #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { char *string = NULL; char *rev = NULL; char *start = NULL; char *end = NULL; char *revstart = NULL; /* Assign command-line input to string if available. */ if (argc > 1) string = argv[1]; /* Otherwise, use default string. */ else { string = "The cow jumped over the moon."; puts("Using default string."); puts("(You can also enter a string in the command line.)\n"); } /* Print string in original form. */ printf("Original: %s\n", string); /* Allocate space for reversed string. */ if (!(rev = malloc(strlen(string) + 1))) { perror("Memory allocation failure for *rev."); exit(EXIT_FAILURE); } /* Initialize rev string to mere spaces and terminate with null. */ rev[strlen(string)] = '\0'; memset(rev, ' ', strlen(string)); /* Put both start and end pointers at end of original string and revstart pointer at beginning of rev string before finding tokens and copying them in reverse order. */ start = end = &string[strlen(string)]; revstart = rev; /* Loop through original string backwards from end to find tokens and copy each token in forward order in rev, moving revstart forward each time to end of new token. */ while(start > string) { for(start = end - 1; *start != ' ' && start >= string; start--); start++; strncpy(revstart, start, end - start); revstart = revstart + (end - start) + 1; for(end = start - 1; *end == ' ' && end > string; end--); end++; } /* Reassign string pointer to point to reversed string. */ string = rev; printf("Reversed: %s\n", string); /* Free memory allocated to reversed string. */ free(string); return EXIT_SUCCESS; Quote: }
|
Mon, 28 Jun 2004 00:14:39 GMT |
|
 |
CBFalcone #2 / 10
|
 Reversing a string
Quote:
> Thanks again to David and Russ for comments on previous version of > this exercise. For some reason, my posted replies to them are not > yet appearing on my newsreader. Anyway, here's another version. > I hope this one addresses the problems in the previous ones. I > also tried to make it more robust: it accepts a command-line > string if there is one, but otherwise uses a default string. > Also, I wrote it so that one can slip in more than one space > between tokens (words) in the string and still get a new string > with the tokens reversed and only once space between them. Total > memory usage is five char pointers, plus the allocated memory and > whatever memory is used for the original string. > Amittai Aviram > /* reverser.c */ > /* Accepts string from command line or uses default > string and reverses order of tokens (words). */ > #include <stdio.h> > #include <stdlib.h> > #include <string.h> > int main(int argc, char *argv[]) > { > char *string = NULL; > char *rev = NULL; > char *start = NULL; > char *end = NULL; > char *revstart = NULL; > /* Assign command-line input to string if available. */ > if (argc > 1) > string = argv[1]; > /* Otherwise, use default string. */ > else > { > string = "The cow jumped over the moon."; > puts("Using default string."); > puts("(You can also enter a string in the command line.)\n"); > } > /* Print string in original form. */ > printf("Original: %s\n", string); > /* Allocate space for reversed string. */ > if (!(rev = malloc(strlen(string) + 1))) > { > perror("Memory allocation failure for *rev."); > exit(EXIT_FAILURE); > } > /* Initialize rev string to mere spaces > and terminate with null. */ > rev[strlen(string)] = '\0'; > memset(rev, ' ', strlen(string)); > /* Put both start and end pointers at end > of original string and revstart pointer > at beginning of rev string before > finding tokens and copying them in > reverse order. */ > start = end = &string[strlen(string)]; > revstart = rev; > /* Loop through original string backwards > from end to find tokens and copy each > token in forward order in rev, moving > revstart forward each time to end of > new token. */ > while(start > string) > { > for(start = end - 1; *start != ' ' && start >= string; start--); > start++; > strncpy(revstart, start, end - start); > revstart = revstart + (end - start) + 1; > for(end = start - 1; *end == ' ' && end > string; end--); > end++; > } > /* Reassign string pointer to point to reversed string. */ > string = rev; > printf("Reversed: %s\n", string); > /* Free memory allocated to reversed string. */ > free(string); > return EXIT_SUCCESS; > }
I think you are over complicating it. /* ====================================== */ /* reverse string in place, return length */ size_t revstring(char *string) { char *last, temp; size_t lgh; lgh = strlen(string); last = string + lgh; /* points to '\0' */ while (last-- > string) { temp = *string; *string++ = *last; *last = temp; } return lgh; Quote: } /* revstring */
--
Available for consulting/temporary embedded and systems. (Remove "XXXX" from reply address. yahoo works unmodified)
|
Mon, 28 Jun 2004 11:39:33 GMT |
|
 |
David Rubi #3 / 10
|
 Reversing a string
[snip - code] Quote: > I think you are over complicating it. > /* ====================================== */ > /* reverse string in place, return length */ > size_t revstring(char *string) > { [...] > } /* revstring */
Amittai's program attempts to reverse the order of the words, not the characters. See my solution in the earlier thread started on Tuesday. david -- If 91 were prime, it would be a counterexample to your conjecture. -- Bruce Wheeler
|
Mon, 28 Jun 2004 08:43:07 GMT |
|
 |
Amittai Avira #4 / 10
|
 Reversing a string
Here is a version that uses only four pointers (having eliminated one, revstart, from the previous version), plus the allocated memory and whatever memory is taken up by the original string (whether in argv[1] or hard-coded as a string literal constant). I also reduced away two lines of codes (the compensatory increments of the pointers start++ and end++), though the resulting code may be deemed a bit harder to read. Amittai /* reverser.c */ /* Accepts string from command line or uses default string and reverses order of tokens (words). */ #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { char *string = NULL; char *rev = NULL; char *start = NULL; char *end = NULL; /* Assign command-line input to string if available. */ if (argc > 1) string = argv[1]; /* Otherwise, use default string. */ else { string = "The cow jumped over the moon."; puts("Using default string."); puts("(You can also enter a string in the command line.)\n"); } /* Print string in original form. */ printf("Original: %s\n", string); /* Allocate space for reversed string. */ if (!(rev = malloc(strlen(string) + 1))) { perror("Memory allocation failure for *rev."); exit(EXIT_FAILURE); } /* Initialize reversed string to mere spaces and close with null termination. */ rev[strlen(string)] = '\0'; memset(rev, ' ', strlen(string)); /* Put end pointer at end of original string before finding tokens and copying them in reverse order. */ end = &string[strlen(string)]; /* Loop through original string backwards from end to find tokens and copy each token in forward order in rev. */ while(end > string) { for(start = end - 1; start - 1 >= string && *(start - 1) != ' '; start--); if (*rev == ' ') strncpy(rev, start, end - start); else strncpy((strstr(rev, " ")) + 1, start, end - start); for(end = start - 1; end > string && *(end - 1) == ' '; end--); } /* Reassign string pointer to point to reversed string. */ string = rev; printf("Reversed: %s\n", string); /* Free memory allocated to reversed string. */ free(string); return EXIT_SUCCESS; Quote: }
|
Wed, 30 Jun 2004 03:26:38 GMT |
|
 |
David Rubi #5 / 10
|
 Reversing a string
Quote:
> Here is a version that uses only four pointers [...] > /* reverser.c */ > /* Accepts string from command line or uses default > string and reverses order of tokens (words). */ > #include <stdio.h> > #include <stdlib.h> > #include <string.h> > int main(int argc, char *argv[]) > { > char *string = NULL; > char *rev = NULL; > char *start = NULL; > char *end = NULL; > /* Assign command-line input to string if available. */
[snip] This is a little clumsy because you have to include the whole string on the command-line, and it's subject to shell quoting rules. For this program to actually be useful, you should probably just read from stdin. Quote: > /* Otherwise, use default string. */
[snip] And this is entirely superfluous. Quote: > /* Print string in original form. */ > printf("Original: %s\n", string); > /* Allocate space for reversed string. */ > if (!(rev = malloc(strlen(string) + 1)))
Although a style issue, it's not considered good practice to use a logical operator with a non-boolean expression. Prefer if((rev=malloc(...)) == NULL) or something similar. Quote: > { > perror("Memory allocation failure for *rev."); > exit(EXIT_FAILURE); > } > /* Initialize reversed string to mere spaces > and close with null termination. */ > rev[strlen(string)] = '\0'; > memset(rev, ' ', strlen(string));
This memset is strange here, but you need it because of your strange algorithm. When manipulating strings, it's much easier to use functions which write and make use of the null character. Quote: > /* Put end pointer at end > of original string before > finding tokens and copying them in > reverse order. */ > end = &string[strlen(string)]; > /* Loop through original string backwards > from end to find tokens and copy each > token in forward order in rev. */ > while(end > string) > { > for(start = end - 1; start - 1 >= string && *(start - 1) != ' '; > start--); > if (*rev == ' ') > strncpy(rev, start, end - start); > else > strncpy((strstr(rev, " ")) + 1, start, end - start); > for(end = start - 1; end > string && *(end - 1) == ' '; end--); > }
This algorithm does not work (at least as I understand the problem): the case ' hello world' becomes 'world hello ' and not 'world hello'. Quote: > /* Reassign string pointer to point to reversed string. */ > string = rev; > printf("Reversed: %s\n", string);
Why not just print rev here? That is more logical. Quote: > /* Free memory allocated to reversed string. */ > free(string);
Likewise, you should free(rev) here since you malloc'd rev earlier. Quote: > return EXIT_SUCCESS; > }
My entry: /* * reverse word order in each input line */ #include <stdio.h> #include <string.h> char * reverse(char *buf) { char *s, *e, c; for(s=buf, e=buf+strlen(buf)-1; e > s; e--, s++){ c = *s; *s = *e; *e = c; } return buf; Quote: }
int main(int argc, char *argv[]) { char s[4096]; /* string */ char r[4096]; /* reversed */ char *t; /* token */ while(fgets(s, sizeof s, stdin) != 0){ if(s[0] == '\n'){ puts(""); continue; } s[strlen(s)-1] = 0; r[0] = 0; for(t=strtok(s, " "); t != 0; t=strtok(0, " ")){ strcat(r, " "); strcat(r, reverse(t)); } puts(reverse(r+1)); } return 0; Quote: }
david -- If 91 were prime, it would be a counterexample to your conjecture. -- Bruce Wheeler
|
Wed, 30 Jun 2004 06:09:42 GMT |
|
 |
Amittai Avira #6 / 10
|
 Reversing a string
Quote: > My entry: > /* > * reverse word order in each input line > */ > #include <stdio.h> > #include <string.h> > char * > reverse(char *buf) > { > char *s, *e, c; > for(s=buf, e=buf+strlen(buf)-1; e > s; e--, s++){ > c = *s; > *s = *e; > *e = c; > } > return buf; > } > int > main(int argc, char *argv[]) > { > char s[4096]; /* string */ > char r[4096]; /* reversed */
No, see, that's exactly _not_ the challenge. The challenge was, originally, to write a program that reversed a string using as little memory as possible, and then gave, as an example of an input string, char *string = "The cow jumped over the moon." It's _easy_ to reverse a string if you put it into a big buffer array, and there are lots of ways of so doing. Sorry that that had not been clear earlier (although I believe I did mention it.) Quote: > char *t; /* token */ > while(fgets(s, sizeof s, stdin) != 0){ > if(s[0] == '\n'){ > puts(""); > continue; > } > s[strlen(s)-1] = 0; > r[0] = 0; > for(t=strtok(s, " "); t != 0; t=strtok(0, " ")){ > strcat(r, " "); > strcat(r, reverse(t)); > } > puts(reverse(r+1)); > } > return 0; > } > david
Amittai
|
Wed, 30 Jun 2004 08:23:39 GMT |
|
 |
Amittai Avira #7 / 10
|
 Reversing a string
[Sorry -- wanted to comment on a couple of other points. -- AA]
Quote:
> > Here is a version that uses only four pointers [...] > > /* reverser.c */ > > /* Accepts string from command line or uses default > > string and reverses order of tokens (words). */ > > #include <stdio.h> > > #include <stdlib.h> > > #include <string.h> > > int main(int argc, char *argv[]) > > { > > char *string = NULL; > > char *rev = NULL; > > char *start = NULL; > > char *end = NULL; > > /* Assign command-line input to string if available. */ > [snip] > This is a little clumsy because you have to include the whole string on > the command-line, and it's subject to shell quoting rules. For this > program to actually be useful, you should probably just read from stdin.
In order to avoid the command line technique, I would have to gets() or fgets() or otherwise retrieve the input -- into a predefined array, which gets us back to the requirement of defining a big enough array, which goes against the spirit of the original challenge, which was to use as little memory as possible. (Please see my previous response on this thread.) Quote: > > /* Otherwise, use default string. */ > [snip] > And this is entirely superfluous.
So? It does not add to memory usage, it is just fun to see the original and reversed strings printed out one on top of the other. In fact, it would be even better with one extra space inserted before the word "Reversed" in the final output, so that the beginnings of the strings would be visibly parallel. Quote: > > /* Print string in original form. */ > > printf("Original: %s\n", string); > > /* Allocate space for reversed string. */ > > if (!(rev = malloc(strlen(string) + 1))) > Although a style issue, it's not considered good practice to use a > logical operator with a non-boolean expression. Prefer
O.k., I've seen it done many times in books the way I wrote it in the program, and I believe I've posted on this very question in the past and gotten a variety of responses. Perhaps you're right, though, that it's clearer style. I recall one poster saying that he preferred to divide the statement into two -- the call to malloc() and the test -- since, after all, they are two separate actions. But, to begin with, I did this if(!(rev = malloc(...))) thing because I recall some authors saying or implying that it may be good style to collapse several actions into a single statement. (K & R even suggest this at one point.) I believe this may be one of those things Richard Heathfield would call "holy wars issues." Quote: > if((rev=malloc(...)) == NULL) > or something similar. > > { > > perror("Memory allocation failure for *rev."); > > exit(EXIT_FAILURE); > > } > > /* Initialize reversed string to mere spaces > > and close with null termination. */ > > rev[strlen(string)] = '\0'; > > memset(rev, ' ', strlen(string)); > This memset is strange here, but you need it because of your strange > algorithm. When manipulating strings, it's much easier to use functions > which write and make use of the null character.
Why easier? How so? I do use the null character, too. I have thought of ways to start with a rev array containing the null at the start and then pushing it forward sequentially, but the solution I came up with allows me to write the new string with only one space between tokens more easily than the other solutions I've found. Quote: > > /* Put end pointer at end > > of original string before > > finding tokens and copying them in > > reverse order. */ > > end = &string[strlen(string)]; > > /* Loop through original string backwards > > from end to find tokens and copy each > > token in forward order in rev. */ > > while(end > string) > > { > > for(start = end - 1; start - 1 >= string && *(start - 1) != ' '; > > start--); > > if (*rev == ' ') > > strncpy(rev, start, end - start); > > else > > strncpy((strstr(rev, " ")) + 1, start, end - start); > > for(end = start - 1; end > string && *(end - 1) == ' '; end--); > > } > This algorithm does not work (at least as I understand the problem): > the case ' hello world' becomes 'world hello ' and not 'world hello'.
Good point! This does not strike me as a big problem -- the string is still reversed -- but I will look into it. Quote: > > /* Reassign string pointer to point to reversed string. */ > > string = rev; > > printf("Reversed: %s\n", string); > Why not just print rev here? That is more logical.
Merely because I wanted the code to illustrate that the same pointer that had previously pointed to the string now points to its reverse. It has no effect on the output, nor on memory usage. Quote: > > /* Free memory allocated to reversed string. */ > > free(string); > Likewise, you should free(rev) here since you malloc'd rev earlier.
Yes, that seems like a good idea. I wasn't sure which one to choose. Quote: > david
Amittai
|
Wed, 30 Jun 2004 08:44:27 GMT |
|
 |
Amittai Avira #8 / 10
|
 Reversing a string
[Solution below. --AA]
Quote:
> > Here is a version that uses only four pointers [...]
[See previous thread for algorithm.] Quote: > This algorithm does not work (at least as I understand the problem): > the case ' hello world' becomes 'world hello ' and not 'world hello'.
Simply add the code below before the pointer called string gets reassigned to rev. Note that I am reusing the pointer called end, since I don't need it for the earlier task anymore. Thus I can avoid using any more memory. BTW: add these two lines to top of program to make it still a little more robust: #define SEP ' ' #define SEP2 " " /* And adjust strrchr() call accordingly. */ /* ... */ /* Get rid of trailing spaces in rev. */ end = &rev[strlen(string) - 1]; while (end > rev && *end == SEP) { if (*(end - 1) == SEP) end--; else *end = '\0'; } /* ... */ Amittai
|
Wed, 30 Jun 2004 09:43:08 GMT |
|
 |
Lawrence Kir #9 / 10
|
 Reversing a string
On Friday, in article
... Quote: >> int >> main(int argc, char *argv[]) >> { >> char s[4096]; /* string */ >> char r[4096]; /* reversed */ >No, see, that's exactly _not_ the challenge. The challenge was, originally, >to write a program that reversed a string using as little memory as >possible,
In which case a solution should reverse the string in place i.e. require no array other than the original, which therefore must be modifiable. Quote: >and then gave, as an example of an input string, char *string = >"The cow jumped over the moon."
That would have to be char string[] = "The cow jumped over the moon." to be modifiable. Quote: > It's _easy_ to reverse a string if you put >it into a big buffer array, and there are lots of ways of so doing. Sorry >that that had not been clear earlier (although I believe I did mention it.)
This reverses the words and also preserves the white-space between the words and at the start and end. You can eliminate those easily enough if you wish. #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h> static void reversewords(char *str); static void reversemem(char *startp, char *endp); int main(int argc, char *argv[]) { char *string = NULL; /* Assign command-line input to string if available. */ if (argc > 1) string = argv[1]; /* Otherwise, use default string. */ else { static char defaultstr[] = "The cow jumped over the moon."; string = defaultstr; puts("Using default string."); puts("(You can also enter a string in the command line.)\n"); } /* Print string in original form. */ printf("Original: \"%s\"\n", string); reversewords(string); /* Print reversed string */ printf("Reversed: \"%s\"\n", string); return EXIT_SUCCESS; Quote: }
static void reversewords(char *str) { char *p = str; char *q; int inword = !isspace((unsigned char)*p); while (*p != '\0') { for (q = p+1; *q != '\0' && inword == !isspace((unsigned char)*q); q++) ; reversemem(p, q-1); p = q; inword = !inword; } reversemem(str, p-1); Quote: }
static void reversemem(char *startp, char *endp) { while (startp < endp) { const char ch = *startp; *startp = *endp; *endp = ch; startp++, endp--; } Quote: }
-- -----------------------------------------
-----------------------------------------
|
Thu, 01 Jul 2004 01:49:07 GMT |
|
 |
David Rubi #10 / 10
|
 Reversing a string
Quote:
> > > #include <stdio.h> > > > #include <stdlib.h> > > > #include <string.h> > > > int main(int argc, char *argv[]) > > > { > > > char *string = NULL; > > > char *rev = NULL; > > > char *start = NULL; > > > char *end = NULL; > > > /* Assign command-line input to string if available. */ > > [snip] > > This is a little clumsy because you have to include the whole string on > > the command-line, and it's subject to shell quoting rules. For this > > program to actually be useful, you should probably just read from stdin. > In order to avoid the command line technique, I would have to gets() or > fgets() or otherwise retrieve the input -- into a predefined array, which > gets us back to the requirement of defining a big enough array, which goes > against the spirit of the original challenge, which was to use as little > memory as possible. (Please see my previous response on this thread.)
I think that's sort of irrelevant though. Your goal is to reverse the string using as little *extra* memory as possible, but how much memory should you allocate to hold the actual string? If you use a buffer of even modest size, say 256 bytes, then your program can actually be useful; i.e., you can fgets a line from stdin and reverse it. So, you can do things like reverse all words on all lines of a file, etc. (What this is actually good for, I don't know, but it's more useful--or at least more robust--than just reversing argv[1]. Actually, you could just consider the "string" to be composed of many command-line "arguments" in which case you just print argv[argc-1]..argv[1].) david -- If 91 were prime, it would be a counterexample to your conjecture. -- Bruce Wheeler
|
Thu, 01 Jul 2004 10:26:20 GMT |
|
|
|