cstringsetcombinationscombinatorics

Combination generator written in C : works for big n but for small n when n=0 or n=k crashes saying "segmentation fault"


Let's say I have a box and there is a bunch of balls in that box. I can label each ball with a character while making sure no two balls get the same character as labels. I get a string this way. Let's say I had 5 balls in the box and I label them alphabetically, then the string I will get is abcde. Now let's say I want to pick 3 balls randomly from the box, which can be done in 10 different ways and I will get 10 different strings of length 3, arranged alphabetically these will be -

abc abd abe acd ace ade bcd bce bde cde

I want to write a computer programme that will be doing the same thing. It will -

  1. Ask for a string from the user.
  2. Calculate the length of the string n.
  3. Ask for a number k less than or equal to the length of the string.
  4. Calculate binomial coefficient nCK.
  5. Declare a nCK X k+1 matrix comb.
  6. Declare an array of nCK pointers ptr.
  7. Set the value of all of the elements of ptr to be equal to the corresponding addresses of the rows of comb.
  8. Compute all possible combinations by calling the function gen_comb () and store the values in comb.
  9. Print the combinations by calling the function prnt_comb and repeat.

I wrote the following -

    #include <stdio.h>
    
    void length (char * string, int * n)
    {
        *n = 0;
        for (int i = 0; string[i] != '\0'; i++)
        {
            *n = *n + 1;
        }
    }
    
    void bin_cof (int * n, int * k, int * nCk)
    {
        int _k; *nCk = 1;
        if (*k > *n - *k)
        {
            _k = *n - *k;
            
        } else {
            _k = *k;
        }
        for (int i = 0; i < _k; i++)
        {
            *nCk = *nCk * (*n - i) / (1 + i);
        }
    }
    
    void first_n_last (char * string, char ** current, char ** last, int * n, int * k)
    {
        for (int i = 0; i < *k; i++)
        {
            current[i] = string + i;
        }
        current[*k] = string + *n;
        for (int i = 0; i < *k; i++)
        {
            last[i] = string + i + *n - *k;
        }
        last[*k] = string + *n;
    }
    
    void prnt_comb (char ** ptr, int * nCk, int * k)
    {
        printf ("\nHere is the set of %d possible combination of length %d :\n\n{%s", *nCk, *k, ptr[0]);
        for (int i = 1; i < *nCk; i++)
        {
            printf (", %s", ptr[i]);
        }
        printf ("}\n\n\n");
    }
    
    void successor (char * string, char ** current, char ** last, int k)
    {
        int con;
        for (int i = k-1; i >= 0; i--)
        {
            if (current[i] != last[i])
            {
                con = i; break;
            }
        }
        char * i = string;
        for (;i != current[con]; i++) {}
        int poi = (int) (i-string);
        for (; con < k; con++)
        {
            current[con] = string+1+poi; poi++;
        }
    }
    
    void gen_comb (char * string, int *n, int * k, int * nCk, char ** ptr)
    {
        char * current[*k+1], * last[*k+1];
        first_n_last (string, current, last, n, k);
        for (int j = 0; j < *nCk; j++)
        {
            for (int i = 0; i < *k+1; i++) {ptr[j][i] = *(current[i]);}
            successor (string, current, last, *k);
        }
    }
    
    int main ()
    {
        for (;;)
        {
            printf ("Give me a string : ");
            char string [100];
            if (scanf("%s", string) == 0)
            {
                printf ("Error!!");
                break;
            }
        
            int n;
            length (string, &n);
            printf ("Give me a number less than or equal to %d : ", n);
            int k;
            if (scanf("%d", &k) == 0)
            {
                printf ("Error!!");
                break;
            }
        
            int nCk;
            bin_cof (&n, &k, &nCk);
            char comb[nCk][k+1], * ptr[nCk];
            for (int i = 0; i < nCk; i++)
            {
                ptr[i] = comb[i];
            }
            
            gen_comb (string, &n, &k, &nCk, ptr);
            prnt_comb (ptr, &nCk, &k);
        }
        return 0;
    }

It works for big ns, but for small ns (less than 7 or 8 let's say) when k becomes 0 or becomes equal to n it crashes saying "Segmentation fault, core dumped." And it does so completely randomly, sometimes when n=4 && k=4 it will crash, other times when n=2 && k=0 it will crash. Randomly. But, for big numbers, n>8 let's say, it works fine. Where is this error originating from and how can it be eliminated? I am running the programme here.


Solution

  • Your crash comes from out-of-bounds pointer/array access when k == 0 or k == n.

    Functions like first_n_last() and successor() assume k > 0 and try to index current[k-1] or loop on invalid ranges, so they dereference garbage pointers.

    So for fix:

    Code:

    int main () {
        for (;;) {
            printf("Give me a string : ");
            char string[100];
            if (scanf("%s", string) == 0) { printf("Error!!"); break; }
            int n; length(string, &n);
            printf("Give me a number less than or equal to %d : ", n);
            int k;
            if (scanf("%d", &k) == 0) { printf("Error!!"); break; }
            // Edge case
            if (k < 0 || k > n) { printf("Invalid k!\n"); continue; }
            if (k == 0) {
                printf("\nHere is the set of 1 possible combination of length 0 :\n\n{ }\n\n\n");
                continue;
            }
            if (k == n) {
                printf("\nHere is the set of 1 possible combination of length %d :\n\n{%s}\n\n\n", k, string);
                continue;
            }
            int nCk; bin_cof(&n, &k, &nCk);
            char comb[nCk][k+1], *ptr[nCk];
            for (int i = 0; i < nCk; i++) { ptr[i] = comb[i]; }
            gen_comb(string, &n, &k, &nCk, ptr);
            prnt_comb(ptr, &nCk, &k);
        }
        return 0;
    }