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
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
.
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:
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:
node
with the 10 ingredients with 64 bytes nameC
was written for this tasks.sum
is the number of ingredients. It is not event a sum: it is a simple counter. Every recipe has up to 10 ingredients.
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 main
the 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.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.#define
and avoid magic numbers (like 10
or 64
in your code).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
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
.
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.
C
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.
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
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.
Recipe
and no user inputTime 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.
Recipe
And we already designed one: it is the Recipes
struct. How to build one?
Keeping the same principle
rcp_g_factory()
and return a group of then.rcp_sort()
to sort themrcp_g_show()
since it is obvious that we want to display a collection before and after sort.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.
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.
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
.
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
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()
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.
#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
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.