Leon Benko Leon Benko - 3 months ago 11
C Question

C program to delete all unnecessary blanks from a string

I tried to make a C function which deletes all unnecessary spaces from a string. For an example:

Hi my name is Leon .


After function call, it should look like following:
Hi my name is Leon.

But my program always lefts one blank space after the last word, so it looks like this:

Hi my name is Leon .


Any ideas for how can that be fixed?
Here's the code:

char *DeleteSpaces(char *str) {
int blank = 1;
char *poc, *start = str, *q;
q = str;
while (*q == ' ') q++;
poc = str;
while (*poc++ = *q++);
while (*str != '\0') {
q = str;
if (*str == ' ') {
if ((blank >= 1 && *(str-1) == ' ')) {
poc = str;
while (*poc == ' ') {
poc++;
}
q = str;
while(*poc != '\0') {
*q++ = *poc++;
}
*q = '\0';
blank = 0;

}
blank++;
}
else if (blank == 1)
blank = 0;
str++;
}
str--;
if (str == ' ') *str = '\0';

return start;
}

Answer

There are a number of subtle issues to consider in removing whitespace from a string in place. One significant issue is preserving a pointer to the beginning of the original so if the original is dynamically allocated, you do not lose the ability to free the memory later, thereby causing a memory leak.

Secondly, you have three basic reindexing considerations: (1) leading whitespace; (2) interleaved whitespace; and (3) whitespace after the end. (plus any custom cases you want to build in like trimming any whitespace before a '.'). You can pretty much take them in order in your routine.

While you are simply looking at a ' ' (space) character now, there is no reason you shouldn't also handle all whitespace the same. The ctype.h header provides the isspace function (macro) to do just that.

Putting those pieces together, you could do something like the following to rmxws (remove excess whitespace):

char *rmxws (char *s)
{
    if (!s) return NULL;             /* valdiate string not NULL */
    if (!*s) return s;                    /* handle empty string */

    char *p = s, *wp = s;            /* pointer and write-pointer */

    while (*p) {
        if (isspace(*p)) {                         /* test for ws */
            if (wp > s)               /* ignore leading ws, while */
                *wp++ = *p;         /* preserving 1 between words */
            while (*p && isspace (*p))         /* skip remainder  */
                p++;
            if (!*p)                     /* bail on end-of-string */
                break;
        }
        if (*p == '.')       /* handle space between word and '.' */
            while (wp > s && isspace (*(wp - 1)))
                wp--;
        *wp++ = *p;                            /* use non-ws char */
        p++;
    }
    while (wp > s && isspace (*(wp - 1)))     /* trim trailing ws */
        wp--;
    *wp = 0;    /* nul-terminate */

    return s;
}

Putting that together with a short example, you could test as follows:

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

char *rmxws (char *s);

int main (int argc, char **argv) {

    char *s = argc > 1 ? argv[1] : (char []){ " Testing  1 2  3. . .  "};
    printf ("\n original : '%s'\n", s);
    printf (" trimmed  : '%s'\n\n", rmxws (s));

    return 0;
}

char *rmxws (char *s)
{
    if (!s) return NULL;             /* valdiate string not NULL */
    if (!*s) return s;                    /* handle empty string */

    char *p = s, *wp = s;            /* pointer and write-pointer */

    while (*p) {
        if (isspace(*p)) {                         /* test for ws */
            if (wp > s)               /* ignore leading ws, while */
                *wp++ = *p;         /* preserving 1 between words */
            while (*p && isspace (*p))         /* skip remainder  */
                p++;
            if (!*p)                     /* bail on end-of-string */
                break;
        }
        if (*p == '.')       /* handle space between word and '.' */
            while (wp > s && isspace (*(wp - 1)))
                wp--;
        *wp++ = *p;                            /* use non-ws char */
        p++;
    }
    while (wp > s && isspace (*(wp - 1)))     /* trim trailing ws */
        wp--;
    *wp = 0;    /* nul-terminate */

    return s;
}

Example Use/Output

$ ./bin/trimxsws "    Hi       my      name is  Leon   .  "

 original : '    Hi       my      name is  Leon   .  '
 trimmed  : 'Hi my name is Leon.'

or just

$ ./bin/trimxsws

 original : ' Testing  1 2  3. . .  '
 trimmed  : 'Testing 1 2 3...'

Look things over and let me know if you have any additional questions.