diff --git a/examples/aoc2024/day23/part1.jou b/examples/aoc2024/day23/part1.jou index f80e7cc9..86296be0 100644 --- a/examples/aoc2024/day23/part1.jou +++ b/examples/aoc2024/day23/part1.jou @@ -5,7 +5,6 @@ import "stdlib/io.jou" class Computer: name: byte[3] - group_id: int # computers connected together have same group id connections: void*[100] # TODO: should be Computer*[100], can't be due to compiler bug def connect(self, other: Computer*) -> None: @@ -34,23 +33,12 @@ def find_or_create_computer(computers: Computer*, ncomputers: int*, name: byte*) c = &computers[*ncomputers] assert strlen(name) == 2 strcpy(c->name, name) - c->group_id = *ncomputers # unique initially memset(c->connections, 0, sizeof(c->connections)) ++*ncomputers return c -def merge_groups(computers: Computer*, ncomputers: int, id1: int, id2: int) -> None: - printf("Merge %d,%d\n", id1, id2) - if id1 == id2: - return - - for i = 0; i < ncomputers; i++: - if computers[i].group_id == id1: - computers[i].group_id = id2 - - def main() -> int: computers: Computer* = malloc(sizeof(computers[0]) * 5000) assert computers != NULL diff --git a/examples/aoc2024/day23/part2.jou b/examples/aoc2024/day23/part2.jou new file mode 100644 index 00000000..391906e4 --- /dev/null +++ b/examples/aoc2024/day23/part2.jou @@ -0,0 +1,252 @@ +import "stdlib/str.jou" +import "stdlib/mem.jou" +import "stdlib/io.jou" + + +def add_computer_if_not_exist(names: byte[3]*, ncomputers: int*, name: byte*) -> None: + for i = 0; i < *ncomputers; i++: + if strcmp(names[i], name) == 0: + return + + assert strlen(name) == 2 + strcpy(names[(*ncomputers)++], name) + + +def find_computer(names: byte[3]*, ncomputers: int, name: byte*) -> int: + for i = 0; i < ncomputers; i++: + if strcmp(names[i], name) == 0: + return i + assert False + + +# If conn_matrix[i][j] is True, then computers i and j are connected +def create_conn_matrix(ncomputers: int) -> bool**: + conn_matrix: bool** = malloc(sizeof(conn_matrix[0]) * ncomputers) + assert conn_matrix != NULL + + for i = 0; i < ncomputers; i++: + conn_matrix[i] = calloc(sizeof(conn_matrix[i][0]), ncomputers) + assert conn_matrix[i] != NULL + + return conn_matrix + + +def sort_names(names: byte[3]*, len: int) -> None: + # very dumb algorithm + while True: + did_something = False + for i = 1; i < len; i++: + if strcmp(names[i-1], names[i]) > 0: + memswap(&names[i-1], &names[i], sizeof(names[0])) + did_something = True + if not did_something: + break + + +def sort_group(group: int*) -> None: + # very dumb algorithm + while True: + did_something = False + for i = 1; group[i] != -1; i++: + if group[i-1] > group[i]: + memswap(&group[i-1], &group[i], sizeof(group[0])) + did_something = True + if not did_something: + break + + +def group_contains(group: int*, value: int) -> bool: + for i = 0; group[i] != -1; i++: + if group[i] == value: + return True + return False + + +def groups_are_equal(g1: int*, g2: int*) -> bool: + for i = 0; g1[i] != -1 or g2[i] != -1; i++: + if g1[i] != g2[i]: + return False + return True + + +# Merges two groups of size n if: +# - they have n-1 same elements in common +# - the two non-common elements (one from each group) are connected +# +# Assumes groups are sorted. +def merge_two_groups(group1: int*, group2: int*, conn_matrix: bool**) -> int*: + n = 0 + while group1[n] != -1 and group2[n] != -1: + n++ + assert group1[n] == -1 + assert group2[n] == -1 + + for i = 1; i < n; i++: + assert group1[i-1] < group1[i] + assert group2[i-1] < group2[i] + + # Find the elements that is only in group 1 or 2. If there are multiple, don't merge. + p1 = group1 + p2 = group2 + only1 = -1 + only2 = -1 + + while *p1 != -1 or *p2 != -1: + if *p2 == -1 or (*p1 != -1 and *p2 != -1 and *p1 < *p2): + # Group 1 left behind, it has element which is not in group 2 + if only1 != -1: + # Not the first one, refuse to merge + return NULL + only1 = *p1++ + elif *p1 == -1 or (*p1 != -1 and *p2 != -1 and *p1 > *p2): + # Group 2 element missing from group 1 + if only2 != -1: + # Not the first one, refuse to merge + return NULL + only2 = *p2++ + else: + # Skip common element + assert *p1 != -1 + assert *p2 != -1 + assert *p1 == *p2 + p1++ + p2++ + + assert p1 == &group1[n] + assert p2 == &group2[n] + + # They shouldn't have everything in common, then they would be the same group + assert only1 != -1 + assert only2 != -1 + + if not conn_matrix[only1][only2]: + return NULL + + result: int* = malloc(sizeof(result[0]) * (n+2)) + assert result != NULL + memcpy(result, group1, sizeof(result[0]) * n) + result[n] = only2 + result[n+1] = -1 + return result + + +# verbose parameter is useful when you're bored and you run this on the real input. +# It is very slow... :) +def merge_all_mergeable_pairs(conn_matrix: bool**, old_groups: int**) -> int**: + n = 0 + while old_groups[n] != NULL: + n++ + printf("Merging %d groups together", n) + fflush(stdout) + + result: int** = malloc(sizeof(result[0])) + assert result != NULL + result_len = 0 + result_alloc = 1 + + for g1 = old_groups; *g1 != NULL; g1++: + for g2 = &g1[1]; *g2 != NULL; g2++: + m = merge_two_groups(*g1, *g2, conn_matrix) + if m == NULL: + continue + + sort_group(m) + + already_exists = False + for i = 0; i < result_len; i++: + if groups_are_equal(result[i], m): + already_exists = True + break + + if already_exists: + free(m) + else: + if result_len == result_alloc: + result_alloc *= 2 + result = realloc(result, sizeof(result[0]) * (result_alloc + 1)) + assert result != NULL + + result[result_len++] = m + + # Print a dot every 100 successful merges. + # Helpful with actual input because it is really slow :) + if result_len % 100 == 0: + printf(".") + fflush(stdout) + + printf("\n") + fflush(stdout) + + result[result_len] = NULL + return result + + +def main() -> int: + names: byte[3]* = malloc(sizeof(names[0]) * 5000) + assert names != NULL + ncomputers = 0 + + f = fopen("input", "r") + assert f != NULL + + name1: byte[3] + name2: byte[3] + while fscanf(f, "%2s-%2s\n", name1, name2) == 2: + assert ncomputers < 5000 + add_computer_if_not_exist(names, &ncomputers, name1) + assert ncomputers < 5000 + add_computer_if_not_exist(names, &ncomputers, name2) + + sort_names(names, ncomputers) + + rewind(f) + conn_matrix = create_conn_matrix(ncomputers) + while fscanf(f, "%2s-%2s\n", name1, name2) == 2: + i1 = find_computer(names, ncomputers, name1) + i2 = find_computer(names, ncomputers, name2) + conn_matrix[i1][i2] = True + conn_matrix[i2][i1] = True + + fclose(f) + + # Create one group for each computer. + groups: int** = malloc(sizeof(groups[0]) * (ncomputers + 1)) + assert groups != NULL + for i = 0; i < ncomputers; i++: + groups[i] = malloc(sizeof(groups[i][0]) * (ncomputers + 1)) + groups[i][0] = i + groups[i][1] = -1 + groups[ncomputers] = NULL + + # Merge until there are at least two groups. + while groups[0] != NULL and groups[1] != NULL: + # Output: Merging 16 groups together + # Output: Merging 32 groups together + # Output: Merging 12 groups together + groups2 = merge_all_mergeable_pairs(conn_matrix, groups) + + for i = 0; groups[i] != NULL; i++: + free(groups[i]) + free(groups) + groups = groups2 + + # There should be one group remaining + assert groups[0] != NULL + + # Output: co-de-ka-ta + for i = 0; groups[0][i] != -1; i++: + if i != 0: + printf("-") + printf("%s", names[groups[0][i]]) + printf("\n") + + free(groups[0]) + free(groups) + + for i = 0; i < ncomputers; i++: + free(conn_matrix[i]) + free(conn_matrix) + + free(names) + + return 0