median blog

Advent of Code 2024 Day 4 - Rust

Part One

There always seem to be a lot of 'move around in a grid'- type puzzles with the advent of code, so I spent some time on day four writing some helper functions—simple things like checking if a new coordinate was out of bounds.

Once that was done, I read the input line by line. When I encountered an X, I added eight potential search options to a list to be checked in the next step.

    for line in content.lines() {
        cols = 0;
        lines.push(
            line.chars()
                .map(|x| {
                    if x == 'X' {
                        rcs.iter().for_each(|e| {
                            let (dr, dc) = e;
                            to_check.push(XmasBit {
                                c: 'X',
                                row: rows,
                                col: cols,
                                dr: *dr,
                                dc: *dc,
                            });
                        });
                    }
                    cols += 1;
                    x
                })
                .collect(),
        );
        rows += 1;
    }

The list rcs was a manually filled list of all the needed column and row shifts for each of the eight valid directions.

Once I'd finished parsing the input, I worked through the to_check list. For each entry, I'd move one step in the stored direction and check if the new location was both in bounds and contained the next letter. If that next letter was the S from XMAS, I added one to the total match counter.

    while let Some(elem) = to_check.pop() {
        // Check Bounds
        let new_r = elem.row + elem.dr;
        let new_c = elem.col + elem.dc;

        if !is_in_bounds(rows, cols, new_r, new_c) {
            continue;
        }

        // Check Letter
        let letter = lines[new_r as usize][new_c as usize];
        match letter {
            'M' => {
                if elem.c == 'X' {
                    to_check.push(XmasBit {
                        c: 'M',
                        row: new_r,
                        col: new_c,
                        dr: elem.dr,
                        dc: elem.dc,
                    });
                }
            }
            'A' => {
                if elem.c == 'M' {
                    to_check.push(XmasBit {
                        c: 'A',
                        row: new_r,
                        col: new_c,
                        dr: elem.dr,
                        dc: elem.dc,
                    });
                }
            }
            'S' => {
                if elem.c == 'A' {
                    found += 1;
                }
            }
            _ => continue,
        };
    }

    println!("{:?}", found);

Part Two

In part two, we were tasked with finding slightly more complicated layouts. To achieve this, I added a second list to the parsing step that looked for all the A characters.

Once I had them, I looped through each and checked the two diagonals. Assuming both entries were in bounds, I wrote both letters of the diagonal to a string. If that string as "MS" or "SM" the diagonal contained 'MAS' either forward or backwards. This lets me check two diagonals per 'A' or four entries. The total match count was increased if the A had two matching diagonals.

    let mut found_b = 0;

    while let Some(a) = to_check_b.pop() {
        let mut diagonals = 0;
        let both_offsets = vec![vec![(-1, -1), (1, 1)], vec![(-1, 1), (1, -1)]];
        for offsets in both_offsets {
            let mut items: String = "".to_string();
            for offset in offsets {
                let new_r = a.row + offset.0;
                let new_c = a.col + offset.1;
                if is_in_bounds(rows, cols, new_r, new_c) {
                    items.push(lines[new_r as usize][new_c as usize]);
                }
            }

            if items == "MS" || items == "SM" {
                diagonals += 1;
            }
        }
        if diagonals == 2 {
            found_b += 1;
        }
    }

    println!("{:?}", found_b);

This was the first puzzle I had to battle between solving and writing a needlessly 'cleaner' solution. Handwriting lists of tuples felt like a bad code smell, but they're often more straightforward and easier to debug.

The complete solution can be found here.