Rank 02 Deep Dives - kevshouse/exam_quest GitHub Wiki

Deep Dive: Linked List & Matrix Functions Let's visualize key algorithms with Mermaid memory diagrams and optimized implementations:

  1. Linked List: ft_list_foreach Purpose: Apply function f to every node's data in a linked list.

Memory Diagram: Diagram Code

graph LR
    subgraph Heap Memory
        A[Node1: data=5, next] --> B[Node2: data=8, next]
        B --> C[Node3: data=3, next]
        C --> D[NULL]
    end
    
    subgraph Stack
        HEAD[Head Pointer] --> A
        FUNC[f: print_data]
    end
    
    subgraph Execution
        FUNC -->|Applied to| A.data
        FUNC -->|Applied to| B.data
        FUNC -->|Applied to| C.data
    end
Loading

Implementation:

typedef struct s_list {
    void *data;
    struct s_list *next;
} t_list;

void ft_list_foreach(t_list *begin_list, void (*f)(void *)) {
    t_list *current = begin_list;
    
    while (current) {
        f(current->data);       // Apply function to data
        current = current->next; // Move to next node
    }
}

// Example usage:
void print_int(void *data) {
    printf("%d\n", *(int *)data);
}

int main() {
    t_list node3 = {.data = malloc(sizeof(int)), .next = NULL};
    *(int *)node3.data = 3;
    
    t_list node2 = {.data = malloc(sizeof(int)), .next = &node3};
    *(int *)node2.data = 8;
    
    t_list node1 = {.data = malloc(sizeof(int)), .next = &node2};
    *(int *)node1.data = 5;
    
    ft_list_foreach(&node1, print_int);
    // Output: 5\n8\n3\n
}

Key Techniques:

Type-agnostic void* data handling

Iterative traversal (safe for large lists)

Function pointer usage (f(void*))

  1. Matrix: flood_fill (Enhanced) Purpose: Fill connected region in 2D grid starting from (x,y).

Memory Diagram: Diagram Code

graph TB
    subgraph Grid Memory
        Row0["'1', '1', '0', '1'"]
        Row1["'1', '1', '0', '1'"]
        Row2["'1', '0', '1', '1'"]
    end
    
    subgraph Stack
        DIRS[Direction Vectors: 1,0 -1,0 0,1 0,-1]
        STACK[Point Stack: 0,0 1,0 0,1 ...]
    end
    
    subgraph Execution
        STACK -->|Pop| CUR["Current: (0,0)"]
        CUR --> CHECK["Check: Within bounds? Color match?"]
        CHECK -->|Yes| FILL["Set to 'F'"]
        FILL --> PUSH["Push neighbors: (1,0), (0,1)"]
    end
Loading

Optimized Implementation:

typedef struct s_point { int x; int y; } t_point;

void flood_fill(char **tab, t_point size, t_point begin) {
    const t_point dirs[4] = {{1,0}, {-1,0}, {0,1}, {0,-1}};
    t_point stack[1000];
    int top = -1;
    char orig = tab[begin.y][begin.x];
    
    if (orig == 'F' || size.y <= 0 || size.x <= 0) return;
    
    stack[++top] = begin; // Push starting point
    
    while (top >= 0) {
        t_point cur = stack[top--];
        
        if (cur.y < 0 || cur.y >= size.y || 
            cur.x < 0 || cur.x >= size.x || 
            tab[cur.y][cur.x] != orig) 
            continue;
        
        tab[cur.y][cur.x] = 'F';
        
        for (int i = 0; i < 4; i++) {
            t_point next = {cur.x + dirs[i].x, cur.y + dirs[i].y};
            if (next.x >= 0 && next.x < size.x && 
                next.y >= 0 && next.y < size.y && 
                tab[next.y][next.x] == orig) {
                stack[++top] = next;
            }
        }
    }
}

Key Optimizations:

Fixed-size stack (no malloc)

Direction vector loop

Boundary checks before stack push

Skip condition before recursion

  1. Array Sorting: sort_int_tab Purpose: Sort integer array in-place using bubble sort.

Memory Diagram: Diagram Code

graph LR
    subgraph Array Memory
        A[Before: 5, 2, 8, 3]
        B[After: 2, 3, 5, 8]
    end
    
    subgraph Process
        P1["Pass 1: Compare 5↔2 → Swap"]
        P2["Pass 2: Compare 5↔3 → Swap"]
        P3["Pass 3: Compare 5↔8 → No swap"]
    end
    
    A --> P1 --> P2 --> P3 --> B
Loading

Implementation:

void sort_int_tab(int *tab, unsigned int size) {
    if (!tab || size < 2) return;
    
    for (unsigned int i = 0; i < size - 1; i++) {
        int swapped = 0;
        for (unsigned int j = 0; j < size - i - 1; j++) {
            if (tab[j] > tab[j+1]) {
                // Swap values
                int temp = tab[j];
                tab[j] = tab[j+1];
                tab[j+1] = temp;
                swapped = 1;
            }
        }
        if (!swapped) break; // Early exit if sorted
    }
}

Key Techniques:

In-place swapping

Early termination when no swaps occur

O(n²) worst-case, optimized for nearly-sorted arrays

  1. String Processing: epur_str Purpose: Remove extra whitespace from string (" hello world " → "hello world").

State Machine Diagram: Diagram

graph TD
    S((Start)) --> A((IN_SPACE))
    A -->|Non-space| B(("IN_WORD<br>Write char"))
    B -->|Space| A
    B -->|Non-space| B
    A -->|Space| C(("LEADING_SPACE<br>Skip"))
    C -->|Non-space| B
Loading

Implementation:

void epur_str(char *str) {
    int i = 0, j = 0;
    int in_word = 0;
    
    while (str[i]) {
        if (str[i] != ' ' && str[i] != '\t') {
            if (in_word && str[i-1] == ' ') 
                str[j++] = ' '; // Add single space between words
            str[j++] = str[i];
            in_word = 1;
        }
        else if (in_word) {
            in_word = 0; // Mark space after word
        }
        i++;
    }
    str[j] = '\0'; // Terminate string
}

Edge Handling:

Leading/trailing spaces removed

Consecutive spaces collapsed

Tabs treated as spaces

  1. Math Operations: lcm/pgcd Purpose: Compute Least Common Multiple and Greatest Common Divisor.

Diagram Code

graph TD
    A[Input a,b] --> B{Compute PGCD}
    B --> C["while b ≠ 0:<br>a, b = b, a % b"]
    C --> D["PGCD = a"]
    D --> E["LCM = |a*b| / PGCD"]
Loading

Euclidean Algorithm Implementation:

unsigned int pgcd(unsigned int a, unsigned int b) {
    if (a == 0 || b == 0) return 0;
    while (b != 0) {
        unsigned int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

unsigned int lcm(unsigned int a, unsigned int b) {
    if (a == 0 || b == 0) return 0;
    unsigned int gcd = pgcd(a, b);
    return (a / gcd) * b; // Avoid overflow
}

Key Notes:

Handle division by zero

Order-independent results

Integer overflow protection in LCM

🧠 Deep Dive: Flood Fill Algorithm

Purpose: Fill connected regions in a 2D grid starting from a given point, replacing target colors while respecting boundaries.

🔢 Core Implementation Options

  1. Recursive DFS (Simple but Risky):

    void fill(int **grid, int rows, int cols, int r, int c, int target, int newColor) {
        if (r < 0 || r >= rows || c < 0 || c >= cols) return;  // Bounds check
        if (grid[r][c] != target || grid[r][c] == newColor) return;  // Color match
        
        grid[r][c] = newColor;  // Update color
        // Recursive 4-directional calls
        fill(grid, rows, cols, r+1, c, target, newColor);  // Down
        fill(grid, rows, cols, r-1, c, target, newColor);  // Up
        fill(grid, rows, cols, r, c+1, target, newColor);  // Right
        fill(grid, rows, cols, r, c-1, target, newColor);  // Left
    }

    Pros: Easy to understand (5–10 lines).
    Cons: Stack overflow risk for large grids (>1k cells).

  2. Iterative BFS/DFS (Recommended for Exams):

    typedef struct { int r, c; } Point;
    void floodFill(int **grid, int rows, int cols, Point start, int newColor) {
        int target = grid[start.r][start.c];
        if (target == newColor) return;
        
        Point *stack = malloc(rows * cols * sizeof(Point));  // Static stack
        int top = -1;
        stack[++top] = start;
        
        while (top >= 0) {
            Point cur = stack[top--];
            if (cur.r < 0 || cur.r >= rows || cur.c < 0 || cur.c >= cols) continue;
            if (grid[cur.r][cur.c] != target) continue;
            
            grid[cur.r][cur.c] = newColor;  // Fill
            // Push neighbors (4-way)
            stack[++top] = (Point){cur.r+1, cur.c};  // Down
            stack[++top] = (Point){cur.r-1, cur.c};  // Up
            stack[++top] = (Point){cur.r, cur.c+1};  // Right
            stack[++top] = (Point){cur.r, cur.c-1};  // Left
        }
        free(stack);
    }

    Pros:

    • No stack overflow (handles grids up to 10k cells).
    • 3x faster than recursion for complex shapes.

⚙️ Key Optimizations

  • Direction Vectors: Simplify neighbor checks:
     const int dirs[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}};  // Down, Up, Right, Left
     for (int i = 0; i < 4; i++) {
         int nr = cur.r + dirs[i][0], nc = cur.c + dirs[i][1];
         if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {
             // Process neighbor
         }
     }
  • Span Filling: For large grids, fill entire horizontal spans first, then scan rows above/below for new seeds. Reduces redundant checks by 50%.

🧪 Critical Test Cases

  1. Single-Cell Grid:
    Input: grid = [[5]], start = (0,0), newColor=2Output: [[2]]
  2. Barrier Test:
    Input: grid = [[1,0,1],[0,1,0],[1,0,1]], start=(1,1), newColor=2Only center updates.
  3. Color Bleed Prevention:
    Input: grid = [[1,2],[2,1]], start=(0,0), newColor=3Updates only connected 1s, not 2s.

🧩 Deep Dive: Linked List Operations

Purpose: Efficiently manipulate dynamic node chains using traversal, insertion, deletion, and transformation.

🔄 Common Operations & Complexities

Operation Code Snippet (C) Time Edge Cases
Insert at Head newNode→next=head; head=newNode; O(1) Empty list
Insert at Tail while(temp→next) temp=temp→next; temp→next=newNode; O(n) Single-node list
Delete Node prev→next=curr→next; free(curr); O(n) Deleting head/tail
Search while(curr){if(curr→data==key) return curr;} O(n) Key not found

🧮 Memory Diagram for Traversal

graph LR
    A[Head] --> B[Node1: data=5]
    B --> C[Node2: data=8]
    C --> D[Node3: data=3]
    D --> E[NULL]
    F[Temp Pointer] --> B
    F -.-> C -.-> D
Loading

Process: Temp pointer moves node-by-node until temp→next == NULL.

🔧 Advanced: Matrix to Linked List Conversion

Convert 2D matrix into a 4-pointer doubly linked list (up/down/left/right):

typedef struct Node {
    int data;
    struct Node *up, *down, *left, *right;
} Node;

Node* buildMatrixList(int **matrix, int rows, int cols) {
    Node *nodes[rows][cols];
    // Create nodes
    for (int i=0; i<rows; i++) {
        for (int j=0; j<cols; j++) {
            nodes[i][j] = malloc(sizeof(Node));
            nodes[i][j]→data = matrix[i][j];
        }
    }
    // Link neighbors
    for (int i=0; i<rows; i++) {
        for (int j=0; j<cols; j++) {
            nodes[i][j]→right = (j+1 < cols) ? nodes[i][j+1] : NULL;
            nodes[i][j]→left = (j-1 >= 0) ? nodes[i][j-1] : NULL;
            nodes[i][j]→down = (i+1 < rows) ? nodes[i+1][j] : NULL;
            nodes[i][j]→up = (i-1 >= 0) ? nodes[i-1][j] : NULL;
        }
    }
    return nodes[0][0];  // Return head
}

Use Case: Sparse matrix storage (e.g., only non-zero elements linked).

🚫 Common Pitfalls in Exams

  1. Memory Leaks: Forgetting to free() after deletion → Use Valgrind checks.
  2. Null Pointer Dereference: Missing if (head == NULL) in insertion/deletion.
  3. Infinite Loops: Incorrectly set pointers in cyclic lists → Always set tail→next=NULL.

💎 Key Takeaways

  1. Flood Fill:
    • Use iterative BFS for exam safety (no stack overflow).
    • Optimize with direction vectors and span filling.
  2. Linked Lists:
    • Master O(1) head operations and O(n) traversals.
    • Practice matrix conversion for spatial problems.
  3. Always Test:
    • Edge cases: empty grids, single-node lists, color boundaries.

For exam simulators, focus on iterative flood fill and pointer safety in linked lists. Implement all test cases above to avoid Moulinette traps! 🔑

⚠️ **GitHub.com Fallback** ⚠️