Skip to content

Commit 19c3ebe

Browse files
committed
[2025] Day 8 part 1
1 parent 8c534af commit 19c3ebe

11 files changed

Lines changed: 322 additions & 8 deletions

File tree

2025/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,4 @@ add_subdirectory("day04")
5050
add_subdirectory("day05")
5151
add_subdirectory("day06")
5252
add_subdirectory("day07")
53+
add_subdirectory("day08")

2025/day08/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/example.txt" "${CMAKE_CURRENT_BINARY_DIR}/example.txt" COPYONLY)
2+
3+
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/input.txt")
4+
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/input.txt" "${CMAKE_CURRENT_BINARY_DIR}/input.txt" COPYONLY)
5+
endif ()
6+
7+
target_sources(aoc2025 PRIVATE
8+
day08.c
9+
day08.h
10+
day08_test.cpp
11+
)

2025/day08/day08.c

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#include "day08/day08.h"
2+
3+
#include "lib/debug.h"
4+
#include "lib/deleter.h"
5+
#include "lib/fatal_error.h"
6+
#include "lib/file.h"
7+
#include "lib/string_split.h"
8+
#include "lib/string_view.h"
9+
#include "lib/vector.h"
10+
11+
#include <assert.h>
12+
#include <stdint.h>
13+
#include <stdio.h>
14+
#include <stdlib.h>
15+
16+
struct JunctionBox {
17+
uint32_t x;
18+
uint32_t y;
19+
uint32_t z;
20+
uint32_t circuit_no; // 0 = no circuit
21+
};
22+
23+
struct JunctionBoxPair {
24+
struct JunctionBox* box_1;
25+
struct JunctionBox* box_2;
26+
uint64_t distance_sq;
27+
};
28+
29+
static uint64_t calc_distance_sq(const struct JunctionBox* box_1, const struct JunctionBox* box_2) {
30+
const int64_t dx = (int64_t)box_1->x - (int64_t)box_2->x;
31+
const int64_t dy = (int64_t)box_1->y - (int64_t)box_2->y;
32+
const int64_t dz = (int64_t)box_1->z - (int64_t)box_2->z;
33+
34+
return dx * dx + dy * dy + dz * dz;
35+
}
36+
37+
static int compare_box_pairs(const void* a, const void* b) {
38+
const struct JunctionBoxPair* box_a = a;
39+
const struct JunctionBoxPair* box_b = b;
40+
41+
if (box_a->distance_sq < box_b->distance_sq) {
42+
return -1;
43+
}
44+
if (box_a->distance_sq > box_b->distance_sq) {
45+
return 1;
46+
}
47+
48+
return 0;
49+
}
50+
51+
static int compare_uint32_t_desc(const void* a, const void* b) {
52+
const uint32_t uint_a = *(uint32_t*)a;
53+
const uint32_t uint_b = *(uint32_t*)b;
54+
55+
if (uint_a < uint_b) {
56+
return 1;
57+
}
58+
if (uint_a > uint_b) {
59+
return -1;
60+
}
61+
62+
return 0;
63+
}
64+
65+
uint32_t day08_part1(FILE* file, bool example) {
66+
char buffer[256];
67+
68+
struct Vector* box_positions = vector_create(sizeof(struct JunctionBox), DEFAULT_DELETER);
69+
70+
while (read_line(file, buffer, sizeof(buffer))) {
71+
struct StringSplitIterator iter = string_split_create(buffer, ",", STRING_SPLIT_DEFAULT);
72+
uint32_t coords[3];
73+
size_t coords_idx = 0;
74+
75+
do {
76+
if (!string_view_try_parse_uint32(&iter.current_segment, &coords[coords_idx])) {
77+
FATAL_ERROR("Failed to parse number");
78+
}
79+
80+
coords_idx += 1;
81+
} while (string_split_move_next(&iter));
82+
83+
if (coords_idx != 3) {
84+
FATAL_ERROR("Wrong number of coordinates");
85+
}
86+
87+
struct JunctionBox pos = {.x = coords[0], .y = coords[1], .z = coords[2], .circuit_no = 0};
88+
89+
vector_append(box_positions, &pos);
90+
}
91+
92+
const size_t max_pair_count = example ? 10 : 1000;
93+
94+
struct Vector* box_pairs = vector_create(sizeof(struct JunctionBoxPair), DEFAULT_DELETER);
95+
96+
for (size_t i = 0; i < vector_size(box_positions); i++) {
97+
struct JunctionBox* box_1 = vector_at(box_positions, i);
98+
for (size_t j = i + 1; j < vector_size(box_positions); j++) {
99+
struct JunctionBox* box_2 = vector_at(box_positions, j);
100+
const uint64_t distance_sq = calc_distance_sq(box_1, box_2);
101+
102+
const struct JunctionBoxPair pair = {
103+
.box_1 = box_1, .box_2 = box_2, .distance_sq = distance_sq};
104+
105+
// We only need to check the N closest pairs, and if we don't impose some sort of
106+
// limit, as the input is 1000 lines, we will have 1000^2 = 1 million pairs in our
107+
// vector.
108+
if (vector_size(box_pairs) < max_pair_count) {
109+
vector_append(box_pairs, &pair);
110+
// TODO: perhaps a tree or some sort would be nice here for not requiring an
111+
// explicit sort step each time
112+
vector_sort(box_pairs, compare_box_pairs);
113+
} else {
114+
const struct JunctionBoxPair* furthest_pair = vector_back(box_pairs);
115+
if (pair.distance_sq < furthest_pair->distance_sq) {
116+
vector_pop(box_pairs, nullptr);
117+
vector_append(box_pairs, &pair);
118+
// Sort again: new pair might not belong at the end of the vector.
119+
vector_sort(box_pairs, compare_box_pairs);
120+
}
121+
}
122+
}
123+
}
124+
125+
int32_t next_circuit_no = 1;
126+
size_t made_connections_count = 0;
127+
128+
// Iterate through the sorted vector
129+
for (size_t i = 0; i < vector_size(box_pairs) && made_connections_count < max_pair_count; i++) {
130+
const struct JunctionBoxPair* pair = vector_at(box_pairs, i);
131+
if (pair->box_1->circuit_no == 0 && pair->box_2->circuit_no == 0) {
132+
// Create a new circuit
133+
DEBUG_PRINT("Connecting Box(%u,%u,%u) to Box(%u,%u,%u) in new circuit %d",
134+
pair->box_1->x, pair->box_1->y, pair->box_1->z, pair->box_2->x,
135+
pair->box_2->y, pair->box_2->z, next_circuit_no);
136+
137+
pair->box_1->circuit_no = next_circuit_no;
138+
pair->box_2->circuit_no = next_circuit_no;
139+
next_circuit_no += 1;
140+
made_connections_count += 1;
141+
} else if (pair->box_1->circuit_no == 0) {
142+
// Join box 1 into box 2's circuit
143+
DEBUG_PRINT("Connecting Box(%u,%u,%u) to Box(%u,%u,%u) in box 2's circuit %d",
144+
pair->box_1->x, pair->box_1->y, pair->box_1->z, pair->box_2->x,
145+
pair->box_2->y, pair->box_2->z, pair->box_2->circuit_no);
146+
147+
pair->box_1->circuit_no = pair->box_2->circuit_no;
148+
made_connections_count += 1;
149+
} else if (pair->box_2->circuit_no == 0) {
150+
// Join box 2 into box 1's circuit
151+
DEBUG_PRINT("Connecting Box(%u,%u,%u) to Box(%u,%u,%u) in box 1's circuit %d",
152+
pair->box_1->x, pair->box_1->y, pair->box_1->z, pair->box_2->x,
153+
pair->box_2->y, pair->box_2->z, pair->box_1->circuit_no);
154+
155+
pair->box_2->circuit_no = pair->box_1->circuit_no;
156+
made_connections_count += 1;
157+
} else if (pair->box_2->circuit_no != pair->box_1->circuit_no) { // Merge circuits
158+
DEBUG_PRINT("Merging Box(%u,%u,%u) circuit %d with Box(%u,%u,%u) circuit %d into new "
159+
"circuit %d",
160+
pair->box_1->x, pair->box_1->y, pair->box_1->z, pair->box_1->circuit_no,
161+
pair->box_2->x, pair->box_2->y, pair->box_2->z, pair->box_2->circuit_no,
162+
next_circuit_no);
163+
164+
const uint32_t target_circuit_no_1 = pair->box_1->circuit_no;
165+
const uint32_t target_circuit_no_2 = pair->box_2->circuit_no;
166+
167+
for (size_t j = 0; j < vector_size(box_positions); j++) {
168+
struct JunctionBox* box = vector_at(box_positions, j);
169+
if (box->circuit_no == target_circuit_no_1 ||
170+
box->circuit_no == target_circuit_no_2) {
171+
DEBUG_PRINT("Moving Box(%u,%u,%u) from circuit %d to circuit %d", box->x,
172+
box->y, box->z, box->circuit_no, next_circuit_no);
173+
box->circuit_no = next_circuit_no;
174+
}
175+
}
176+
next_circuit_no += 1;
177+
made_connections_count += 1;
178+
} else {
179+
DEBUG_PRINT("Cannot connect Box(%u,%u,%u) to Box(%u,%u,%u): both already members of "
180+
"the same circuit %d and %d",
181+
pair->box_1->x, pair->box_1->y, pair->box_1->z, pair->box_2->x,
182+
pair->box_2->y, pair->box_2->z, pair->box_1->circuit_no,
183+
pair->box_2->circuit_no);
184+
made_connections_count += 1; // I don't get this
185+
}
186+
}
187+
188+
uint32_t circuit_sizes[1000] = {0};
189+
for (size_t j = 0; j < vector_size(box_positions); j++) {
190+
const struct JunctionBox* box = vector_at(box_positions, j);
191+
assert(box->circuit_no < 1000);
192+
if (box->circuit_no != 0) {
193+
circuit_sizes[box->circuit_no] += 1;
194+
}
195+
}
196+
197+
for (size_t i = 1; i < 10; i++) {
198+
DEBUG_PRINT("Circuit no. %zu has size %d", i, circuit_sizes[i]);
199+
}
200+
201+
qsort(circuit_sizes, sizeof(circuit_sizes) / sizeof(uint32_t), sizeof(uint32_t),
202+
compare_uint32_t_desc);
203+
204+
DEBUG_PRINT("Largest circuits: %u, %u, and %u\n", circuit_sizes[0], circuit_sizes[1],
205+
circuit_sizes[2]);
206+
207+
const uint32_t result = circuit_sizes[0] * circuit_sizes[1] * circuit_sizes[2];
208+
209+
vector_free(box_positions);
210+
vector_free(box_pairs);
211+
212+
return result;
213+
}

2025/day08/day08.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#ifndef AOC2025_DAY08_H
2+
#define AOC2025_DAY08_H
3+
4+
#include <stdint.h>
5+
#include <stdio.h>
6+
7+
uint32_t day08_part1(FILE* file, bool example);
8+
9+
#endif // AOC2025_DAY08_H

2025/day08/day08_test.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
extern "C" {
2+
#include "day08/day08.h"
3+
#include "lib/file.h"
4+
}
5+
6+
#include <cstdint>
7+
#include <cstdio>
8+
9+
#include "gtest/gtest.h"
10+
11+
TEST(day08part1, example) {
12+
FILE* example = get_example_input(8);
13+
14+
const uint32_t answer = day08_part1(example, true);
15+
printf("answer = %d", answer);
16+
17+
ASSERT_EQ(40, answer);
18+
}
19+
20+
TEST(day08part1, real) {
21+
FILE* real = get_real_input(8);
22+
23+
const uint32_t answer = day08_part1(real, false);
24+
printf("answer = %d", answer);
25+
}
26+
27+
TEST(day08part2, example) {}
28+
29+
TEST(day08part2, real) {}

2025/day08/example.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
162,817,812
2+
57,618,57
3+
906,360,560
4+
592,479,940
5+
352,342,300
6+
466,668,158
7+
542,29,236
8+
431,825,988
9+
739,650,466
10+
52,470,668
11+
216,146,977
12+
819,987,18
13+
117,168,530
14+
805,96,715
15+
346,949,466
16+
970,615,88
17+
941,993,340
18+
862,61,35
19+
984,92,344
20+
425,690,689

2025/lib/hash_map.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ static void hash_map_resize_if_needed(struct HashMap* hash_map) {
121121
struct HashMap hash_map_create(const size_t key_size, const size_t value_size, const HashFn hash_fn,
122122
const EqualFn equal_fn) {
123123

124-
// n.b. MUST use calloc, as hash_map_free will access each bucket and try to free the
125-
// owned vector, if uninitialised buckets are not zeroed then that is UB.
124+
// n.b. MUST use calloc, as we often access new buckets and check if the vector is a nullptr,
125+
// for lazy creation, but this is UB if we do not zero the bucket array
126126
struct HashMapBucket* hash_map_buckets =
127127
calloc(INITIAL_BUCKETS_SIZE, sizeof(struct HashMapBucket));
128128
if (hash_map_buckets == nullptr) {
@@ -181,7 +181,7 @@ void* hash_map_get_value_ptr(struct HashMap* hash_map, const void* key) {
181181
* a pointer to that.
182182
* @param hash_map The hash map to search.
183183
* @param key The key to search for.
184-
* @return A pointer to a valuue.
184+
* @return A pointer to a value.
185185
* @note The default value is constructed by zeroing out memory of size value_size.
186186
*/
187187
void* hash_map_get_value_ptr_or_add_default(struct HashMap* hash_map, const void* key) {

2025/lib/string_view.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#include "debug.h"
44

55
#include <assert.h>
6-
#include <ctype.h>
76
#include <limits.h>
87
#include <stddef.h>
98
#include <stdint.h>
@@ -75,14 +74,22 @@ bool string_view_try_parse_int32(const struct StringView* view, int32_t* out_res
7574
* @return A boolean indicating whether the parsing succeeded or not.
7675
*/
7776
bool string_view_try_parse_int64(const struct StringView* view, int64_t* out_result) {
78-
long result = 0;
77+
int64_t result = 0;
7978

8079
for (size_t i = 0; i < view->length; i++) {
80+
if (result > INT64_MAX / 10) {
81+
return false;
82+
}
83+
8184
result *= 10;
8285

8386
const char ch = view->data[i];
84-
if (isdigit(ch)) {
87+
if (ch >= '0' && ch <= '9') {
8588
const int digit = ch - '0';
89+
if (result > INT64_MAX - digit) {
90+
return false;
91+
}
92+
8693
result += digit;
8794
} else {
8895
DEBUG_PRINT("Found non-digit character: %c", ch);
@@ -92,4 +99,21 @@ bool string_view_try_parse_int64(const struct StringView* view, int64_t* out_res
9299

93100
*out_result = result;
94101
return true;
102+
}
103+
104+
/**
105+
* Tries to parse a StringView to an uint64_t.
106+
* @param view The input view.
107+
* @param out_result Pointer to result. Will not be written to if this returns false.
108+
* @return A boolean indicating whether the parsing succeeded or not.
109+
*/
110+
bool string_view_try_parse_uint32(const struct StringView* view, uint32_t* out_result) {
111+
int64_t tmp_result = 0;
112+
const bool result = string_view_try_parse_int64(view, &tmp_result);
113+
if (result && tmp_result <= UINT32_MAX && tmp_result >= 0) {
114+
*out_result = (uint32_t)tmp_result;
115+
return true;
116+
}
117+
118+
return false;
95119
}

2025/lib/string_view.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ bool string_view_try_parse_int32(const struct StringView* view, int32_t* out_res
2121

2222
bool string_view_try_parse_int64(const struct StringView* view, int64_t* out_result);
2323

24+
bool string_view_try_parse_uint32(const struct StringView* view, uint32_t* out_result);
25+
2426
#endif // AOC2025_STRING_VIEW_H

2025/lib/vector.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,7 @@ void* vector_back(struct Vector* vector) { return vector_at(vector, vector->size
133133

134134
void vector_remove_at(struct Vector* vector, const size_t index) {
135135
if (index >= vector->size) {
136-
assert(false && "out of bounds access to vector");
137-
abort();
136+
FATAL_ERROR("out of bounds access to vector");
138137
}
139138

140139
const size_t num_elements_to_move = vector->size - index - 1;
@@ -151,6 +150,10 @@ void vector_remove_at(struct Vector* vector, const size_t index) {
151150
vector->size -= 1;
152151
}
153152

153+
void vector_sort(struct Vector* vector, int (*comparer)(const void*, const void*)) {
154+
qsort(vector->data, vector->size, vector->element_size, comparer);
155+
}
156+
154157
void vector_free(struct Vector* vector) {
155158
// DEBUG_PRINT("Destroying vector at %p", (void*)vector);
156159

0 commit comments

Comments
 (0)