Advent of Code 2015
2026-03-16
Puzzle: https://adventofcode.com/2015/day/9
My solution: https://codeberg.org/adfoucart/aocode15/src/branch/main/src/day9.c
This is a traveling salesman problem. What could go wrong?
We have a set of routes with an associated distance, such as:
London to Dublin = 464
London to Belfast = 518
Dublin to Belfast = 141
The goal is to find the shortest path that visits all locations exactly once.
The traveling salesman problem is known to be a little bit computationally expensive to solve. Since we can start from any node, and then we can visit the nodes in any order, the amount of combinations to test very quickly skyrocket.
So before we go any further, I’ll take a closer look at my real puzzle input, to get a sense of the complexity.
I have 8 nodes in my graph, and each node is connected to all the other nodes.
Let’s start from a simpler example with four nodes to get a better sense of the problem.
A
/ | \
D --|-- B
\ | /
C
Starting from A, I have three choices. From each destination, I get two remaining choices, etc. SO I have (N − 1)! possible paths. Here:
ABCD
ABDC
ACBD
ACDB
ADBC
ADCB
Since in my input I have 8 nodes, that means 7! paths to check, or 5040. That… doesn’t seems too bad. No trick needed so far, so let’s go for the brute force.
We start by defining a data structure. I think I want to have a
graph which has a pointer to an arbitrary starting
node, and which knows how many nodes there
are. Each node should hold a list of (pointers to)
connections, which have a pointer to another
node and a weight. Something like:
typedef struct connection_t connection_t;
typedef struct node_t node_t;
typedef struct graph_t graph_t;
struct connection_t {
node_t* dest;
int dist;
};
struct node_t {
char* name;
connection_t** connections;
int n_connections;
int capacity;
};
struct graph_t {
node_t* start;
int n;
};With connection_create, node_create,
graph_create, and the corresponding destroy.
We’ve gone through this before, and I’m sure I’ll still screw it up
somehow, so we’ll quickly have to move on to some sort of test. Do I
want to directly parse the test input? I think so. Day 7 showed me how some design choices
become a lot easier to make once we are actively trying to make the
input fit into the data structure.
So let’s go back to the test input and think about the order that we get the information, and what we can do with it.
London to Dublin = 464
London to Belfast = 518
Dublin to Belfast = 141
Splitting by “=” and by “to”, we can easily get two node names, knowing that they are connected, and with the weight of the connection. The nodes could be new nodes, in which case we want to create them, or exist, in which case we want to add the connection to them. Which means that we need a set of nodes in addition to our graph. Ideally with direct access to a node by its name. Should I take the opportunity to implement a hash map? I don’t think I want to go there yet, maybe as a refactoring step. Let’s start by a simple list, even if we need to go through all of it to know if it contains a node.
We’ve made lists before:
typedef struct {
node_t** nodes;
int n_nodes;
int capacity;
} list_node_t;Almost forgot to put the two * there but caught myself:
we want a list of pointers, not a list of nodes. In addition to the
create, destroy and add, we need
a way to find a node by name:
node_t* list_node_find(list_node_t* list, char* name){
for (int i = 0; i < list->n_nodes; i++){
if (strcmp(list->nodes[i]->name, name) == 0)
return list->nodes[i];
}
return NULL;
}I have now developed a lot of stuff without tests, which means that
I’m probably going to run into some problems soon. But everything
compiles, so it should be fine, right? Let’s start working on the
parse_input and see where it will all break.
First, create an empty graph and list.
graph_t* parse_input(char* input){
graph_t* graph = graph_create();
list_node_t* list = list_node_create(16);
return graph;
}Now parse to get the three strings that I want:
graph_t* parse_input(char* input){
// ...
str_list_t* instructions = parse_strings(input, '\n');
for (int i=0; i < instructions->n; i++){
char* instr = instructions->str_list[i];
if (strlen(instr) == 0)
continue;
str_list_t* parsed_line = parse_strings_with_string(instr, " = ");
if (parsed_line->n != 2){
printf("error: invalid line: %s", instr);
exit(1);
}
int w = atoi(parsed_line->str_list[1]);
char *nodes = parsed_line->str_list[0];
str_list_t* nodes_str = parse_strings_with_string(nodes, " to ");
if (nodes_str->n != 2){
printf("error: invalid nodes: %s", nodes);
exit(1);
}
char* nodeA_str = nodes_str->str_list[0];
char* nodeB_str = nodes_str->str_list[1];
printf("%s -> %s (%d)\n", nodeA_str, nodeB_str, w);
str_list_destroy(nodes_str);
str_list_destroy(parsed_line);
}
str_list_destroy(instructions);
// ...
}The output is correct, I can parse everything. I haven’t touched my nodes yet though, so I don’t know if anything I’ve done before works. How do I do this?
For each of the two nodes:
// ...
node_t* nodeA = list_node_find(list, nodeA_str);
node_t* nodeB = list_node_find(list, nodeB_str);
if (nodeA == NULL){
nodeA = node_create(nodeA_str);
list_node_add(list, nodeA);
}
if (nodeB == NULL){
nodeB = node_create(nodeB_str);
list_node_add(list, nodeB);
}
connection_t* AtoB = connection_create(nodeB, w);
connection_t* BtoA = connection_create(nodeA, w);
// ...I don’t have a function to add a connection to a node yet:
void node_add_connection(node_t* node, connection_t* conn){
if (node->n_connections == node->capacity){
node->capacity *= 2;
node->connections = realloc(node->connections, sizeof(connection_t*)*node->capacity);
}
node->connections[node->n_connections] = conn;
node->n_connections++;
}And:
//...
node_add_connection(nodeA, AtoB);
node_add_connection(nodeB, BtoA);
// ...And at the end, set the graph and destroy the list:
// ...
graph->start = list->nodes[0];
graph->n = list->n_nodes;
return graph;Everything runs, but I don’t have a way of checking what’s in my graph yet. That will be for tomorrow: let’s put a break here.