Skip to content

Commit 87da355

Browse files
Beatris Mendez GandicaCopilot
andcommitted
Add answer key for Debugging workshop
Covers all tutorial bugs and three C algorithm exercises: Tutorial section (consolidated from inline expand blocks): - Binary Search: recursive call searched upper half instead of lower half - Quicksort: missing p1++ after swap in partition loop - Vector: forgot to update list->__arr after realloc Exercise 1 - Binary Tree: correct treeAdd, treeRemove (3 cases), findMin implementations with sentinel node pattern Exercise 2 - Binary Heap: correct heapAdd (sift up) and heapRemoveMax (sift down) for 1-indexed max heap array Exercise 3 - Burrows-Wheeler Transform: suffix array construction + safe modulo pattern for C remainder operator pitfall Also includes: - Common bugs to watch for per exercise - GDB and Valgrind quick reference table - Note about C remainder vs modulo operator PT-BR translation exists. C code is language-agnostic. 10-model QA passed (3 clean passes). Hugo build verified. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 65b3f46 commit 87da355

1 file changed

Lines changed: 321 additions & 0 deletions

File tree

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
---
2+
title: "Debugging - Answer Key"
3+
date: 2026-04-27T00:00:00-07:00
4+
draft: false
5+
weight: 15
6+
hidden: true
7+
---
8+
9+
{{% notice note %}}
10+
This answer key covers all bugs from the tutorial sections and provides correct implementations for the three exercises. The tutorial bugs are shown with expand blocks in the activity pages, but this consolidates everything into one teacher reference.
11+
12+
**Note:** The exercise implementations below are based on the algorithms described in the workshop. The actual Replit starter code may use slightly different variable names or function signatures. The logic and approach are the same.
13+
{{% /notice %}}
14+
15+
## Tutorial Section Bugs
16+
17+
### Binary Search Bug (Part 2)
18+
19+
**The bug:** The recursive call for searching the lower half of the array incorrectly searches the upper half.
20+
21+
**The fix:** Change the recursive call arguments so the lower half search uses `lo` and `middle - 1`:
22+
23+
```c
24+
// BUGGY: searches upper half instead of lower half
25+
return rbin_search(arr, middle + 1, hi, element);
26+
27+
// FIXED: searches lower half correctly
28+
return rbin_search(arr, lo, middle - 1, element);
29+
```
30+
31+
### Quicksort Bug (Part 3)
32+
33+
**The bug:** The `p1` pointer (tracking the "greater" element) is not incremented after swapping a smaller element into place.
34+
35+
**The fix:** Add `p1++;` after the swap statement inside the for loop:
36+
37+
```c
38+
if (arr[p2] < arr[pivot]) {
39+
swap(&arr[p1], &arr[p2]);
40+
p1++; // THIS LINE WAS MISSING
41+
}
42+
```
43+
44+
### Vector Memory Bug (Part 4)
45+
46+
**The bug:** When reallocating the array in `pushBack`, the code forgets to update `list->__arr` to point to the new memory. This causes:
47+
- Invalid reads/writes (accessing freed memory)
48+
- Double frees (freeing the same pointer twice)
49+
- Memory leaks (newly allocated memory is never referenced)
50+
51+
**The fix:** After reallocating, update the pointer:
52+
53+
```c
54+
int *newArr = realloc(list->__arr, newCapacity * sizeof(int));
55+
list->__arr = newArr; // THIS LINE WAS MISSING
56+
```
57+
58+
Also add `deleteVectorInt` call at the end of main to prevent memory leaks.
59+
60+
## Exercise 1: Binary Tree
61+
62+
A correct binary tree implementation in C. The tree uses a sentinel node where the actual root is the sentinel's left child.
63+
64+
### Key operations
65+
66+
**Tree Add** - traverse to find correct position, then insert:
67+
68+
```c
69+
void treeAdd(BinaryTree *tree, int value) {
70+
BTNode *newNode = (BTNode *)malloc(sizeof(BTNode));
71+
newNode->data = value;
72+
newNode->left = NULL;
73+
newNode->right = NULL;
74+
newNode->parent = NULL;
75+
76+
// Empty tree: insert as root (left child of sentinel)
77+
if (tree->__root->left == NULL) {
78+
tree->__root->left = newNode;
79+
newNode->parent = tree->__root;
80+
return;
81+
}
82+
83+
// Traverse to find insertion point
84+
BTNode *current = tree->__root->left;
85+
while (current != NULL) {
86+
if (value < current->data) {
87+
if (current->left == NULL) {
88+
current->left = newNode;
89+
newNode->parent = current;
90+
return;
91+
}
92+
current = current->left;
93+
} else {
94+
if (current->right == NULL) {
95+
current->right = newNode;
96+
newNode->parent = current;
97+
return;
98+
}
99+
current = current->right;
100+
}
101+
}
102+
}
103+
```
104+
105+
**Find Min** - go as far left as possible:
106+
107+
```c
108+
BTNode *findMin(BTNode *node) {
109+
if (node == NULL) return NULL;
110+
while (node->left != NULL) {
111+
node = node->left;
112+
}
113+
return node;
114+
}
115+
```
116+
117+
**Tree Remove** - handle 3 cases (0, 1, or 2 children):
118+
119+
```c
120+
void treeRemove(BinaryTree *tree, int value) {
121+
BTNode *node = treeFind(tree, value);
122+
if (node == NULL) return;
123+
124+
// Case 1: No children (leaf)
125+
if (node->left == NULL && node->right == NULL) {
126+
if (node->parent->left == node)
127+
node->parent->left = NULL;
128+
else
129+
node->parent->right = NULL;
130+
free(node);
131+
}
132+
// Case 2: One child
133+
else if (node->left == NULL || node->right == NULL) {
134+
BTNode *child = (node->left != NULL) ? node->left : node->right;
135+
child->parent = node->parent;
136+
if (node->parent->left == node)
137+
node->parent->left = child;
138+
else
139+
node->parent->right = child;
140+
free(node);
141+
}
142+
// Case 3: Two children
143+
else {
144+
BTNode *minRight = findMin(node->right);
145+
node->data = minRight->data;
146+
// Remove the minRight node (it has at most 1 right child)
147+
if (minRight->right != NULL) {
148+
minRight->right->parent = minRight->parent;
149+
}
150+
if (minRight->parent->left == minRight)
151+
minRight->parent->left = minRight->right;
152+
else
153+
minRight->parent->right = minRight->right;
154+
free(minRight);
155+
}
156+
}
157+
```
158+
159+
{{% notice tip %}}
160+
**Common bugs to watch for:**
161+
- Forgetting to update parent pointers when moving nodes
162+
- Not handling the case where the node to remove IS the root
163+
- Memory leaks from not freeing removed nodes
164+
- The sentinel node's name `__root` is confusing — the actual root is `tree->__root->left`
165+
{{% /notice %}}
166+
167+
## Exercise 2: Binary Heap (Max Heap)
168+
169+
A correct max binary heap using a 1-indexed array.
170+
171+
### Key index formulas (1-indexed)
172+
173+
| Relationship | Formula |
174+
|-------------|---------|
175+
| Left child of node `i` | `2 * i` |
176+
| Right child of node `i` | `2 * i + 1` |
177+
| Parent of node `i` | `i / 2` |
178+
179+
### Heap Add (sift up)
180+
181+
```c
182+
void heapAdd(BinaryHeap *heap, int value) {
183+
heap->size++;
184+
heap->__arr[heap->size] = value;
185+
186+
// Sift up: swap with parent while larger than parent
187+
int i = heap->size;
188+
while (i > 1 && heap->__arr[i] > heap->__arr[i / 2]) {
189+
swap(&heap->__arr[i], &heap->__arr[i / 2]);
190+
i = i / 2;
191+
}
192+
}
193+
```
194+
195+
### Heap Remove Max (sift down)
196+
197+
```c
198+
int heapRemoveMax(BinaryHeap *heap) {
199+
int max = heap->__arr[1];
200+
201+
// Move last element to root
202+
heap->__arr[1] = heap->__arr[heap->size];
203+
heap->size--;
204+
205+
// Sift down: swap with larger child while smaller than children
206+
int i = 1;
207+
while (2 * i <= heap->size) {
208+
int child = 2 * i; // left child
209+
// Pick the larger child
210+
if (child + 1 <= heap->size && heap->__arr[child + 1] > heap->__arr[child]) {
211+
child = child + 1;
212+
}
213+
// Stop if heap property is satisfied
214+
if (heap->__arr[i] >= heap->__arr[child]) {
215+
break;
216+
}
217+
swap(&heap->__arr[i], &heap->__arr[child]);
218+
i = child;
219+
}
220+
221+
return max;
222+
}
223+
```
224+
225+
{{% notice tip %}}
226+
**Common bugs to watch for:**
227+
- Off-by-one errors with 1-indexed array (root is at index 1, not 0)
228+
- Comparing with wrong child during sift down (must pick the LARGER child)
229+
- Not handling duplicates (use `>=` not just `>` when checking heap property)
230+
- Forgetting to decrement size after removal
231+
{{% /notice %}}
232+
233+
## Exercise 3: Burrows-Wheeler Transform
234+
235+
The BWT uses a suffix array to efficiently compute the transform.
236+
237+
### Key insight from the workshop
238+
239+
The last character of each rotation is found by: `original_string[(suffix_array[i] - 1 + length) % length]`
240+
241+
{{% notice warning %}}
242+
**Critical C pitfall:** The `%` operator in C is the **remainder** operator, not modulo. For negative numbers, `(-1) % 6` gives `-1` in C, not `5`. Use this safe modulo pattern:
243+
244+
```c
245+
// Safe modulo that works for negative numbers
246+
int safeMod(int a, int n) {
247+
return ((a % n) + n) % n;
248+
}
249+
```
250+
{{% /notice %}}
251+
252+
### Suffix Array Construction
253+
254+
```c
255+
// Compare two suffixes for sorting
256+
int compareSuffixes(const void *a, const void *b, void *arg) {
257+
char *str = (char *)arg;
258+
int i = *(int *)a;
259+
int j = *(int *)b;
260+
return strcmp(str + i, str + j);
261+
}
262+
263+
// Build suffix array: sorted indices of all suffixes
264+
void buildSuffixArray(char *str, int *suffixArray, int len) {
265+
for (int i = 0; i < len; i++) {
266+
suffixArray[i] = i;
267+
}
268+
// Sort suffix indices by the suffix they represent
269+
// (uses qsort_r or equivalent to pass string as context)
270+
qsort_r(suffixArray, len, sizeof(int), compareSuffixes, str);
271+
}
272+
```
273+
274+
### BWT using suffix array
275+
276+
```c
277+
void bwt(char *dest, char *src) {
278+
int len = strlen(src) + 1; // include null terminator
279+
280+
int *suffixArray = malloc(len * sizeof(int));
281+
buildSuffixArray(src, suffixArray, len);
282+
283+
// Get last character of each rotation
284+
for (int i = 0; i < len; i++) {
285+
int idx = ((suffixArray[i] - 1) % len + len) % len; // safe modulo
286+
dest[i] = src[idx];
287+
}
288+
289+
free(suffixArray);
290+
}
291+
```
292+
293+
{{% notice tip %}}
294+
**Common bugs to watch for:**
295+
- Using `%` instead of safe modulo for negative numbers
296+
- Forgetting the null terminator adds 1 to the string length
297+
- Off-by-one errors in suffix array indices
298+
- Using `printf("%s", dest)` to print the result — this stops at the null terminator character inside the transformed string. Print character by character instead:
299+
300+
```c
301+
for (int i = 0; i < STR_LEN; i++) {
302+
printf("%c", dest[i]);
303+
}
304+
```
305+
{{% /notice %}}
306+
307+
## Debugging Tools Quick Reference
308+
309+
| Tool | Command | Purpose |
310+
|------|---------|---------|
311+
| Compile | `make <program>` | Build the program |
312+
| Run | `./<program>` | Execute the program |
313+
| GDB | `gdb <program>` | Start the debugger |
314+
| GDB: run | `run` or `r` | Run the program in GDB |
315+
| GDB: breakpoint | `break file.c:42` | Pause at line 42 |
316+
| GDB: next | `next` or `n` | Step over (skip into functions) |
317+
| GDB: step | `step` or `s` | Step into functions |
318+
| GDB: print | `print varName` | Show a variable's value |
319+
| GDB: continue | `continue` or `c` | Resume until next breakpoint |
320+
| GDB: quit | `quit` or `q` | Exit GDB |
321+
| Valgrind | `valgrind --tool=memcheck --leak-check=full ./<program>` | Find memory errors |

0 commit comments

Comments
 (0)