Reversing a string 
Author Message
 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  
 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  
 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  
 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  
 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.

- Show quoted text -

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  
 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.)

- Show quoted text -

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  
 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."

- Show quoted text -

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.

- Show quoted text -

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  
 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  
 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  
 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  
 
 [ 10 post ] 

 Relevant Pages 

1. REVERSE A STRING ?

2. Reversing a string

3. Reversing a string in place with pointers

4. Reversing a string word by word

5. Reversing a string w strtok() & sprintf()

6. reversing a string - RECURSION

7. reversing a string

8. Reversing the string

9. Reversing strings in place with pointers

10. reversing strings

11. Smarter compare (string reverse?)

12. Reversing string with pointers

 

 
Powered by phpBB® Forum Software