From 0e585e1db9240095bd2621c5578e39cef0944e4c Mon Sep 17 00:00:00 2001 From: Akuli Date: Mon, 11 Dec 2023 20:00:00 +0200 Subject: [PATCH] Aoc days 9, 10, 11 (#446) --- .gitignore | 13 +- examples/aoc2023/day09/part1.jou | 48 +++++++ examples/aoc2023/day09/part2.jou | 62 +++++++++ examples/aoc2023/day09/sampleinput.txt | 3 + examples/aoc2023/day10/part1.jou | 103 +++++++++++++++ examples/aoc2023/day10/part2.jou | 166 ++++++++++++++++++++++++ examples/aoc2023/day10/sampleinput.txt | 5 + examples/aoc2023/day10/sampleinput2.txt | 9 ++ examples/aoc2023/day11/part1.jou | 86 ++++++++++++ examples/aoc2023/day11/part2.jou | 92 +++++++++++++ examples/aoc2023/day11/sampleinput.txt | 10 ++ 11 files changed, 591 insertions(+), 6 deletions(-) create mode 100644 examples/aoc2023/day09/part1.jou create mode 100644 examples/aoc2023/day09/part2.jou create mode 100644 examples/aoc2023/day09/sampleinput.txt create mode 100644 examples/aoc2023/day10/part1.jou create mode 100644 examples/aoc2023/day10/part2.jou create mode 100644 examples/aoc2023/day10/sampleinput.txt create mode 100644 examples/aoc2023/day10/sampleinput2.txt create mode 100644 examples/aoc2023/day11/part1.jou create mode 100644 examples/aoc2023/day11/part2.jou create mode 100644 examples/aoc2023/day11/sampleinput.txt diff --git a/.gitignore b/.gitignore index 3454a4b0..61a48c3d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,11 +25,12 @@ # ide stuff /.vscode/ -# Advent of Code input files https://adventofcode.com/ -# These are large text files, and each AoC user gets different input files. -# Tests use sampleinput.txt files, copied from problem descriptions +# Advent of Code files https://adventofcode.com/ +# Inputs are large text files, and each AoC user gets different input files. +# Tests use sampleinput.txt files, copied from problem descriptions. +# +# Creator of AoC tells people not to commit inputs to git. I also cannot +# commit problem descriptions to git because of copyright. +/examples/aoc2023/day*/input /examples/aoc2023/day*/input.txt - -# files created by https://github.com/scarvalhojr/aoc-cli -# not sure if I can commit these to git because copyright /examples/aoc2023/day*/puzzle.md diff --git a/examples/aoc2023/day09/part1.jou b/examples/aoc2023/day09/part1.jou new file mode 100644 index 00000000..d201dd41 --- /dev/null +++ b/examples/aoc2023/day09/part1.jou @@ -0,0 +1,48 @@ +import "stdlib/io.jou" +import "stdlib/ascii.jou" +import "stdlib/str.jou" +import "stdlib/mem.jou" + + +def predict_next(nums: long*, len: int) -> long: + all_zero = True + for i = 0; i < len; i++: + if nums[i] != 0: + all_zero = False + break + + if all_zero: + return 0 + + diffs: long* = malloc(sizeof(diffs[0]) * len) + for i = 1; i < len; i++: + diffs[i-1] = nums[i]-nums[i-1] + + result = nums[len-1] + predict_next(diffs, len-1) + free(diffs) + return result + + +# return value is an array terminated by nums_len=-1 +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + line: byte[1000] + result: long = 0 + + while fgets(line, sizeof(line) as int, f) != NULL: + nums: long[100] + nnums = 0 + parts = split_by_ascii_whitespace(line) + for p = parts; *p != NULL; p++: + assert nnums < sizeof(nums)/sizeof(nums[0]) + nums[nnums++] = atoll(*p) + free(parts) + + result += predict_next(nums, nnums) + + fclose(f) + + printf("%lld\n", result) # Output: 114 + return 0 diff --git a/examples/aoc2023/day09/part2.jou b/examples/aoc2023/day09/part2.jou new file mode 100644 index 00000000..f3521c2c --- /dev/null +++ b/examples/aoc2023/day09/part2.jou @@ -0,0 +1,62 @@ +import "stdlib/io.jou" +import "stdlib/ascii.jou" +import "stdlib/str.jou" +import "stdlib/mem.jou" + + +def predict_next(nums: long*, len: int) -> long: + all_zero = True + for i = 0; i < len; i++: + if nums[i] != 0: + all_zero = False + break + + if all_zero: + return 0 + + diffs: long* = malloc(sizeof(diffs[0]) * len) + for i = 1; i < len; i++: + diffs[i-1] = nums[i]-nums[i-1] + + result = nums[len-1] + predict_next(diffs, len-1) + free(diffs) + return result + + +def swap(a: long*, b: long*) -> None: + tmp = *a + *a = *b + *b = tmp + + +def reverse(nums: long*, len: int) -> None: + p = nums + q = &nums[len-1] + while p < q: + swap(p++, q--) + + +# return value is an array terminated by nums_len=-1 +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + line: byte[1000] + result: long = 0 + + while fgets(line, sizeof(line) as int, f) != NULL: + nums: long[100] + nnums = 0 + parts = split_by_ascii_whitespace(line) + for p = parts; *p != NULL; p++: + assert nnums < sizeof(nums)/sizeof(nums[0]) + nums[nnums++] = atoll(*p) + free(parts) + + reverse(nums, nnums) + result += predict_next(nums, nnums) + + fclose(f) + + printf("%lld\n", result) # Output: 2 + return 0 diff --git a/examples/aoc2023/day09/sampleinput.txt b/examples/aoc2023/day09/sampleinput.txt new file mode 100644 index 00000000..539a763f --- /dev/null +++ b/examples/aoc2023/day09/sampleinput.txt @@ -0,0 +1,3 @@ +0 3 6 9 12 15 +1 3 6 10 15 21 +10 13 16 21 30 45 diff --git a/examples/aoc2023/day10/part1.jou b/examples/aoc2023/day10/part1.jou new file mode 100644 index 00000000..5cf3106c --- /dev/null +++ b/examples/aoc2023/day10/part1.jou @@ -0,0 +1,103 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "stdlib/str.jou" + + +class Input: + width: int + height: int + data: byte* + + def is_in_bounds(self, point: int[2]) -> bool: + x = point[0] + y = point[1] + return 0 <= x and x < self->width and 0 <= y and y < self->height + + def get(self, point: int[2]) -> byte: + assert self->is_in_bounds(point) + x = point[0] + y = point[1] + return self->data[(self->width + 1)*y + x] + + +def find_S(input: Input*) -> int[2]: + for x = 0; x < input->width; x++: + for y = 0; y < input->height; y++: + if input->get([x, y]) == 'S': + return [x, y] + + assert False + + +def get_dirs(c: byte) -> int[2][2]: + if c == '7': + return [[-1, 0], [0, 1]] + if c == 'J': + return [[-1, 0], [0, -1]] + if c == 'L': + return [[1, 0], [0, -1]] + if c == 'F': + return [[1, 0], [0, 1]] + if c == '-': + return [[-1, 0], [1, 0]] + if c == '|': + return [[0, -1], [0, 1]] + + assert False + + +def eq(a: int[2], b: int[2]) -> bool: + return a[0] == b[0] and a[1] == b[1] + + +def find_initial_direction(input: Input*, S: int[2]) -> int[2]: + directions = [[0,1], [0,-1], [1,0], [-1,0]] + for d = &directions[0]; d < &directions[4]; d++: + S_to_neighbor = *d + neighbor = [S[0] + S_to_neighbor[0], S[1] + S_to_neighbor[1]] + + if input->is_in_bounds(neighbor) and input->get(neighbor) != '.': + dirs = get_dirs(input->get(neighbor)) + if eq(dirs[0], S_to_neighbor) or eq(dirs[1], S_to_neighbor): + return S_to_neighbor + + assert False + + +def main() -> int: + max_len = 100000 + input = Input{data = calloc(1, max_len+1)} + + f = fopen("sampleinput.txt", "r") + assert f != NULL + fread(input.data, 1, max_len, f) + fclose(f) + + input.width = strcspn(input.data, "\n") as int + input.height = (strlen(input.data) as int) / (input.width + 1) + + point = find_S(&input) + + # take first step away from S + dir = find_initial_direction(&input, point) + point[0] += dir[0] + point[1] += dir[1] + loop_length = 1 + + while input.get(point) != 'S': + came_from = [-dir[0], -dir[1]] + dirs = get_dirs(input.get(point)) + assert eq(came_from, dirs[0]) or eq(came_from, dirs[1]) + if eq(came_from, dirs[0]): + dir = dirs[1] + else: + dir = dirs[0] + point[0] += dir[0] + point[1] += dir[1] + loop_length++ + + assert loop_length % 2 == 0 + printf("%d\n", loop_length / 2) # Output: 4 + + free(input.data) + return 0 diff --git a/examples/aoc2023/day10/part2.jou b/examples/aoc2023/day10/part2.jou new file mode 100644 index 00000000..1319c025 --- /dev/null +++ b/examples/aoc2023/day10/part2.jou @@ -0,0 +1,166 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "stdlib/str.jou" +import "stdlib/math.jou" + + +class Input: + width: int + height: int + data: byte* + + def is_in_bounds(self, point: int[2]) -> bool: + x = point[0] + y = point[1] + return 0 <= x and x < self->width and 0 <= y and y < self->height + + def get(self, point: int[2]) -> byte: + assert self->is_in_bounds(point) + x = point[0] + y = point[1] + return self->data[(self->width + 1)*y + x] + + +def find_S(input: Input*) -> int[2]: + for x = 0; x < input->width; x++: + for y = 0; y < input->height; y++: + if input->get([x, y]) == 'S': + return [x, y] + + assert False + + +def get_dirs(c: byte) -> int[2][2]: + if c == '7': + return [[-1, 0], [0, 1]] + if c == 'J': + return [[-1, 0], [0, -1]] + if c == 'L': + return [[1, 0], [0, -1]] + if c == 'F': + return [[1, 0], [0, 1]] + if c == '-': + return [[-1, 0], [1, 0]] + if c == '|': + return [[0, -1], [0, 1]] + + assert False + + +def eq(a: int[2], b: int[2]) -> bool: + return a[0] == b[0] and a[1] == b[1] + + +def find_initial_direction(input: Input*, S: int[2]) -> int[2]: + directions = [[0,1], [0,-1], [1,0], [-1,0]] + for d = &directions[0]; d < &directions[4]; d++: + S_to_neighbor = *d + neighbor = [S[0] + S_to_neighbor[0], S[1] + S_to_neighbor[1]] + + if input->is_in_bounds(neighbor) and input->get(neighbor) != '.': + dirs = get_dirs(input->get(neighbor)) + if eq(dirs[0], S_to_neighbor) or eq(dirs[1], S_to_neighbor): + return S_to_neighbor + + assert False + + +def polygon_area(corners: int[2]*, ncorners: int) -> int: + # Any polygon area can be thought of as consisting of triangle areas. + # + # Consider a triangle with two adjacent sides as corners: + # + # O corners[i] + # |\ + # | \ + # | \ + # | \ + # | A O corners[i+1] + # | / + # | / + # | / + # |/ + # O + # + # (0,0) + # + # Its area is 1/2 |det(corners[i], corners[i+1])|, where det(a,b) + # denotes a 2x2 determinant with a and b as rows (or columns), and + # | ... | denotes the absolute value. Without the absolute value, + # the sign of a determinant is basically the orientation of the + # triangle. + # + # We can view a polygon's area as consisting of triangles like this. + # As we go around the polygon, the differently oriented triangles + # overlap with opposite orientations outside the polygon, but not + # inside the polygon. So + # + # polygon area = 1/2 |sum(det(corners[i], corners[i+1]))| + double_area = 0 + for i = 0; i < ncorners; i++: + a = corners[i][0] + b = corners[i][1] + c = corners[(i+1) % ncorners][0] + d = corners[(i+1) % ncorners][1] + double_area += a*d - b*c + return abs(double_area)/2 + + +def main() -> int: + max_len = 100000 + input = Input{data = calloc(1, max_len+1)} + + f = fopen("sampleinput2.txt", "r") + assert f != NULL + fread(input.data, 1, max_len, f) + fclose(f) + + input.width = strcspn(input.data, "\n") as int + input.height = (strlen(input.data) as int) / (input.width + 1) + + point = find_S(&input) + + loop: int[2]* = malloc(input.width * input.height * sizeof(loop[0])) + loop[0] = point + + # take first step away from S + dir = find_initial_direction(&input, point) + point[0] += dir[0] + point[1] += dir[1] + loop_length = 1 + + while input.get(point) != 'S': + came_from = [-dir[0], -dir[1]] + dirs = get_dirs(input.get(point)) + assert eq(came_from, dirs[0]) or eq(came_from, dirs[1]) + if eq(came_from, dirs[0]): + dir = dirs[1] + else: + dir = dirs[0] + loop[loop_length++] = point + point[0] += dir[0] + point[1] += dir[1] + + area_along_middle_of_path = polygon_area(loop, loop_length) + + # Suppose the path has: + # n 90 degree turns towards inside of loop [1/4] + # m 90 degree turns towards outside of loop [3/4] + # k straight lines [+1/2] + # + # In square brackets is how much bigger area_along_middle_of_path + # becomes than area inside loop. For example, each straight line + # contributes 1/2 extra. So: + # + # area_along_middle_of_path + # = area_inside_loop + 1/4 n + 3/4 m + 1/2 k + # = area_inside_loop + (n+m+k)/2 - (n-m)/4 + # = area_inside_loop + loop_length/2 - 1 + # + # Here n-m = 4 because the loop goes around a total of 360 degrees + # towards the inside. + printf("%d\n", area_along_middle_of_path + 1 - loop_length/2) # Output: 4 + + free(input.data) + free(loop) + return 0 diff --git a/examples/aoc2023/day10/sampleinput.txt b/examples/aoc2023/day10/sampleinput.txt new file mode 100644 index 00000000..7650925b --- /dev/null +++ b/examples/aoc2023/day10/sampleinput.txt @@ -0,0 +1,5 @@ +..... +.S-7. +.|.|. +.L-J. +..... diff --git a/examples/aoc2023/day10/sampleinput2.txt b/examples/aoc2023/day10/sampleinput2.txt new file mode 100644 index 00000000..bd9cdf53 --- /dev/null +++ b/examples/aoc2023/day10/sampleinput2.txt @@ -0,0 +1,9 @@ +........... +.S-------7. +.|F-----7|. +.||.....||. +.||.....||. +.|L-7.F-J|. +.|..|.|..|. +.L--J.L--J. +........... diff --git a/examples/aoc2023/day11/part1.jou b/examples/aoc2023/day11/part1.jou new file mode 100644 index 00000000..ea39cd6f --- /dev/null +++ b/examples/aoc2023/day11/part1.jou @@ -0,0 +1,86 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "stdlib/str.jou" +import "stdlib/math.jou" + + +class Input: + width: int + height: int + data: byte* + + def duplicate_blank_lines(self) -> None: + self->data = realloc(self->data, 2*strlen(self->data) + 1) + assert self->data != NULL + + y = 0 + while y < self->height: + if strspn(&self->data[(self->width + 1)*y], ".") == self->width: + # duplicate row + blank_start = &self->data[(self->width + 1)*y] + next_start = &self->data[(self->width + 1)*(y+1)] + memmove(next_start, blank_start, strlen(blank_start) + 1) + y += 2 + self->height++ + else: + y++ + + def transpose(self) -> None: + old_content = strdup(self->data) + old_width = self->width + old_height = self->height + self->width = old_height + self->height = old_width + + memset(self->data, '\n', strlen(self->data)) + for x = 0; x < self->width; x++: + for y = 0; y < self->height; y++: + self->data[(self->width + 1)*y + x] = old_content[(old_width + 1)*x + y] + + free(old_content) + + # array is terminated by [-1, -1] + def get_hashtag_coords(self) -> int[2]*: + result: int[2]* = malloc(sizeof(result[0]) * (self->width * self->height + 1)) + result_len = 0 + for y = 0; y < self->height; y++: + for x = 0; x < self->width; x++: + if self->data[(self->width + 1)*y + x] == '#': + result[result_len++] = [x, y] + + result[result_len] = [-1, -1] + return result + + +def manhattan_distance(a: int[2], b: int[2]) -> int: + return abs(a[0]-b[0]) + abs(a[1]-b[1]) + + +def main() -> int: + max_len = 100000 + input = Input{data = calloc(1, max_len+1)} + + f = fopen("sampleinput.txt", "r") + assert f != NULL + fread(input.data, 1, max_len, f) + fclose(f) + + input.width = strcspn(input.data, "\n") as int + input.height = (strlen(input.data) as int) / (input.width + 1) + + input.duplicate_blank_lines() + input.transpose() + input.duplicate_blank_lines() + input.transpose() + + coords = input.get_hashtag_coords() + + sum = 0 + for i = 0; coords[i][0] != -1; i++: + for k = 0; k < i; k++: + sum += manhattan_distance(coords[i], coords[k]) + printf("%d\n", sum) # Output: 374 + + free(input.data) + free(coords) + return 0 diff --git a/examples/aoc2023/day11/part2.jou b/examples/aoc2023/day11/part2.jou new file mode 100644 index 00000000..b05ace4b --- /dev/null +++ b/examples/aoc2023/day11/part2.jou @@ -0,0 +1,92 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "stdlib/str.jou" + + +class Input: + width: int + height: int + data: byte* + + # returned array is terminated by -1 + def find_blank_lines(self) -> int*: + result: int* = malloc(sizeof(result[0]) * (self->height + 1)) + result_len = 0 + + for y = 0; y < self->height; y++: + if strspn(&self->data[(self->width + 1)*y], ".") == self->width: + result[result_len++] = y + result[result_len] = -1 + return result + + def transpose(self) -> None: + old_content = strdup(self->data) + old_width = self->width + old_height = self->height + self->width = old_height + self->height = old_width + + memset(self->data, '\n', strlen(self->data)) + for x = 0; x < self->width; x++: + for y = 0; y < self->height; y++: + self->data[(self->width + 1)*y + x] = old_content[(old_width + 1)*x + y] + + free(old_content) + + # returned array is terminated by -1 + def get_hashtag_y_coords(self) -> int*: + result: int* = malloc(sizeof(result[0]) * (self->width * self->height + 1)) + result_len = 0 + for y = 0; y < self->height; y++: + for x = 0; x < self->width; x++: + if self->data[(self->width + 1)*y + x] == '#': + result[result_len++] = y + + result[result_len] = -1 + return result + + +def sum_vertical_distances(input: Input*) -> long: + hashtag_y_coords = input->get_hashtag_y_coords() + blank_lines = input->find_blank_lines() + + result = 0L + for end = hashtag_y_coords; *end != -1; end++: + for start = hashtag_y_coords; start < end; start++: + for y = *start; y < *end; y++: + y_is_blank = False + for p = blank_lines; *p != -1; p++: + if *p == y: + y_is_blank = True + break + if y_is_blank: + # Change to 1000000 for actual input + result += 100 + #result += 1000000 + else: + result += 1 + + free(hashtag_y_coords) + free(blank_lines) + return result + + +def main() -> int: + max_len = 100000 + input = Input{data = calloc(1, max_len+1)} + + f = fopen("sampleinput.txt", "r") + assert f != NULL + fread(input.data, 1, max_len, f) + fclose(f) + + input.width = strcspn(input.data, "\n") as int + input.height = (strlen(input.data) as int) / (input.width + 1) + + result = sum_vertical_distances(&input) + input.transpose() + result += sum_vertical_distances(&input) + printf("%lld\n", result) # Output: 8410 + + free(input.data) + return 0 diff --git a/examples/aoc2023/day11/sampleinput.txt b/examples/aoc2023/day11/sampleinput.txt new file mode 100644 index 00000000..986aad4a --- /dev/null +++ b/examples/aoc2023/day11/sampleinput.txt @@ -0,0 +1,10 @@ +...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#.....