diff --git a/examples/aoc2023/day12/part1.jou b/examples/aoc2023/day12/part1.jou new file mode 100644 index 00000000..e3c326c1 --- /dev/null +++ b/examples/aoc2023/day12/part1.jou @@ -0,0 +1,95 @@ +import "stdlib/str.jou" +import "stdlib/io.jou" +import "stdlib/mem.jou" + + +# arrays are empty string terminated +def append_all(dest: byte[100]*, src: byte[100]*) -> byte[100]*: + while (*src)[0] != '\0': + *dest++ = *src++ + *dest = "" + return dest + + +def count(arr: byte[100]*) -> int: + n = 0 + while arr[n][0] != '\0': + n++ + return n + + +# returns NULL terminated array +def substitute_questionmarks_in_all_ways(questional_string: byte*) -> byte[100]*: + assert strlen(questional_string) == strspn(questional_string, "?.#") + assert 0 < strlen(questional_string) and strlen(questional_string) < 100 + + for i = 0; questional_string[i] != '\0'; i++: + if questional_string[i] == '?': + temp: byte[100] + strcpy(temp, questional_string) + + temp[i] = '#' + with_hashtag = substitute_questionmarks_in_all_ways(temp) + temp[i] = '.' + without_hashtag = substitute_questionmarks_in_all_ways(temp) + + # Concatenate lists + result: byte[100]* = malloc(sizeof(result[0]) * (count(with_hashtag) + count(without_hashtag) + 1)) + assert result != NULL + append_all(append_all(result, with_hashtag), without_hashtag) + free(with_hashtag) + free(without_hashtag) + return result + + result = malloc(sizeof(result[0]) * 2) + strcpy(result[0], questional_string) + result[1] = "" + return result + + +# "##..#..###" --> "2,1,3" +def generate_numbers(non_questional_string: byte*) -> byte[100]: + assert strlen(non_questional_string) < 50 + result: byte[100] = "" + + prev = '.' + for p = non_questional_string; *p != '\0'; p++: + assert *p == '#' or *p == '.' + if prev == '.' and *p == '#': + n = 1 + elif prev == '#' and *p == '#': + n++ + elif prev == '#' and *p == '.': + sprintf(&result[strlen(result)], "%d,", n) + prev = *p + + if prev == '#': + sprintf(&result[strlen(result)], "%d,", n) + + if result[0] != '\0': + # remove last comma + result[strlen(result) - 1] = '\0' + return result + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + questional_string: byte[100] + non_questional_string: byte[100] + + result = 0 + + while fscanf(f, "%99s %99s\n", questional_string, non_questional_string) == 2: + #puts(questional_string) + possibilities = substitute_questionmarks_in_all_ways(questional_string) + for i = 0; possibilities[i][0] != '\0'; i++: + s = generate_numbers(possibilities[i]) + if strcmp(s, non_questional_string) == 0: + result++ + free(possibilities) + + fclose(f) + printf("%d\n", result) # Output: 21 + return 0 diff --git a/examples/aoc2023/day12/part2.jou b/examples/aoc2023/day12/part2.jou new file mode 100644 index 00000000..2c5b6b0a --- /dev/null +++ b/examples/aoc2023/day12/part2.jou @@ -0,0 +1,166 @@ +import "stdlib/str.jou" +import "stdlib/io.jou" +import "stdlib/mem.jou" + + +class State: + questional_string: byte[200] + numbers: int* # -1 terminated, not owned + repeat_count: long # how many equal states does this object represent, not enough mem without this + + def print(self) -> None: + printf("State{qs=\"%s\", nums=[", self->questional_string) + for i = 0; self->numbers[i] != -1; i++: + if i > 0: + printf(",") + printf("%d", self->numbers[i]) + printf("], repeat=%lld}\n", self->repeat_count) + fflush(stdout) + + def simplify(self) -> None: + s = &self->questional_string[0] + nums = self->numbers + + while s[0] == '.': + s++ + + while True: + n1 = strspn(s, "#") + n2 = strspn(s, "#?") + if n1 == n2 and n1 == nums[0]: + # Skip number and hashtag + nums++ + s = &s[n1] + while s[0] == '.': + s++ + else: + break + + memcpy(&self->questional_string, s, strlen(s) + 1) + self->numbers = nums + + def equals(self, other: State*) -> bool: + return ( + self->numbers == other->numbers + and strcmp(self->questional_string, other->questional_string) == 0 + ) + + # Returns whether a string can be filled according to consecutive_counts + # "##.#?.#", [3, 2, 1] --> True + # + # Does not detect some impossible cases until remaining question marks are substituted. + def is_possible(self) -> bool: + if self->questional_string[0] == '\0': + # ran out of string + # return false, if we still have numbers + return self->numbers[0] == -1 + + if self->numbers[0] == -1: + # ran out of numbers + # return false, if we still must put '#' in string + return strstr(self->questional_string, "#") == NULL + + # This state is impossible if: + # * it starts with a section of hashtags + # * there is wrong number of hashtags + # * there are no question marks after hashtags + # + # There are other cases we could say this state is impossible, but we don't have to. + n1 = strspn(self->questional_string, "#") + n2 = strspn(self->questional_string, "#?") + if n1 == n2 and n1 > 0 and self->numbers[0] != n1: + return False + return True + + # Substitutes first question mark with '#' and '.' + def substitute_both_ways(self) -> State[2]: + assert strchr(self->questional_string, '?') != NULL + result = [*self, *self] + *strchr(result[0].questional_string, '?') = '#' + *strchr(result[1].questional_string, '?') = '.' + return result + + +def split_ints_by_commas(s: byte*) -> int[200]: + assert strlen(s) < 200 + result: int[200] + p = &result[0] + + while True: + *p++ = atoi(s) + s = strchr(s, ',') + if s == NULL: + *p = -1 + return result + s++ + + +def repeat_5x(s: byte*, sep: byte) -> None: + n = strlen(s) + strcpy(&s[n+1], s) + strcpy(&s[2*(n+1)], s) + strcpy(&s[3*(n+1)], s) + strcpy(&s[4*(n+1)], s) + s[n] = sep + s[2*n+1] = sep + s[3*n+2] = sep + s[4*n+3] = sep + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + questional_string: byte[200] + number_string: byte[200] + + result = 0L + + while fscanf(f, "%39s %39s\n", questional_string, number_string) == 2: + repeat_5x(questional_string, '?') + repeat_5x(number_string, ',') + + numbers = split_ints_by_commas(number_string) + + n_question_marks = 0 + for i = 0; questional_string[i] != '\0'; i++: + if questional_string[i] == '?': + n_question_marks++ + + states: State* = malloc(sizeof(states[0])) + states[0] = State{questional_string=questional_string, numbers=numbers, repeat_count=1} + nstates = 1 + + while n_question_marks --> 0: + states = realloc(states, sizeof(states[0]) * (2*nstates)) + assert states != NULL + + for i = nstates-1; i >= 0; i--: + both_ways = states[i].substitute_both_ways() + states[2*i] = both_ways[0] + states[2*i+1] = both_ways[1] + nstates *= 2 + + # Simplify states to merge as much as possible + for i = 0; i < nstates; i++: + states[i].simplify() + + # Merge duplicate states + for i = 0; i < nstates; i++: + for k = nstates-1; k > i; k--: + if states[i].equals(&states[k]): + states[i].repeat_count += states[k].repeat_count + states[k] = states[--nstates] + + # Delete impossible states + for i = nstates-1; i >= 0; i--: + if not states[i].is_possible(): + states[i] = states[--nstates] + + for i = 0; i < nstates; i++: + result += states[i].repeat_count + free(states) + + fclose(f) + printf("%lld\n", result) # Output: 525152 + return 0 diff --git a/examples/aoc2023/day12/sampleinput.txt b/examples/aoc2023/day12/sampleinput.txt new file mode 100644 index 00000000..e9259357 --- /dev/null +++ b/examples/aoc2023/day12/sampleinput.txt @@ -0,0 +1,6 @@ +???.### 1,1,3 +.??..??...?##. 1,1,3 +?#?#?#?#?#?#?#? 1,3,1,6 +????.#...#... 4,1,1 +????.######..#####. 1,6,5 +?###???????? 3,2,1 diff --git a/examples/aoc2023/day13/part1.jou b/examples/aoc2023/day13/part1.jou new file mode 100644 index 00000000..1b495f2a --- /dev/null +++ b/examples/aoc2023/day13/part1.jou @@ -0,0 +1,104 @@ +import "stdlib/io.jou" +import "stdlib/str.jou" +import "stdlib/mem.jou" +import "stdlib/ascii.jou" + + +class Grid: + 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_h_reflection(g: Grid*) -> int: + for reflect = 1; reflect < g->width; reflect++: + left = reflect-1 + right = reflect + matches = True + while left >= 0 and right < g->width and matches: + for y = 0; y < g->height; y++: + if g->get([left, y]) != g->get([right, y]): + matches = False + break + left-- + right++ + if matches: + return reflect + return -1 + + +def find_v_reflection(g: Grid*) -> int: + for reflect = 1; reflect < g->height; reflect++: + top = reflect-1 + bottom = reflect + matches = True + while top >= 0 and bottom < g->height and matches: + for x = 0; x < g->width; x++: + if g->get([x, top]) != g->get([x, bottom]): + matches = False + break + top-- + bottom++ + if matches: + return reflect + return -1 + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + line: byte[200] + max_size = 10000 + + grid = Grid{data = malloc(max_size)} + result = 0 + + while True: + grid.width = 0 + grid.height = 0 + grid.data[0] = '\0' + + while fgets(line, sizeof(line) as int, f) != NULL: + trim_ascii_whitespace(line) + if line[0] == '\0': + break + + if grid.height == 0: # set width on first round + grid.width = strlen(line) as int + assert grid.width == strlen(line) + grid.height++ + + assert grid.width * grid.height < max_size + strcat(grid.data, line) + strcat(grid.data, "\n") + + if grid.height == 0: + # end of input + break + + h = find_h_reflection(&grid) + v = find_v_reflection(&grid) + assert h == -1 or v == -1 # does not have both + if h != -1: + result += h + elif v != -1: + result += 100*v + else: + assert False + + free(grid.data) + fclose(f) + printf("%d\n", result) # Output: 405 + return 0 diff --git a/examples/aoc2023/day13/part2.jou b/examples/aoc2023/day13/part2.jou new file mode 100644 index 00000000..6ec1f8dc --- /dev/null +++ b/examples/aoc2023/day13/part2.jou @@ -0,0 +1,108 @@ +import "stdlib/io.jou" +import "stdlib/str.jou" +import "stdlib/mem.jou" +import "stdlib/ascii.jou" + + +class Grid: + 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_h_reflection(g: Grid*, mismatch_count: int) -> int: + for reflect = 1; reflect < g->width; reflect++: + left = reflect-1 + right = reflect + n = 0 + + while left >= 0 and right < g->width: + for y = 0; y < g->height; y++: + if g->get([left, y]) != g->get([right, y]): + n++ + left-- + right++ + + if n == mismatch_count: + return reflect + + return -1 + + +def find_v_reflection(g: Grid*, mismatch_count: int) -> int: + for reflect = 1; reflect < g->height; reflect++: + top = reflect-1 + bottom = reflect + n = 0 + + while top >= 0 and bottom < g->height: + for x = 0; x < g->width; x++: + if g->get([x, top]) != g->get([x, bottom]): + n++ + top-- + bottom++ + + if n == mismatch_count: + return reflect + + return -1 + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + line: byte[200] + max_size = 10000 + + grid = Grid{data = malloc(max_size)} + result = 0 + + while True: + grid.width = 0 + grid.height = 0 + grid.data[0] = '\0' + + while fgets(line, sizeof(line) as int, f) != NULL: + trim_ascii_whitespace(line) + if line[0] == '\0': + break + + if grid.height == 0: # set width on first round + grid.width = strlen(line) as int + assert grid.width == strlen(line) + grid.height++ + + assert grid.width * grid.height < max_size + strcat(grid.data, line) + strcat(grid.data, "\n") + + if grid.height == 0: + # end of input + break + + h = find_h_reflection(&grid, 1) + v = find_v_reflection(&grid, 1) + assert h == -1 or v == -1 # does not have both + if h != -1: + result += h + elif v != -1: + result += 100*v + else: + assert False + + free(grid.data) + fclose(f) + printf("%d\n", result) # Output: 400 + return 0 diff --git a/examples/aoc2023/day13/sampleinput.txt b/examples/aoc2023/day13/sampleinput.txt new file mode 100644 index 00000000..3b6b5ccd --- /dev/null +++ b/examples/aoc2023/day13/sampleinput.txt @@ -0,0 +1,15 @@ +#.##..##. +..#.##.#. +##......# +##......# +..#.##.#. +..##..##. +#.#.##.#. + +#...##..# +#....#..# +..##..### +#####.##. +#####.##. +..##..### +#....#..# diff --git a/examples/aoc2023/day14/part1.jou b/examples/aoc2023/day14/part1.jou new file mode 100644 index 00000000..7373bcdd --- /dev/null +++ b/examples/aoc2023/day14/part1.jou @@ -0,0 +1,81 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "stdlib/str.jou" +import "stdlib/ascii.jou" + + +class Grid: + 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 set(self, point: int[2], value: byte) -> None: + assert self->is_in_bounds(point) + x = point[0] + y = point[1] + self->data[(self->width + 1)*y + x] = value + + +def roll_north(grid: Grid*) -> None: + for ysrc = 1; ysrc < grid->height; ysrc++: + for x = 0; x < grid->width; x++: + if grid->get([x, ysrc]) != 'O' or grid->get([x, ysrc-1]) != '.': + continue + + ydest = ysrc - 1 + while ydest > 0 and grid->get([x, ydest-1]) == '.': + ydest-- + + grid->set([x, ysrc], '.') + grid->set([x, ydest], 'O') + + +def calculate_load(g: Grid*) -> int: + result = 0 + for y = 0; y < g->height; y++: + load_per_O = g->height - y + for x = 0; x < g->width; x++: + if g->get([x, y]) == 'O': + result += load_per_O + return result + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + line: byte[200] + max_size = 100000 + + grid = Grid{data = malloc(max_size)} + grid.data[0] = '\0' + + while fgets(line, sizeof(line) as int, f) != NULL: + trim_ascii_whitespace(line) + if grid.height == 0: # set width on first round + grid.width = strlen(line) as int + assert grid.width == strlen(line) + grid.height++ + + assert grid.width * grid.height < max_size + strcat(grid.data, line) + strcat(grid.data, "\n") + + fclose(f) + + roll_north(&grid) + printf("%d\n", calculate_load(&grid)) # Output: 136 + + free(grid.data) + return 0 diff --git a/examples/aoc2023/day14/part2.jou b/examples/aoc2023/day14/part2.jou new file mode 100644 index 00000000..70ef0a39 --- /dev/null +++ b/examples/aoc2023/day14/part2.jou @@ -0,0 +1,147 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "stdlib/str.jou" +import "stdlib/ascii.jou" +import "stdlib/math.jou" + + +class Grid: + 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 set(self, point: int[2], value: byte) -> None: + assert self->is_in_bounds(point) + x = point[0] + y = point[1] + self->data[(self->width + 1)*y + x] = value + + def swap(self, a: int[2], b: int[2]) -> None: + old_a = self->get(a) + self->set(a, self->get(b)) + self->set(b, old_a) + + def copy(self) -> Grid: + return Grid{width = self->width, height = self->height, data = strdup(self->data)} + + +def can_roll(grid: Grid*, rock: int[2], dir: int[2], n: int) -> bool: + new_rock_pos = [rock[0] + n*dir[0], rock[1] + n*dir[1]] + return ( + grid->get(rock) == 'O' + and grid->is_in_bounds(new_rock_pos) + and grid->get(new_rock_pos) == '.' + ) + + +def roll_one_round_rock(grid: Grid*, rock: int[2], dir: int[2]) -> None: + if can_roll(grid, rock, dir, 1): + n = 1 + while can_roll(grid, rock, dir, n+1): + n++ + grid->swap(rock, [rock[0] + n*dir[0], rock[1] + n*dir[1]]) + + +def roll(grid: Grid*, dir: int[2]) -> None: + assert (dir[0] == 0 and abs(dir[1]) == 1) or (dir[1] == 0 and abs(dir[0]) == 1) + + # Traverse grid opposite to the given direction, so that rocks won't block each other's rolling. + x_backwards = (dir[0] == 1) + y_backwards = (dir[1] == 1) + + if x_backwards: + x0 = grid->width - 1 + dx = -1 + else: + x0 = 0 + dx = 1 + + if y_backwards: + y0 = grid->height - 1 + dy = -1 + else: + y0 = 0 + dy = 1 + + for y = y0; 0 <= y and y < grid->height; y += dy: + for x = x0; 0 <= x and x < grid->width; x += dx: + roll_one_round_rock(grid, [x, y], dir) + + +def calculate_load(g: Grid*) -> int: + result = 0 + for y = 0; y < g->height; y++: + load_per_O = g->height - y + for x = 0; x < g->width; x++: + if g->get([x, y]) == 'O': + result += load_per_O + return result + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + line: byte[200] + max_size = 100000 + + grid = Grid{data = malloc(max_size)} + grid.data[0] = '\0' + + while fgets(line, sizeof(line) as int, f) != NULL: + trim_ascii_whitespace(line) + if grid.height == 0: # set width on first round + grid.width = strlen(line) as int + assert grid.width == strlen(line) + grid.height++ + + assert grid.width * grid.height < max_size + strcat(grid.data, line) + strcat(grid.data, "\n") + + fclose(f) + + previous_states: Grid* = NULL + n = 0 + + # Eventually the states will repeat in a cyclic way. + cycle_start = -1 + cycle_length = -1 + while cycle_start == -1 and cycle_length == -1: + previous_states = realloc(previous_states, sizeof(previous_states[0]) * (n+1)) + previous_states[n++] = grid + + grid = grid.copy() + roll(&grid, [0, -1]) + roll(&grid, [-1, 0]) + roll(&grid, [0, 1]) + roll(&grid, [1, 0]) + + for i = 0; i < n; i++: + if strcmp(previous_states[i].data, grid.data) == 0: + # State i and state n are the same. + # We haven't added state n to the list, but we don't have to. + cycle_start = i + cycle_length = n-i + break + + i = cycle_start + ((1000000000 - cycle_start) % cycle_length) + printf("%d\n", calculate_load(&previous_states[i])) # Output: 64 + + free(grid.data) + for i = 0; i < n; i++: + free(previous_states[i].data) + free(previous_states) + + return 0 diff --git a/examples/aoc2023/day14/sampleinput.txt b/examples/aoc2023/day14/sampleinput.txt new file mode 100644 index 00000000..5a24dce3 --- /dev/null +++ b/examples/aoc2023/day14/sampleinput.txt @@ -0,0 +1,10 @@ +O....#.... +O.OO#....# +.....##... +OO.#O....O +.O.....O#. +O.#..O.#.# +..O..#O..O +.......O.. +#....###.. +#OO..#....