img of Advent of Code 2024 - Day 3: Mull It Over

Advent of Code 2024 - Day 3: Mull It Over


Attention!

  • Check Advent of Code for the full challenge details. They created everything we need to know about the challenge - give them your support.

  • My solutions are not ‘the’ solutions - I target one of the possible solutions. They might not be the most efficient, but they are a way to solve the problem. I do not plan to spend too much time doing the challenges.

  • Every challenge = different language - just for fun and learning.

  • I do not include the input data, but you can find it on the Advent of Code website or in the repository of the code.


Welcome back to my Advent of Code 2024 journey! Day 3. This one brings a fun puzzle with corrupted memory, challenging me to extract valid instructions from a messy stream of data.

In this post, I’ll walk through the problem breakdown and my solution, which is written in C.


Problem Breakdown

The task revolves around a corrupted program with an array of multiplication instructions. The instructions are surrounded by invalid characters, which need to be ignored. For example, I might encounter patterns like xmul(2,4)%&mul[3,7]!@^—only the real mul instructions need to be processed.

Instructions to Handle:

  • mul(X, Y): This instruction multiplies X and Y. I need to extract valid instances of this pattern and compute their products.
  • do() and don’t(): These instructions affect whether future mul instructions are enabled or disabled.

What Needs to Be Done:

  1. Part One: Ignore invalid characters and extract valid multiplication instructions. Sum the results.
  2. Part Two: Handle the do() and don't() instructions. If do() is encountered, subsequent multiplications are enabled; if don't() is encountered, they are disabled.

Part One: Parsing Valid mul Instructions

I start by scanning through the corrupted memory for valid multiplication operations. The main challenge is to filter out unwanted characters and only process the real mul instructions.

Code for Part One

For Part One I wrote a parse_mul function that detects the mul instruction in the input and extracts the numbers to multiply. Then I simply sum up the results of all valid multiplications in the input. The result is printed after scanning the entire input.

// main.c
void part_one(char buffer[]) {
    int sum = 0;
    int i = 0;
    while (i < FILE_SIZE) {
        sum += parse_mul(buffer, &i, NULL);
    }
    printf("Part1 :: Sum: %d\n", sum);
}

int parse_mul(char buff[], int *ptr, int *do_flag) {
    while (*ptr < FILE_SIZE && !chop(buff, ptr, "mul(")) {
        if (do_flag != NULL) check_flag(buff, ptr, do_flag); // no need for part one
        if (chop(buff, ptr, "mul(")) break;
        (*ptr)++;
    }
    int a = parse_number(buff, ptr);
    if (!chop(buff, ptr, ",")) return 0;
    int b = parse_number(buff, ptr);
    if (!chop(buff, ptr, ")")) return 0;
    return a * b;
}

parse_mul: This function searches for valid mul(X, Y) instructions. It parses the numbers and computes their product if the pattern is valid.

In parse_mul functions I’m using some helper functions:

  • chop: Checks if a given string str is present in the buffer at the current position. If found, it advances the pointer accordingly.
  • parse_number: Processes the input buffer and converts a sequence of digits into an integer. It handles multi-digit numbers, advancing the pointer as it reads.
// main.c
int chop(char buff[], int* ptr, char str[]) {
    int i = 0;
    while (str[i] != '\0') {
        if (buff[*ptr + i] != str[i]) return 0;
        i++;
    }
    (*ptr) += i;
    return 1;
}

int parse_number(char buff[], int* ptr) {
    int num = 0;
    while (buff[*ptr] != '\0' && (buff[*ptr] >= '0' && buff[*ptr] <= '9')) {
        num = num * 10 + (buff[*ptr] - '0');
        (*ptr)++;
    }
    return num;
}

Part Two: do() and don't() Instructions

In Part Two, I have the added complexity of do() and don't() instructions, which enable or disable the subsequent mul instructions. I need to ensure that only enabled multiplications are considered for the sum.

Code for Part Two

The main loop in Part Two is similar to Part One but incorporates the flag check to determine if the multiplication should be performed.

// main.c
void part_two(char buffer[]) {
    int sum = 0;
    int do_flag = 1; // Initially enabled
    int i = 0;
    while (i < FILE_SIZE) {
        int m = parse_mul(buffer, &i, &do_flag);
        if (do_flag) sum += m; // Only add if mul is enabled
    }
    printf("Part2 :: Sum: %d\n", sum);
}
  • do_flag: A flag that tracks whether multiplications are enabled (1) or disabled (0).
  • parse_mul: Updated this function: now checks the do_flag before adding the result of a multiplication to the total. You could see the updated version already.
// main.c
void check_flag(char s[], int* i, int* do_flag) {
    if (chop(s, i, "do()")) {
        (*do_flag) = 1; // Enable future mul instructions
    } else if (chop(s, i, "don't()")) {
        (*do_flag) = 0; // Disable future mul instructions
    }
}

check_flag: Simple function that checks if do() or don't() appears in the input and updates the do_flag accordingly.


Final Solution Code

The final solution code can be found on my GitHub repository.

If you like the content, don’t forget to give it a star ⭐.

  • Time Complexity: The program processes each character in the input file once, making it O(n) where n is the size of the input.
  • Space Complexity: The space complexity is O(n) due to storing the input data in a buffer.

Conclusion

Day 3 was a fun challenge, requiring a combination of string manipulation and conditional logic. The addition of the do() and don't() instructions in Part Two made things a little trickier, but by maintaining a simple flag, the solution remained clear and manageable.

I felt surprisingly comfortable with the problem-solving process and the implementation in C. I did some C programming earlier, but never before have I felt so good writing in this language.

Tomorrow, C++. Stay tuned.