Advent of Code 2015
2026-03-10
Puzzle: https://adventofcode.com/2015/day/7
My solution: https://codeberg.org/adfoucart/aocode15/src/branch/main/src/day7.c
In part 1, I made wires and encoded dependencies between them. In part 2, I added lists of wires, operations, and a solver. What we are still lacking is a parser.
Let’s take a look back at what our input looks like. The test input, that is, the real one for me has 339 instructions.
123 -> x
456 -> y
x AND y -> d
x OR y -> e
x LSHIFT 2 -> f
y RSHIFT 2 -> g
NOT x -> h
NOT y -> i
Every line corresponds to one wire’s output, at the right side of the
->. So: first parse by new line (we can do that), then
parse by -> (we can do that as well). The second term of
that last split gives us the wire name. The left is a bit trickier, as
there are three possibilities:
123 -> x).NOT x).x AND y).Actually no, there are for. The binary operation has to be split between:
x LSHIFT 2)x AND y).In the first case, the tricky part is that 2 will not
have a wire definition by itself. Question: are the LSHIFT
and RSHIFT operations sometimes between two wires, or
always with a value?
Looking at my input: no. And the AND/OR operations are
always between two wires. So that makes thing a little bit easier. Let’s
set the rules:
NOT: it’s followed by a
single wire name.LSHIFT or
RSHIFT, it has the form
{wirename} {INSTRUCTION} {value}.AND or OR, it
has the form {wirename} {INSTRUCTION} {wirename}.We should be able to work with that.
Starting with the newline:
uint16_t find_value(char* input, char* wire){
str_list_t* instructions = parse_strings(input, '\n');
for (int i=0; i < instructions->n; i++){
if (strlen(instructions->str_list[i]) == 0)
continue;
printf("%s\n", instructions->str_list[i]);
}
str_list_destroy(instructions);
return 0;
}I realized that I hadn’t actually written a
str_list_destroy, which I added to my parser
code. I’m probably leaking memory everywhere in the previous days. Oops
again.
Now to parse by ->, and I have a problem. My parser
parses by a single char, not a string. I thought that would
be a problem at some point, but then forgot about it. Now I get a
reminder !
Why do I need the separator to be a char? Because I
wanted to count the number of separators before starting to
call strtok, so that I knew how much memory to allocate. I
think I have a better understanding of strtok, memory
allocation, and strings in general now than I did when I coded that, so
I can probably do better.
Start by adding a nonfunctional function in parser:
str_list_t* parse_strings_with_string(char* str, char* sep){
return parse_strings(str, sep[0]);
}And a failing test in test_parser:
bool test_parse_strings_with_string(){
char test[] = "this text :: should be parse :: into 3 lines";
str_list_t* list = parse_strings_with_string(test, " :: ");
return (list->n == 3);
}Now to make the real code. How about this: we start with an initial capacity of 2, which we double when needed. At the end, we remove the unused bits of memory.
str_list_t* parse_strings_with_string(char* str, char* sep){
char tok_start[strlen(str)+1];
strcpy(tok_start, str);
int capacity = 2;
str_list_t *str_list = malloc(sizeof(str_list_t));
str_list->n = 0;
str_list->str_list = malloc(sizeof(char*)*capacity);
char *buffer = strtok(tok_start, sep);
while(buffer){
if (str_list->n == capacity){
capacity *= 2;
str_list->str_list = realloc(str_list->str_list, sizeof(char*)*capacity);
}
str_list->str_list[str_list->n] = malloc(sizeof(char)*(strlen(buffer)+1));
strcpy(str_list->str_list[str_list->n], buffer);
buffer = strtok(NULL, sep);
str_list->n++;
}
// remove unecessary memory
str_list->str_list = realloc(str_list->str_list, sizeof(char*)*str_list->n);
return str_list;
}Trying… and the test still fails. I get… 8 substrings? Apparently I
get a split on every ' ' as well as the
' :: '. Did I misunderstand what strtok does
again? Let’s read the
docs…
A sequence of calls to this function split
strinto tokens, which are sequences of contiguous characters separated by any of the characters that are part of delimiters.
Yes I did. So much for my “better understanding” of
strtok. I guess I’ll have to use strstr?
After a bit of fussing around, I get to this:
char* strcpy_between(char* start, char* end){
char *dst = malloc(end-start+1);
memcpy(dst, start, end-start);
dst[end-start] = '\0';
return dst;
}
str_list_t* parse_strings_with_string(char* str, char* sep){
int capacity = 2;
str_list_t *str_list = malloc(sizeof(str_list_t));
str_list->n = 0;
str_list->str_list = malloc(sizeof(char*)*capacity);
char *prv = str;
char *nxt = strstr(str, sep);
while (nxt != NULL){
if (str_list->n == capacity){
capacity *= 2;
str_list->str_list = realloc(str_list->str_list, sizeof(char*)*capacity);
}
str_list->str_list[str_list->n] = strcpy_between(prv, nxt);
prv = nxt+strlen(sep);
nxt = strstr(nxt+strlen(sep), sep);
str_list->n++;
}
str_list->str_list[str_list->n] = strcpy_between(prv, str+strlen(str));
str_list->n++;
str_list->str_list = realloc(str_list->str_list, sizeof(char*)*str_list->n);
return str_list;
}This appears to work. I added a test with a string without a separator, and it passes as well.
Back to day7, I change parse_instruction to
call the new parsing method:
wire_t* parse_instruction(char* instruction){
str_list_t* instr_list = parse_strings_with_string(instruction, " -> ");
printf("%s :: %s\n", instr_list->str_list[0], instr_list->str_list[1]);
return NULL;
}I get the two sides correctly. The right side is the name of the wire. The left side needs to be further parsed according to our rules.
And this is turning out to be a problem.
First, there is still a fair amount of pointer arithmetic to do, and it’s getting a bit late for this for me, so I’m making lots of mistakes. Second… I made a big mistake. When adding dependencies, I add wires. I thought I was clever, making a graph-like structure. But I don’t know the wire that I’m depending on yet when I’m parsing.
All my prep work seems to be falling apart. I need a break.