diff --git a/include/aoc/macros.h b/include/aoc/macros.h index c188751..404b5fe 100644 --- a/include/aoc/macros.h +++ b/include/aoc/macros.h @@ -40,3 +40,23 @@ (y) = temp; \ } while (0) #define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) + +/** + * Accesses an element in a one-dimensional array as if it were a two-dimensional array. + * + * This macro calculates the index in a one-dimensional array corresponding to + * the (x, y) position in a two-dimensional array layout. The two-dimensional + * array is assumed to be row-major, where each row contains 'W' elements. + * + * @param arr The one-dimensional array. + * @param x The x-coordinate (column index) in the two-dimensional layout. + * @param y The y-coordinate (row index) in the two-dimensional layout. + * @param W The width of the two-dimensional array, i.e., the number of elements in each row. + * @return The reference to the array element at the specified (x, y) position. + * + * Example usage: + * int myArray[10]; // A one-dimensional array + * int W = 5; // Width of the two-dimensional array representation + * INDEX(myArray, 2, 1, W) = 42; // Sets the element at (x=2, y=1) to 42 + */ +#define INDEX(arr, x, y, W) ((arr)[(y) * (W) + (x)]) diff --git a/include/aoc/point.h b/include/aoc/point.h index 60be7a2..83553c6 100644 --- a/include/aoc/point.h +++ b/include/aoc/point.h @@ -10,18 +10,32 @@ #include +#include "aoc/macros.h" +#include "aoc/types.h" + +#ifndef T +#define T i32 +#endif + typedef struct { - int x; - int y; + T x, y; } Point2D; -inline int Point2D_equal(Point2D *lhs, Point2D *rhs) { return lhs->x == rhs->x && lhs->y == rhs->y; } +static _unused_ int Point2D_equal(Point2D *lhs, Point2D *rhs) { return lhs->x == rhs->x && lhs->y == rhs->y; } -inline size_t Point2D_hash(Point2D *p) { +static _unused_ size_t Point2D_hash(Point2D *p) { const size_t prime = 31; size_t hash = 17; // Combine the hash of each struct member - hash = hash * prime + (unsigned int)p->x; - hash = hash * prime + (unsigned int)p->y; + hash = hash * prime + (size_t)p->x; + hash = hash * prime + (size_t)p->y; return hash; } + +static _unused_ T Point2D_manhattan(Point2D a, Point2D b) { + T delta_x = a.x < b.x ? b.x - a.x : a.x - b.x; + T delta_y = a.y < b.y ? b.y - a.y : a.y - b.y; + return delta_x + delta_y; +} + +#undef T diff --git a/lib/aoc/point.c b/lib/aoc/point.c deleted file mode 100644 index ff5dab8..0000000 --- a/lib/aoc/point.c +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Author: Michael Adler - * - * Copyright: 2023 Michael Adler - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "aoc/point.h" - -extern int Point2D_equal(Point2D *lhs, Point2D *rhs); -extern size_t Point2D_hash(Point2D *p); diff --git a/meson.build b/meson.build index e640b08..7d339fc 100644 --- a/meson.build +++ b/meson.build @@ -26,7 +26,6 @@ aoc_lib = static_library( 'lib/aoc/io.c', 'lib/aoc/math.c', 'lib/aoc/parser.c', - 'lib/aoc/point.c', 'lib/aoc/string.c', include_directories: include_directories(inc_dirs) ) @@ -42,6 +41,7 @@ days = { 'day08': [ 'src/day08/solve.c' ], 'day09': [ 'src/day09/solve.c' ], 'day10': [ 'src/day10/solve.c' ], + 'day11': [ 'src/day11/solve.c' ], # XXX: marker } diff --git a/puzzle/day11.md b/puzzle/day11.md new file mode 100644 index 0000000..d6e423b --- /dev/null +++ b/puzzle/day11.md @@ -0,0 +1,121 @@ + +--- Day 11: Cosmic Expansion --- + +You continue following signs for "Hot Springs" and eventually come across an observatory. The Elf within turns out to be a researcher studying cosmic expansion using the giant telescope here. + +He doesn't know anything about the missing machine parts; he's only visiting for this research project. However, he confirms that the hot springs are the next-closest area likely to have people; he'll even take you straight there once he's done with today's observation +analysis. + +Maybe you can help him with the analysis to speed things up? + +The researcher has collected a bunch of data and compiled the data into a single giant image (your puzzle input). The image includes empty space (.) and galaxies (#). For example: + +...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#..... + +The researcher is trying to figure out the sum of the lengths of the shortest path between every pair of galaxies. However, there's a catch: the universe expanded in the time it took the light from those galaxies to reach the observatory. + +Due to something involving gravitational effects, only some space expands. In fact, the result is that any rows or columns that contain no galaxies should all actually be twice as big. + +In the above example, three columns and two rows contain no galaxies: + + v v v + ...#...... + .......#.. + #......... +>..........< + ......#... + .#........ + .........# +>..........< + .......#.. + #...#..... + ^ ^ ^ + +These rows and columns need to be twice as big; the result of cosmic expansion therefore looks like this: + +....#........ +.........#... +#............ +............. +............. +........#.... +.#........... +............# +............. +............. +.........#... +#....#....... + +Equipped with this expanded universe, the shortest path between every pair of galaxies can be found. It can help to assign every galaxy a unique number: + +....1........ +.........2... +3............ +............. +............. +........4.... +.5........... +............6 +............. +............. +.........7... +8....9....... + +In these 9 galaxies, there are 36 pairs. Only count each pair once; order within the pair doesn't matter. For each pair, find any shortest path between the two galaxies using only steps that move up, down, left, or right exactly one . or # at a time. (The shortest path +between two galaxies is allowed to pass through another galaxy.) + +For example, here is one of the shortest paths between galaxies 5 and 9: + +....1........ +.........2... +3............ +............. +............. +........4.... +.5........... +.##.........6 +..##......... +...##........ +....##...7... +8....9....... + +This path has length 9 because it takes a minimum of nine steps to get from galaxy 5 to galaxy 9 (the eight locations marked # plus the step onto galaxy 9 itself). Here are some other example shortest path lengths: + +Between galaxy 1 and galaxy 7: 15 +Between galaxy 3 and galaxy 6: 17 +Between galaxy 8 and galaxy 9: 5 + +In this example, after expanding the universe, the sum of the shortest path between all 36 pairs of galaxies is 374. + +Expand the universe, then find the length of the shortest path between every pair of galaxies. What is the sum of these lengths? + +Your puzzle answer was 9274989. + +The first half of this puzzle is complete! It provides one gold star: * + +--- Part Two --- + +The galaxies are much older (and thus much farther apart) than the researcher initially estimated. + +Now, instead of the expansion you did before, make each empty row or column one million times larger. That is, each empty row should be replaced with 1000000 empty rows, and each empty column should be replaced with 1000000 empty columns. + +(In the example above, if each empty row or column were merely 10 times larger, the sum of the shortest paths between every pair of galaxies would be 1030. If each empty row or column were merely 100 times larger, the sum of the shortest paths between every pair of galaxies +would be 8410. However, your universe will need to expand far beyond these values.) + +Starting with the same initial image, expand the universe according to these new rules, then find the length of the shortest path between every pair of galaxies. What is the sum of these lengths? + +Answer: + +Although it hasn't changed, you can still get your puzzle input. + +You can also [Shareon Twitter Mastodon] this puzzle. + diff --git a/src/day11/solve.c b/src/day11/solve.c new file mode 100644 index 0000000..0c308d8 --- /dev/null +++ b/src/day11/solve.c @@ -0,0 +1,100 @@ +/* + * Author: Michael Adler + * + * Copyright: 2023 Michael Adler + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "solve.h" +#include "aoc/all.h" + +#define MAX_POINTS 1024 + +void solve(const char *buf, size_t buf_size, Solution *result) { + i64 part1 = 0, part2 = 0; + size_t pos = 0; + + Point2D point[MAX_POINTS]; + i32 point_count = 0, empty_row_count = 0, empty_col_count = 0, rows = 0, cols = 0; + i32 empty_row[MAX_POINTS], empty_col[MAX_POINTS]; + + { // parser + i32 x = 0, y = 0; + bool is_empty_row = true; + while (pos < buf_size) { + char c = buf[pos++]; + if (c == '\n') { + if (is_empty_row) { empty_row[empty_row_count++] = y; } + is_empty_row = true; + cols = x; + x = 0; + y++; + continue; + } + if (c == '#') { + is_empty_row = false; + Point2D p = {.x = x, .y = y}; + point[point_count++] = p; + } + x++; + } + rows = y; + } + + for (i32 x = 0; x < cols; x++) { + bool is_empty = true; + for (i32 y = 0; y < rows; y++) { + char c = INDEX(buf, x, y, cols + 1); + if (c != '.') { + is_empty = false; + break; + } + } + if (is_empty) { empty_col[empty_col_count++] = x; } + } + + for (int i = 0; i < point_count; i++) { + Point2D a = point[i]; + for (int j = i + 1; j < point_count; j++) { + Point2D b = point[j]; + i32 dist = Point2D_manhattan(a, b); + + { // account for empty cols + i32 count = 0; + i32 lower_x = MIN(a.x, b.x); + i32 upper_x = MAX(a.x, b.x); + for (i32 k = 0; k < empty_col_count; k++) { + i32 c = empty_col[k]; + if (c > lower_x && c < upper_x) count++; + } + dist += count; + } + { // account for empty rows + i32 count = 0; + i32 lower_y = MIN(a.y, b.y); + i32 upper_y = MAX(a.y, b.y); + for (i32 k = 0; k < empty_row_count; k++) { + i32 r = empty_row[k]; + if (r > lower_y && r < upper_y) count++; + } + dist += count; + } + part1 += dist; + } + } + + aoc_itoa(part1, result->part1, 10); + aoc_itoa(part2, result->part2, 10); +} + +int solve_input(const char *fname, Solution *result) { + char buf[1 << 15]; + int n = aoc_io_read_input(fname, buf, sizeof(buf)); + if (n <= 0) { + fprintf(stderr, "Failed to read %s\n", fname); + return -1; + } + solve(buf, n, result); + return 0; +} diff --git a/src/day11/solve_test.c b/src/day11/solve_test.c new file mode 100644 index 0000000..494fe5e --- /dev/null +++ b/src/day11/solve_test.c @@ -0,0 +1,40 @@ +/* + * Author: Michael Adler + * + * Copyright: 2023 Michael Adler + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define CTEST_MAIN + +#include "ctest.h" +#include "solve.h" + +CTEST(day11, example) { + const char *buf = "...#......\n\ +.......#..\n\ +#.........\n\ +..........\n\ +......#...\n\ +.#........\n\ +.........#\n\ +..........\n\ +.......#..\n\ +#...#.....\n"; + Solution solution; + solve(buf, strlen(buf), &solution); + ASSERT_STR("374", solution.part1); + // ASSERT_STR("0", solution.part2); +} + +#ifdef HAVE_INPUTS +CTEST_SKIP(day11, real) { + Solution solution; + solve_input("input/" DAY ".txt", &solution); + ASSERT_STR("9274989", solution.part1); + // ASSERT_STR("0", solution.part2); +} +#endif + +int main(int argc, const char *argv[]) { return ctest_main(argc, argv); }