How Do I Sort Structs by Different Keys?
Selection sort on struct arrays — sorting by name, ID, or GPA
After this lesson, you will be able to:
- Adapt selection sort to operate on an array of structs
- Use
strcmpfor sorting by string fields - Modify the comparison to sort in descending order
- Identify code duplication between sort-by-name and sort-by-GPA
Same Algorithm, Structured Data
In Lesson 2.14, you sorted arrays of int. Now you’re sorting arrays of structs — and the key insight is that the algorithm stays the same. The only thing that changes is what you compare and what you swap.
Compare one field. Swap the entire struct.
Sorting by Different Fields
Sort by Name (String Field)
void sort_by_name(Student roster[], int size)
{
for (int i = 0; i < size - 1; i++)
{
int min = i;
for (int j = i + 1; j < size; j++)
{
if (strcmp(roster[j].name, roster[min].name) < 0)
{
min = j;
}
}
if (min != i)
{
Student temp = roster[i];
roster[i] = roster[min];
roster[min] = temp;
}
}
}
Key Insight: When sorting by a string field, you must use
strcmp— not<or>.strcmpcompares the actual characters;<would compare pointer addresses (meaningless).strcmp(a, b) < 0means a comes before b alphabetically.
name field (char name[50]). What happens if you use roster[j].name < roster[min].name instead of strcmp?< on array names, C decays them to pointers. You're comparing the memory addresses where the strings are stored, not the characters themselves. The addresses have nothing to do with alphabetical order. You must use strcmp for string comparison.
Sort by GPA (Numeric Field)
void sort_by_gpa(Student roster[], int size)
{
for (int i = 0; i < size - 1; i++)
{
int min = i;
for (int j = i + 1; j < size; j++)
{
if (roster[j].gpa < roster[min].gpa)
{
min = j;
}
}
if (min != i)
{
Student temp = roster[i];
roster[i] = roster[min];
roster[min] = temp;
}
}
}
The Pattern
Notice these two functions are almost identical. The only difference is the comparison line. That duplication is going to bother you — and in Lesson 4.6, you’ll eliminate it with function pointers.
Swapping Entire Structs
Student temp = roster[i];
roster[i] = roster[min];
roster[min] = temp;
This works because struct assignment copies all fields. With fixed-size char name[50], the name bytes are copied too. No pointer issues.
From Java: In Java,
Collections.sort(list, Comparator.comparing(Student::getName))lets you sort by any field with one line. C requires you to write a separate sort function (or comparison function) for each field. In Lesson 4.6, function pointers will give you something closer to Java’s approach.
Student structs with Student temp = roster[i]; roster[i] = roster[min]; roster[min] = temp;, how many bytes are copied in total (assuming sizeof(Student) is 64)?roster[i] to temp (64 bytes), roster[min] to roster[i] (64 bytes), temp to roster[min] (64 bytes). That's 192 bytes per swap. With large structs, this can be expensive — which is why later you'll see techniques like sorting arrays of pointers instead.
Why does this matter?
The duplication between sort_by_name and sort_by_gpa is the setup for function pointers in Lesson 4.6. Recognizing duplicate code and understanding what varies (just the comparison) is how you design flexible, reusable functions. This same insight drives Java’s Comparator interface.
Quick Check: Why use strcmp instead of < for sorting by name?
< compares pointer addresses (where the strings are stored in memory), which is meaningless. strcmp compares the actual character content. strcmp(a, b) < 0 means string a comes before string b alphabetically.
Quick Check: When you swap two structs, what happens to the string fields?
With char name[50] (fixed array inside struct), the swap copies all 50 bytes as part of the struct copy. With char *name (pointer), only the pointer is copied — both structs would point to the same string after a swap, which is actually fine for sorting purposes.
Quick Check: The sort-by-name and sort-by-GPA functions are nearly identical. What's the only difference?
The comparison line. sort_by_name uses strcmp(roster[j].name, roster[min].name) < 0. sort_by_gpa uses roster[j].gpa < roster[min].gpa. Everything else — the loops, the swap, the structure — is identical. Function pointers (Lesson 4.6) will let you write one sort function that accepts any comparison.
Deep dive: Sorting an array of pointers vs. sorting structs directly
When structs are large (hundreds of bytes), swapping them is expensive — each swap copies the entire struct three times. An alternative: sort an array of pointers to structs instead:
void sort_ptrs_by_name(Student *ptrs[], int size)
{
for (int i = 0; i < size - 1; i++)
{
int min = i;
for (int j = i + 1; j < size; j++)
{
if (strcmp(ptrs[j]->name, ptrs[min]->name) < 0)
{
min = j;
}
}
if (min != i)
{
Student *temp = ptrs[i]; // Swap pointers (8 bytes each)
ptrs[i] = ptrs[min];
ptrs[min] = temp;
}
}
}
| Approach | Swap cost | When to use |
|---|---|---|
| Sort structs directly | 3 × sizeof(Student) per swap |
Small structs, simple code |
| Sort array of pointers | 3 × 8 bytes per swap | Large structs, performance matters |
The pointer approach swaps 24 bytes per swap regardless of struct size. The tradeoff: you need to set up the pointer array first and use -> instead of . for access. For Lab 7’s struct sizes, sorting directly is fine. In production code with thousands of large records, pointer sorting is standard.
This is also how qsort works internally — it uses void * and memcpy to swap arbitrary-sized elements, but the callback pattern lets you avoid knowing the swap details.
Deep dive: Descending order and multi-key sorting
Descending order: Flip the comparison. For ascending GPA: roster[j].gpa < roster[min].gpa. For descending: roster[j].gpa > roster[min].gpa. With strcmp, ascending name: strcmp(a, b) < 0. Descending: strcmp(a, b) > 0.
Multi-key sorting (e.g., sort by GPA, break ties by name):
void sort_by_gpa_then_name(Student roster[], int size)
{
for (int i = 0; i < size - 1; i++)
{
int min = i;
for (int j = i + 1; j < size; j++)
{
if (roster[j].gpa < roster[min].gpa ||
(roster[j].gpa == roster[min].gpa &&
strcmp(roster[j].name, roster[min].name) < 0))
{
min = j;
}
}
if (min != i)
{
Student temp = roster[i];
roster[i] = roster[min];
roster[min] = temp;
}
}
}
The comparison checks GPA first. If GPAs are equal, it falls through to comparing names. This is equivalent to Java’s Comparator.comparing(Student::getGpa).thenComparing(Student::getName).
< to > in the comparison. Option B is wrong — you can't use strcmp on double values; it's for strings only. Option D works but is unnecessarily wasteful — just change the comparison direction.
Big Picture: Sorting structured data is one of the most common operations in programming. The pattern here — compare one field, swap the whole record — is the same whether you’re sorting database rows, search results, or student rosters. The code duplication you see is not a flaw; it’s a stepping stone to the abstraction (function pointers) that eliminates it.
Toward Generic Sorting
You now have working sort functions for struct arrays. But writing a separate function for every field is tedious and error-prone. The fix — function pointers — is coming in Lesson 4.6. First, let’s organize your growing codebase with multi-file projects.