qsort in struct, but the sorted struct is messy

80 Views Asked by At

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
3

There are 3 best solutions below

7
arfneto On BEST ANSWER

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

But:

  • will cost you much more time than needed to write and specially to test
  • is slower to change anything
  • is harder to use in other scenarios

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:

  • There is no such thing is C as reading a number and declaring an array with the size of this number. There is a thing called VLA treated as extension by some compilers, and even if now, after decades using C, it goes into the standard it is still problematic. Do the simple:
    • estimate a max value like you did on the node with the 10 ingredients with 64 bytes name
    • or allocate it as needed. C was written for this tasks.
  • it is almost clear that sum is the number of ingredients. It is not event a sum: it is a simple counter. Every recipe has up to 10 ingredients.
    • sum must be there, inside node.
  • ingredient is a vector of strings. Since the 70's we know that main prototype can be
          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

  • scanf is a scanner, as the name suggests. And it was written to consume tabular data, like large tables with thousands of lines of similar data. It was not written to get user input. For this we have fgetc and fgets for example. If we choose scanf to read free-form input like from the keyboard we can expect much more work.
  • as a scanner, to read nothing is ok to scanf so it returns an int with the total fields parsed from input. It is very naive not to test. If the user types a w instead of a 2 there goes your program to space.
  • use symbolic constants like #define and avoid magic numbers (like 10 or 64 in your code).
  • use pointers and malloc and free instead of static things.

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?

  • limit is here so make it easy to avoid overflow on the array.
  • size is the obvious total of ingredients in each recipe.
  • unsigned is here so we do not need to worry if someone enter -20 as the num
  • Recipes is a container for the node. And it is way better than a modest node[] because we can have metadata, things like limit and size, kept inside the struct
  • we can even write
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.

  • Use files. It is trivial in C
  • Use factory functions. It is trivial in C. It is a function that returns some unique Recipe at each call so you can test with 1 or 1,000 recipes at will.

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

  • write rcp_g_factory() and return a group of then.
  • write rcp_sort() to sort them
  • write rcp_g_show() since it is obvious that we want to display a collection before and after sort.
  • go for a beer.

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.

1
chux - Reinstate Monica On

Key Problem:

Unsorted sum[]

Code is only sorting recipe[], but not also sum[].

Move sum into a member of node.


Other Problems:

Missing headers

Prepend

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

scanf() woes

  • scanf("%s"... risk buffer overrun without a width.

  • Better code would check the return values.

1
Fe2O3 On

The code sorts the array of recipes, but each recipe becomes separated from its sum.

Here's the changes required:

typedef struct node{
    char name[64];
    int sum; // <== here!!
    char ingredient[10][64];
}node;

int main(){
    int num; scanf("%d", &num);
    node recipe[num];
    //input
    for(int i = 0; i < num; i++){
        printf( "Recipe #%d:\n", i + 1 ); // alert user to start of next recipe
        scanf("%s", recipe[i].name);
        scanf("%d", &recipe[i].sum); // <== here!!
        for(int j = 0; j < 10 && j < recipe[i]sum; j++){ // <==here!!
            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 < recipe[i].sum; j++){ // <== finally here
            printf("%s ", recipe[i].ingredient[j]);
        }
        printf("\n");
    }
}

Yes, I used a "magic number" with 10 appearing in the code. In the future, it would be better to use #define MAX_INGRED 10 (or some such value) and use that token instead.


"I also want to sort the ingredient within each node"
Good attempt, but this might work better

for( int i = 0; i < num; i++)
    qsort(
        recipe[i].ingredient,
        recipe[i].sum, // the correct # for each different recipe
        sizeof(recipe[0].ingredient[0]),
        mycompare
    );