arrayscstringstructqsort

qsort in struct, but the sorted struct is messy


I want to sort an array of struct using qsort by their name, But the output is not what I expected.

typedef struct node{
    char name[64];
    char ingredient[10][64];
}node;

int compare(const void *a, const void *b){
    node *nodeA = (node *)a;
    node *nodeB = (node *)b;
    return strcmp(nodeA->name, nodeB->name);
}

int main(){
    int num; scanf("%d", &num);
    int sum[num];
    node recipe[num];
    //input
    for(int i = 0; i < num; i++){
        scanf("%s", recipe[i].name);
        scanf("%d", &sum[i]);
        for(int j = 0; j < sum[i]; j++){
            scanf("%s", recipe[i].ingredient[j]);
        }
    }
    //sort
    qsort(recipe, num, sizeof(node), compare);

    //print out to check
    printf("\n");
    for(int i = 0; i < num; i++){
        printf("%s ", recipe[i].name);
        for(int j = 0; j < sum[i]; j++){
            printf("%s ", recipe[i].ingredient[j]);
        }
        printf("\n");
    }

input as follows

5
cake 4 egg flour sugar butter
omelet 4 egg bacon ham butter
bread 1 flour
breed 0
breag 0

output as follows

bread flour    
breag H??    
breed ?i?
          
cake 
omelet 

expected output as follows

bread flour 
breag 
breed 
cake egg flour sugar butter 
omelet egg bacon ham butter 

Moreover, what if I also want to sort the ingredient within each node as follows, how to achieve this?

//maybe I can do something like this
for(int i = 0; i < num; i++){
    qsort(recipe[i].ingredient, num, sizeof(recipe[i].ingredient[0]), mycompare//to compare each ingredient);
}

expected output as follows

bread flour 
breag 
breed 
cake butter egg flour sugar  
omelet bacon butter egg ham

Solution

  • It is possible and it is common to write code this way.

    But:

    I will show down here a common way to write this --- and to reason about this --- using your code as input. I will write here as I code and at the end there will be a complete program, with output and comments along the way.

    I will use no memory allocation and will not change sorting algorithm. It is just a toy just to show a way to write this kind of code. A way that usually works at the first run, as it worked here.

    This can be a long post. No TL;DR

    typedef struct node
    {
       char name[64];
       char ingredient[10][64];
    } node;
    

    This is the basic data? No. And there is no need to name the struct: let it anonymous. And the name is already showing something: a node? A node is part of something and your program has no representation for this something.
    As far as your model is from the real thing, more trouble you will have to operate on your data.

    Want proof? Look at the array sum and the value num in your code. If you need to write this to a file, a simple thing, the data is spread inside the code. Imagine an array of recipes and how in the world would you control that?

    Encapsulation is the way to go, as we see in all OOP languages. Encapsulation and OOP is much more a phylosophy than a technology, specially in C.

    The data as a thing

    This is the start of the posted code:

        int num;
        scanf("%d", &num);
        int  sum[num];
        node recipe[num];
    

    And we have more and more problems:

              int main( int argc, char** argv )
    

    And for mainthe command line arguments are... a vector of getc strings. Just like char ingredient [10][64]should be a vector with sum ingredients. It should raise some flags since all C program works very well with this arguments for main

    So a possible way to go

    typedef struct
    {
        char name[64];
        unsigned size;
        unsigned limit;
        char ingredient[10][64];
    } Recipe;
    
    typedef struct
    {
        unsigned size;
        unsigned limit;
        Recipe   rcp[40];
    } Recipes;
    

    Now we have encapsulation: we can operate on Recipes. And it is the data after all. We wrote no line of code yet, but we can see it is easier. Or not?

    typedef struct
    {
        unsigned size;
        unsigned limit;
        Recipes  coll[40]; // a collection of a collection
    } Book;
    

    For a book of up to 40 collections of up to 40 recipes each. And we wrote no code yet.

    And we can change the code of each one of this things without changing the others.

    Recipe could be

    typedef struct
    {
        char* name;
        unsigned size;
        unsigned limit;
        char** ingr;
    } Recipe;
    

    This way each recipe is constructed at run time instead of having a fixed size. And the same principle could be applied to Recipes and Book for that matter. It is the usual way in C.

    never write interactive code

    This is important: if you rely on typing recipe's fields each time you test your code things can be really hard. For 10 recipes of around 5 ingredients each, and with the program crashing in seconds at each run, life can be sad, i will cost you hours to do simple testing.

    Then when the code is ok you can insert logic to get user input. Only then. And only if it is part of the request: parsing user input is hard and boring.

    write the obvious stuff first

    display one recipe

    It is clear that we need to show a recipe on screen. So let us write this.

    From the original code, 5 recipe's prints:

    cake 4 egg flour sugar butter
    omelet 4 egg bacon ham butter
    bread 1 flour
    breed 0
    breag 0
    

    Not good. Better plan ahead:

        Recipe: cake, 4 ingredients:
            egg
            flour
            sugar 
            butter
    --
    

    It looks better. And it is better not to code a bunch of printf each time we need to print a recipe.

    int rcp_show(Recipe* rcp, const char* msg)
    {
        if (rcp == NULL) return -1;
        if (msg != NULL) printf("%s", msg);
        printf(
            "    Recipe: %s, %u ingredient (s):\n", rcp->name,
            rcp->size);
        for (unsigned i = 0; i < rcp->size; i += 1)
            printf("        %s\n", rcp->ingredient[i]);
        printf("--\n");
        return 0;
    };
    

    Less than a dozen lines, and it will print as planned. Not good? Change in a single point. Another OOP thing, a.k.a. SRP, Single Responsibility Principle ;)

    Why the second parameter? Well, we are testing and this means we can add a message to each call, embedded in the call itself. Handy. See the example below

    test ahead

    We need a Recipe. Any one. From the original code, the first one:

    cake 4 egg flour sugar butter
    

    In C terms:

        Recipe one = {
            .limit      = 10,
            .size       = 4,
            .name       = "cake",
            .ingredient = {"egg", "flour", "sugar", "butter"}};
    

    So this code must run ok:

    int main(void)
    {
        Recipe one = {
            .limit      = 10,
            .size       = 4,
            .name       = "cake",
            .ingredient = {"egg", "flour", "sugar", "butter"}};
        rcp_show(&one, NULL);
        rcp_show(&one, "This is the second);
        rcp_show(&one, "The 3rd one);
        return 0;
    }
    

    And it shows

        Recipe: cake, 4 ingredient (s):
            egg
            flour
            sugar
            butter
    --
    This is the second    Recipe: cake, 4 ingredient (s):
            egg
            flour
            sugar
            butter
    --
    The 3rd one    Recipe: cake, 4 ingredient (s):
            egg
            flour
            sugar
            butter
    --
    

    Good for moral support. And less typing.

    but we need more Recipe and no user input

    Time for a factory function: rcp_factory()

    Recipe rcp_factory(unsigned size)
    {
        static unsigned usn = 1;
        Recipe          one = {0};
        if (size > 10) size = 10;
        one.limit = 10;
        one.size  = 1 + rand() % size;
        sprintf(one.name, "Recipe #%u", usn);
        usn++;
        for (unsigned i = 0; i < one.size; i += 1)
        {
            unsigned ing = rand() % 100 + 1;  // up to 100
            sprintf(one.ingredient[i], "Ingredient %u", ing);
        }
        return one;
    };
    

    Not hard: at each call it returns a recipe with controlled random data. The argument limits the total ingredients of each Recipe, limited to the obvious 10.

    At each call a full struct is copied. In general we would prefer to return a pointer to a new Recipe but this is for demonstration only: only static data.

    Would it work? How to test? Just change the program above:

    int main(void)
    {
        srand(230922); // to repeat results... 
        Recipe one = {0};
        
        one = rcp_factory(10);
        rcp_show(&one,NULL);
    
        one = rcp_factory(10);
        rcp_show(&one,NULL);
    
        one = rcp_factory(10);
        rcp_show(&one,"(last)";
        return 0;
    }
    

    And it shows

        Recipe: Recipe #1, 9 ingredient (s):
            Ingredient 92
            Ingredient 85
            Ingredient 68
            Ingredient 25
            Ingredient 18
            Ingredient 65
            Ingredient 87
            Ingredient 23
            Ingredient 84
    --
        Recipe: Recipe #2, 9 ingredient (s):
            Ingredient 93
            Ingredient 7
            Ingredient 17
            Ingredient 15
            Ingredient 54
            Ingredient 64
            Ingredient 81
            Ingredient 95
            Ingredient 24
    --
    (last)    Recipe: Recipe #3, 5 ingredient (s):
            Ingredient 33
            Ingredient 16
            Ingredient 74
            Ingredient 58
            Ingredient 83
    --
    

    Good for moral support.

    but to sort we need a group of Recipe

    And we already designed one: it is the Recipes struct. How to build one? Keeping the same principle

    creating the group

    It is a matter of copy and paste from rcp_factory(). Once again we will use only static data. Not smart. All data is being copied around at each call.

    Recipes rcp_g_factory(unsigned size)
    {
        static unsigned usn = 1;
        Recipes          one = {0};
        if (size > 40) size = 40;
        one.limit = 40;
        one.size  = 1 + rand() % size;
        usn++;
        for (unsigned i = 0; i < one.size; i += 1)
            one.rcp[i]   = rcp_factory(10);
        return one;
    };
    

    Sure, we want to display it.

    Adding a way to display the collection

    Copying and pasting from rcp_show() is a good first move since we do not want to waste time

    int rcp_g_show(Recipes* rcp, const char* msg)
    {
        if (rcp == NULL) return -1;
        if (msg != NULL) printf("%s", msg);
        printf("    %u recipe (s) in this group\n\n", rcp->size);
        for (unsigned i = 0; i < rcp->size; i += 1)
            rcp_show(&rcp->rcp[i],NULL);
        printf("End of recipes\n");
        return 0;
    };
    

    Since we already know how to print one, this is just a matter of calling rcp_show(). Thank you SRP

    Will this work? How to test such a thing?

    int main(void)
    {
        srand(230922);  // to repeat results...
        Recipes grp = rcp_g_factory(15);
        rcp_g_show(&grp, "before sort");
        return 0;
    }
    

    Not bad huh? Works?

    before sort    4 recipe (s) in this group
    
        Recipe: Recipe #1, 2 ingredient (s):
            Ingredient 85
            Ingredient 68
    --
        Recipe: Recipe #2, 5 ingredient (s):
            Ingredient 18
            Ingredient 65
            Ingredient 87
            Ingredient 23
            Ingredient 84
    --
        Recipe: Recipe #3, 9 ingredient (s):
            Ingredient 93
            Ingredient 7
            Ingredient 17
            Ingredient 15
            Ingredient 54
            Ingredient 64
            Ingredient 81
            Ingredient 95
            Ingredient 24
    --
        Recipe: Recipe #4, 5 ingredient (s):
            Ingredient 33
            Ingredient 16
            Ingredient 74
            Ingredient 58
            Ingredient 83
    --
    End of recipes
    

    Good. But no surprise. It is just a loop over things already tested.

    time to sort Recipes

    Sure, we want a function like rcp_g_ sort() to encapsulate things. We know that we need to sort by some criteria, this criteria is a user-supplied function, so this is the time to pass it down:

            int rcp_g_sort( Recipes* group, int (*)(const void*)(const void*))
    

    Not much to think: it is the same signature as qsort.

    a problem: recipes are already sorted

    Since we are using a factory function that returns names as "Recipe #n" they come already sorted. We can just invert the sort order by changing the compare function, but I will change the factory function anyway. SRP again.

    Just one change: the sprintf call from

        sprintf(one.name, "Recipe #%u",usn);
    

    to

        sprintf(
            one.name, "Recipe %c%c%c#%u",
            (1 + rand() % 25 + 'A'), (1 + rand() % 25 + 'A'),
            (1 + rand() % 25 + 'A'), usn);
    

    And now the recipes have a 3-letter random prefix to the number.

    Will it work?

    How to test such a complex thing?

    int main(void)
    {
        srand(230922);  // to repeat results...
        Recipes grp = rcp_g_factory(15);
        rcp_g_show(&grp, "before sort");
        rcp_g_sort(&grp, rcp_compare);
        rcp_g_show(&grp, "\n\nafter sort");
        return 0;
    }
    

    And it shows

    before sort    4 recipe (s) in this group
    
        Recipe: Recipe XKZ#1, 2 ingredient (s):
            Ingredient 18
            Ingredient 65
    --
        Recipe: Recipe JOF#2, 7 ingredient (s):
            Ingredient 93
            Ingredient 7
            Ingredient 17
            Ingredient 15
            Ingredient 54
            Ingredient 64
            Ingredient 81
    --
        Recipe: Recipe JLC#3, 5 ingredient (s):
            Ingredient 16
            Ingredient 74
            Ingredient 58
            Ingredient 83
            Ingredient 99
    --
        Recipe: Recipe HWE#4, 5 ingredient (s):
            Ingredient 53
            Ingredient 48
            Ingredient 46
            Ingredient 68
            Ingredient 74
    --
    End of recipes
    
    
    after sort    4 recipe (s) in this group
    
        Recipe: Recipe HWE#4, 5 ingredient (s):
            Ingredient 53
            Ingredient 48
            Ingredient 46
            Ingredient 68
            Ingredient 74
    --
        Recipe: Recipe JLC#3, 5 ingredient (s):
            Ingredient 16
            Ingredient 74
            Ingredient 58
            Ingredient 83
            Ingredient 99
    --
        Recipe: Recipe JOF#2, 7 ingredient (s):
            Ingredient 93
            Ingredient 7
            Ingredient 17
            Ingredient 15
            Ingredient 54
            Ingredient 64
            Ingredient 81
    --
        Recipe: Recipe XKZ#1, 2 ingredient (s):
            Ingredient 18
            Ingredient 65
    --
    End of recipes
    

    And it works. At first run. Good for moral support

    what if we want to sort the ingredients by name?

    It is the same stuff. The compare function is the same, the data is already a string --- the beauty of student code: little or no testing at all. So it is just a matter of calling qsort. Sure it is naive to call qsort to sort such a small thing, but it is just a toy.

    int rcp_sort(
        Recipe* rcb, int (*f)(const void*, const void*))
    {
        if (f == NULL) return -1;
        if (rcb == NULL) return -2;
        if (rcb->size == 0) return 0;
        qsort(
            rcb->ingredient, rcb->size,
            sizeof(rcb->ingredient[0]), f);
        return 0;
    };
    

    It is just a matter of calling qsort()

    how to test rcp_sort()

    The same thing: let us use

    int main(void)
    {
        srand(230922);  // to repeat results...
        Recipe rcp = rcp_factory(10);
        rcp_show(&rcp, "[before sorting by ingredient]");
        rcp_sort(&rcp, rcp_compare);
        rcp_show(&rcp, "[after sorting by ingredient]");
        return 0;
    }
    

    That shows

    [before sorting by ingredient]    Recipe: Recipe SKR01, 9 ingredient (s):
            Ingredient MPS25
            Ingredient SOJ23
            Ingredient EPR07
            Ingredient YUG64
            Ingredient YQI45
            Ingredient UYI58
            Ingredient DZP40
            Ingredient YSV48
            Ingredient ZWU93
    --
    [after sorting by ingredient]    Recipe: Recipe SKR01, 9 ingredient (s):
            Ingredient DZP40
            Ingredient EPR07
            Ingredient MPS25
            Ingredient SOJ23
            Ingredient UYI58
            Ingredient YQI45
            Ingredient YSV48
            Ingredient YUG64
            Ingredient ZWU93
    --
    

    It is time for me to stop.

    Here is the final code. A few changes: I changed all factory functions to use the 3-letters prefix in the names, to better show the sorting process.

    The complete code at this point

    source

    #define _CRT_SECURE_NO_WARNINGS
    
    #define MAX_COLL_SIZE 20
    #define MAX_INGR_COUNT 10
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef struct
    {
        char     name[64];
        unsigned size;
        unsigned limit;
        char     ingredient[MAX_INGR_COUNT][64];
    } Recipe;
    
    typedef struct
    {
        unsigned size;
        unsigned limit;
        Recipe   rcp[MAX_COLL_SIZE];
    } Recipes;
    
    typedef struct
    {
        unsigned size;
        unsigned limit;
        Recipes  coll[MAX_COLL_SIZE];
    } Book;
    
    int    rcp_compare(const void* a, const void* b);
    Recipe rcp_factory(unsigned);
    int    rcp_show(Recipe*, const char*);
    int    rcp_sort(Recipe*, int (*)(const void*, const void*));
    
    Recipes rcp_g_factory(unsigned);
    int     rcp_g_show(Recipes*, const char*);
    int rcp_g_sort(Recipes*, int (*)(const void*, const void*));
    
    int main(void)
    {
        srand(230922);  // to repeat results...
        Recipes grp = rcp_g_factory(15);
        rcp_g_show(&grp, "before sort");
        rcp_g_sort(&grp, rcp_compare);
        rcp_g_show(&grp, "\n\nafter sort");
    
        Recipe rcp = rcp_factory(10);
        rcp_show(&rcp, "\n\n\n[before sorting by ingredient]");
        rcp_sort(&rcp, rcp_compare);
        rcp_show(&rcp, "[after sorting by ingredient]");
        return 0;
    }
    
    int rcp_compare(const void* a, const void* b)
    {
        Recipe* A = (Recipe*)a;
        Recipe* B = (Recipe*)b;
        return strcmp(A->name, B->name);
    };
    
    Recipe rcp_factory(unsigned size)
    {
        static unsigned usn = 1;
        Recipe          one = {0};
        if (size > MAX_INGR_COUNT) size = MAX_INGR_COUNT;
        one.limit = size;
        one.size  = 1 + rand() % size;
        sprintf(
            one.name, "Recipe %c%c%c%02u",
            (1 + rand() % 25 + 'A'), (1 + rand() % 25 + 'A'),
            (1 + rand() % 25 + 'A'), usn);
        usn++;
        for (unsigned i = 0; i < one.size; i += 1)
        {
            unsigned ing = rand() % 100 + 1;  // up to 100
            sprintf(
                one.ingredient[i], "Ingredient %c%c%c%02u",
                (1 + rand() % 25 + 'A'),
                (1 + rand() % 25 + 'A'),
                (1 + rand() % 25 + 'A'), ing);
        }
        return one;
    };
    
    Recipes rcp_g_factory(unsigned size)
    {
        static unsigned usn = 1;
        Recipes         one = {0};
        if (size > MAX_COLL_SIZE) size = MAX_COLL_SIZE;
        one.limit = size;
        one.size  = 1 + rand() % size;
        usn++;
        for (unsigned i = 0; i < one.size; i += 1)
            one.rcp[i] = rcp_factory(10);
        return one;
    };
    
    int rcp_show(Recipe* rcp, const char* msg)
    {
        if (rcp == NULL) return -1;
        if (msg != NULL) printf("%s", msg);
        printf(
            "    Recipe: %s, %u ingredient (s):\n", rcp->name,
            rcp->size);
        for (unsigned i = 0; i < rcp->size; i += 1)
            printf("        %s\n", rcp->ingredient[i]);
        printf("--\n");
        return 0;
    }
    
    int rcp_sort(
        Recipe* rcb, int (*f)(const void*, const void*))
    {
        if (f == NULL) return -1;
        if (rcb == NULL) return -2;
        if (rcb->size == 0) return 0;
        qsort(
            rcb->ingredient, rcb->size,
            sizeof(rcb->ingredient[0]), f);
        return 0;
    };
    
    int rcp_g_show(Recipes* rcp, const char* msg)
    {
        if (rcp == NULL) return -1;
        if (msg != NULL) printf("%s", msg);
        printf(
            "    %u recipe (s) in this group\n\n", rcp->size);
        for (unsigned i = 0; i < rcp->size; i += 1)
            rcp_show(&rcp->rcp[i], NULL);
        printf("End of recipes\n");
        return 0;
    }
    int rcp_g_sort(
        Recipes* coll, int (*f)(const void*, const void*))
    {
        if (coll == NULL) return -1;
        if (f == NULL) return -2;
        qsort(coll->rcp, coll->size, sizeof(Recipe), f);
    
        return 0;
    };
    
    // https://stackoverflow.com/questions/77161477/
    // qsort-in-struct-but-the-sorted-struct-is-messy
    

    output

    before sort    4 recipe (s) in this group
    
        Recipe: Recipe ZSK01, 2 ingredient (s):
            Ingredient XMP18
            Ingredient HSO84
    --
        Recipe: Recipe OEP02, 7 ingredient (s):
            Ingredient UYU81
            Ingredient IYQ33
            Ingredient PUY83
            Ingredient XDZ90
            Ingredient SYS46
            Ingredient SZW70
            Ingredient NOJ53
    --
        Recipe: Recipe URV03, 7 ingredient (s):
            Ingredient ZSY98
            Ingredient FPD57
            Ingredient YWS83
            Ingredient BQJ19
            Ingredient SUP23
            Ingredient ECD99
            Ingredient ZXS03
    --
        Recipe: Recipe JGZ04, 7 ingredient (s):
            Ingredient NLM42
            Ingredient MKQ13
            Ingredient UYF73
            Ingredient LWE47
            Ingredient DID71
            Ingredient FFF20
            Ingredient IYJ51
    --
    End of recipes
    
    
    after sort    4 recipe (s) in this group
    
        Recipe: Recipe JGZ04, 7 ingredient (s):
            Ingredient NLM42
            Ingredient MKQ13
            Ingredient UYF73
            Ingredient LWE47
            Ingredient DID71
            Ingredient FFF20
            Ingredient IYJ51
    --
        Recipe: Recipe OEP02, 7 ingredient (s):
            Ingredient UYU81
            Ingredient IYQ33
            Ingredient PUY83
            Ingredient XDZ90
            Ingredient SYS46
            Ingredient SZW70
            Ingredient NOJ53
    --
        Recipe: Recipe URV03, 7 ingredient (s):
            Ingredient ZSY98
            Ingredient FPD57
            Ingredient YWS83
            Ingredient BQJ19
            Ingredient SUP23
            Ingredient ECD99
            Ingredient ZXS03
    --
        Recipe: Recipe ZSK01, 2 ingredient (s):
            Ingredient XMP18
            Ingredient HSO84
    --
    End of recipes
    
    
    
    [before sorting by ingredient]    Recipe: Recipe KDF05, 5 ingredient (s):
            Ingredient GYM89
            Ingredient YNR73
            Ingredient OVJ84
            Ingredient ZNK30
            Ingredient NNT31
    --
    [after sorting by ingredient]    Recipe: Recipe KDF05, 5 ingredient (s):
            Ingredient GYM89
            Ingredient NNT31
            Ingredient OVJ84
            Ingredient YNR73
            Ingredient ZNK30
    --
    

    I did almost no testing.