0

This function is supposed to expand expressions like "a-z" in s1 to "abcd....xyz" in s2, but for some reasons it does not work properly, every time i print s2 it stops at the second char that is supposed to be expanded.
For example, if s1="a-z", printing s2 gives me "ab".
Why?

void expand(char s1[], char s2[]) {
    int i, j, k;
    for (i = 0, j = 0; s1[i] != '\0'; i++, j++) {
        if (s1[i] == '-' && s1[i-1] != ' ' && s1[i+1] != ' ') {
            for (k = s1[i-1]+1; k < s1[i+1]; ++j, ++k)
                s2[j] = k;
        } else {
            s2[j] = s1[i];
        }
    }
 } 

The function is called this way:

int caller (void) {
  char des[30];
  expand("a-z", des);
  printf("%s\n", des);
}
Iyad
  • 11
  • 1
  • 2
    First loop `s1[i-1]` invokes [UB](https://stackoverflow.com/a/4105123/3436922) – LPs Dec 18 '19 at 10:51
  • This is horribly non-idiomatic. Using indices for this sort of thing instead of incrementing `s1` and `s2` make the code difficult to read. – William Pursell Dec 18 '19 at 10:52
  • 1
    When `i` is zero, the loop potentially accesses `s1[-1]`. That causes undefined behaviour if `s1`, as passed, is the first element of an array. In any event, for anyone to help you further, you'll need to show a representative example of how you CALL the function. Read up on how to provide a [mcve]. – Peter Dec 18 '19 at 10:53
  • 1
    Why are you looking at `s2[i+1]` before you've assigned it? – William Pursell Dec 18 '19 at 10:53
  • 1
    from a-z if you want z to be printed, you will have to have `for (k = s1[i-1]+1; k < s1[i+1]; ++j, ++k)` to `for (k = s1[i-1]+1; k <= s1[i+1]; ++j, ++k)` ... notice the equal to sign in the termination condition... Also, please do a memset on the 2nd array if you are not going to put a '\0' explicitly... BTW, the program worked for me, but there are better ways to write code... – Siddharth Dec 18 '19 at 10:57
  • And why is one assignment to `s2[j]` but the other is to `s2[i]`? As I commented above, using indices like this is making the logic overly convoluted. Increment the pointers. – William Pursell Dec 18 '19 at 10:57
  • Hint: if the format is fixted (lower case letter and -) after a check of the format you can simply increment first letter up to the last: char are numbers at the end...`void expand (char *text_to_expand, char *expanded_text) { int i=0; if (text_to_expand[1] == '-') { uint8_t nData = tolower(text_to_expand[2])-tolower(text_to_expand[0])+1; for (i=0; i – LPs Dec 18 '19 at 11:03

3 Answers3

0

Why so many complications? Your code has some problems, nevertheless, it produces a meaningful output for me than what you are getting.

$ ./main.out
abcdefghijklmnopqrstuvwxyz

Just that, you need to modify the termination condition in the inner loop to include the last character.

Let me help you with a readable solution. I have tried to derive it out of your code's gist only, as there's nothing horribly wrong there. There are still much better ways to write code than below, but for quickness' sake... You can put your own validations.

void expand (char s1[], char s2[])
{
    int cnt = 0;
    for (int i = 0; i < s1[i] != '\0'; ++i)
    {
        switch (s1[i])
        {
            case '-':
                for (char k = s1[i-1]+1; k < s1[i+1]; ++k)
                    s2[cnt++] = k;
                continue;
            break;
            default:
                s2[cnt++] = s1[i];
        }
    }
}
Siddharth
  • 321
  • 1
  • 5
0

In function, you are using the next snippet of code

for (i = 0, j = 0; s1[i] != '\0'; i++, j++) {
        if (s1[i] == '-' && s1[i-1] != ' ' && s1[i+1] != ' ') {

If you are going to do something with s1[i-1], then i cannot go from 0, or you'll be checking s1[-1] in first loop iteration, which is out of array bounds. This is an error, that produces Undefined Behaviour. An alternative would be to begin at i = 1, or to check if strcmp(s1 + i, " - ") == 0, which never checks before i or never goes after a \0.

for (i = 0, j = 0; s1[i] != '\0'; i++, j++) {
        if (strcmp(s1 + i, " - ") == 0) {

(but it is possible that this is not what you are looking for, while checking that the character at i is - and the character at i-1 is a space, and the character at i+1 is another space is somehow equivalent as checking if the character sequence at i ---well, not at i-1--- is the sequence -)

The problem in your code is that you need a buffer to copy the strings... as when you say something like: a-z, then that spans a string longer than the original a-z sequence. First of all, you must recognize the subsequence of two chars (which can or cannot be the - char) and a - in the middle. This is something you can do with this state machine:

/* expand.c -- expands ranges in the form a-b
 * Date: Fri Dec 20 08:02:30 EET 2019
 */

#include <stdio.h>

#define ERR_ENOSPACE      (-1)
#define ERR_ERANGE        (-2)

ssize_t expand(
        char *source,           /* the source string */
        char *target,           /* the target string */
        size_t target_length)   /* the target length */
{
    int     ch,         /* the character to copy */
            first_char, /* first char in range */
            last_char;  /* last char in range */
    size_t  len = 0;    /* the length of the complete range string */
    ssize_t result = 0; /* the length returned */

    while((ch = *source++) != 0) {  /* s is the input string */
        switch (len) { /* length of substring (or machine state). */
        case 0:
                first_char = ch; /* annotate first char */
                len = 1; /* state is now 1 */
                break;

        case 1: switch (ch) {
            case '-': /* valid range go to state 2 */
                len = 2;
                break;
            default: /* not a valid range, store a new first char
                        and remain in this state.  And copy the last
                        char to the output string. */
                if (target_length < 3) {
                    /* not enough space (3 is needed for first_char,
                     * this char and the final \0 char) */
                    return ERR_ENOSPACE;
                }
                *target++ = first_char; target_length--;
                first_char = ch; /* len = 1; */
                result++;
            } break;

        case 2:
            last_char = ch; /* we completed a range */
            if (first_char > last_char)
                return ERR_ERANGE;
            ssize_t n = last_char - first_char + 1; /* number of output chars */
            if (n + 1 > target_length) {
                /* we need space for n characters, * plus a '\0' char */
                return ERR_ENOSPACE;
            }
            /* copy the string */ 
            while (first_char <= last_char)
                *target++ = first_char++;
            target_length -= n; 
            result += n;
            len = 0; /* state comes back to 0 */
            break;
        } /* switch (l) */ 
    } /* while */

    /* check state on end. */
    switch (len) { /* depending on length we need to add a partial
                      built sequence */
    case 0: break; /* nothing to append */
    case 1: /* we have a spare first_char, add it */
            if (target_length < 2)
                return ERR_ENOSPACE;
            *target++ = first_char; target_length--;
            result++;
            break;
    case 2: if (target_length < 3)
                return ERR_ENOSPACE;
            *target++ = first_char; *target++ = '-';
            target_length -= 2; result += 2;
            break;
    }
    /* now fill the final \0 */
    if (target_length < 1) {
        return ERR_ENOSPACE;
    }
    *target = '\0';
    return result;
} /* expand */

int main()
{
    char line[1024];
    char outbuf[8192];
    while (fgets(line, sizeof line, stdin)) {
        ssize_t n = expand(line, outbuf, sizeof outbuf);
#define CASE(err) case err: fprintf(stderr, "ERROR: " #err "\n"); break;
        switch(n) {
        CASE(ERR_ENOSPACE)
        CASE(ERR_ERANGE)
        default: printf("OUTPUT: %s\n", outbuf); break;
        } /* switch */
    } /* while */
} /* main */

The sample code is a full sample (with a main() routine) you can compile and test.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31
0

To add a bit of flexibility in handling your s1 string so it will accept "a-z", "az" or "a #%^#%& z", assuming the string starts with the first character in the range, you can save the first character and then iterate over s1 looking for the next alpha character as the end character for the expansion. You can use the isalpha() macro provided in ctype.h.

You also need to handle the cases where no ending character is found, or the ending character is less than the beginning from an ASCII value standpoint.

Note this only works for ASCII character sets. Others character sets do not guarantee sequential alpha-characters "A-Z" or "a-z".

You can do something similar to the following:

#include <ctype.h>
...
void expand (const char *s1, char *s2)
{
    int c = *s1;            /* assign 1st char in s1 to c */

    if (!isalpha(*s1++)) {  /* validate 1st char is alpha character */
        fputs ("error: invalid format s1\n", stderr);
        return;
    }

    *s2++ = c;              /* assign c to 1st char in s2 */
    *s2 = 0;                /* nul-terminate in case end char in s1 not found */

    while (*s1 && !isalpha(*s1))    /* loop s1 looking for next alpha char */
        s1++;

    if (!*s1) { /* if 2nd alpha char not found, handle error */
        fputs ("error: invalid format s1\n", stderr);
        return;
    }

    for (; c <= *s1; c++)   /* loop until c == end char in s1 */
        *s2++ = c;          /* assign c to s2, increment pointer */

    *s2 = 0;                /* nul-terminate s2 */
}

(note: the initial nul-termination of s2 after the first character covers both cases where (1) no ending alpha-character is found or (2) the end character has an ASCII value less than the first. Consider changing the order of the s1 and s2 parameter to make them consistent with strcpy, etc..)

If you like you can iterate to find the first alpha character in s1 to handle leading non-alpha characters -- that is left to you.

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

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85