Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aoc day 8 #432

Merged
merged 4 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions examples/aoc2023/day08/part1.jou
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import "stdlib/ascii.jou"
import "stdlib/mem.jou"
import "stdlib/io.jou"
import "stdlib/str.jou"


class Node:
name: byte[4]
left: byte[4]
right: byte[4]


class Input:
left_right_string: byte[1000]
nodes: Node[1000]
nnodes: int

def add_node(self, node: Node) -> void:
assert self->nnodes < sizeof(self->nodes)/sizeof(self->nodes[0])
self->nodes[self->nnodes++] = node

def find_node(self, name: byte*) -> Node*:
for i = 0; i < self->nnodes; i++:
if strcmp(self->nodes[i].name, name) == 0:
return &self->nodes[i]
assert False


def parse_input() -> Input*:
input: Input* = calloc(1, sizeof(*input))

f = fopen("sampleinput2.txt", "r")
assert f != NULL

assert fgets(input->left_right_string, sizeof(input->left_right_string) as int, f) != NULL
trim_ascii_whitespace(input->left_right_string)

assert fgetc(f) == '\n'

n: Node
while fscanf(f, "%3s = (%3s, %3s)\n", &n.name, &n.left, &n.right) == 3:
input->add_node(n)

fclose(f)
return input


def main() -> int:
input = parse_input()

aaa = input->find_node("AAA")
zzz = input->find_node("ZZZ")

current = aaa
counter = 0

while current != zzz:
c = input->left_right_string[counter++ % strlen(input->left_right_string)]
if c == 'L':
current = input->find_node(current->left)
elif c == 'R':
current = input->find_node(current->right)
else:
assert False

free(input)
printf("%d\n", counter) # Output: 6

return 0
252 changes: 252 additions & 0 deletions examples/aoc2023/day08/part2.jou
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import "stdlib/ascii.jou"
import "stdlib/mem.jou"
import "stdlib/io.jou"
import "stdlib/str.jou"


class Node:
name: byte[4]
left: byte[4]
right: byte[4]
left_node: Node*
right_node: Node*


class Input:
left_right_string: byte[1000]
nodes: Node[1000]
nnodes: int

def add_node(self, node: Node) -> void:
assert self->nnodes < sizeof(self->nodes)/sizeof(self->nodes[0])
self->nodes[self->nnodes++] = node

def find_node(self, name: byte*) -> Node*:
for i = 0; i < self->nnodes; i++:
if strcmp(self->nodes[i].name, name) == 0:
return &self->nodes[i]
assert False

def find_node_pointers(self) -> void:
for i = 0; i < self->nnodes; i++:
self->nodes[i].left_node = self->find_node(self->nodes[i].left)
self->nodes[i].right_node = self->find_node(self->nodes[i].right)


class List:
ptr: long*
len: int
alloc: int

def end(self) -> long*:
return &self->ptr[self->len]

def last(self) -> long:
assert self->len != 0
return self->end()[-1]

def append(self, n: long) -> void:
if self->alloc == self->len:
if self->alloc == 0:
self->alloc = 4
else:
self->alloc *= 2

self->ptr = realloc(self->ptr, self->alloc * sizeof(self->ptr[0]))
assert self->ptr != NULL

self->ptr[self->len++] = n

def extend(self, other: List) -> void:
for v = other.ptr; v < other.end(); v++:
self->append(*v)


def swap(a: long*, b: long*) -> void:
temp = *a
*a = *b
*b = temp

def gcd(a: long, b: long) -> long:
assert a > 0 and b > 0
while True:
a %= b
if a == 0:
return b
swap(&a, &b)

def lcm(a: long, b: long) -> long:
return (a/gcd(a,b)) * b


# We say that a number is a Z-count for a node, if doing that many steps
# with the given node as starting node gets you into a node ending with
# letter Z.
#
# Each node ending with 'A' seems to have infinitely many Z-counts. After
# a while they will start to repeat periodically. For example, it could
# be something like:
#
# all_z_counts = [1, 2, 3, 10, 12, 15, 20, 22, 25, 30, 32, 35, ...]
# ^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^
# non-repeating 1st cycle 2nd cycle 3rd cycle
# part
#
# Repeating starts where we reach the same state twice. By same state, I
# mean same current node and same index into the left_right_string.
class ZCounts:
non_repeating: List
first_cycle: List
cycle_offset: long # 10 in the example, each cycle starts 10 bigger than previous

def free(self) -> void:
free(self->non_repeating.ptr)
free(self->first_cycle.ptr)

def print(self) -> void:
printf(" ")
for p = self->non_repeating.ptr; p < self->non_repeating.end(); p++:
printf("%d ", *p)
for k=0; k<2; k++:
# Print "|" character between cycles to distinguish them.
# Numbers before the first "|" do not belong to any cycle.
printf("| ")
for p = self->first_cycle.ptr; p < self->first_cycle.end(); p++:
printf("%d ", *p + k*self->cycle_offset)
printf("| ...\n")

def simplify_cycles(self) -> void:
# If the first cycle repeats the same thing twice, reduce it to half of its length.
# If the first cycle repeats the same thing 3 times, reduce it to 1/3 length.
# And so on.
#
# Example:
# before: | 3 6 | 9 12 | ...
# after: | 3 | 6 | 9 | 12 | ...
#
# We try more parts first: "| 1 2 3 4 | 5 6 7 8 | ..." should be split into 4
# parts, not into 2 parts.
for num_parts = self->first_cycle.len; num_parts >= 2; num_parts--:
if self->first_cycle.len % num_parts != 0 or self->cycle_offset % num_parts != 0:
continue

small_cycle_len = self->first_cycle.len / num_parts
small_offset = self->cycle_offset / num_parts

can_split = True
for i = 0; i < self->first_cycle.len - small_cycle_len; i++:
if self->first_cycle.ptr[i] + small_offset != self->first_cycle.ptr[i + small_cycle_len]:
can_split = False
break

if can_split:
self->first_cycle.len = small_cycle_len
self->cycle_offset = small_offset
return


def get_z_counts(input: Input*, start: Node*) -> ZCounts:
left_right_strlen = strlen(input->left_right_string)

# Number of (index, node) combinations. Guaranteed to cycle after this.
max_num_states = left_right_strlen * input->nnodes

states: Node** = malloc(max_num_states * sizeof(states[0]))
assert states != NULL
nstates = 0

node = start

while True:
# Check if we got the same state before
for i = 0; i < nstates; i++:
if i % left_right_strlen == nstates % left_right_strlen and states[i] == node:
# from here it cycles
non_repeating = List{}
first_cycle = List{}
for k = 0; k < nstates; k++:
if states[k]->name[2] == 'Z':
if k < i:
non_repeating.append(k)
else:
first_cycle.append(k)
free(states)

assert first_cycle.len != 0
return ZCounts{non_repeating=non_repeating, first_cycle=first_cycle, cycle_offset=nstates-i}

assert nstates < max_num_states
states[nstates] = node
c = input->left_right_string[nstates % left_right_strlen]
nstates++

if c == 'L':
node = node->left_node
elif c == 'R':
node = node->right_node
else:
assert False


def parse_input() -> Input*:
input: Input* = calloc(1, sizeof(*input))

f = fopen("sampleinput3.txt", "r")
assert f != NULL

assert fgets(input->left_right_string, sizeof(input->left_right_string) as int, f) != NULL
trim_ascii_whitespace(input->left_right_string)

assert fgetc(f) == '\n'

n: Node
while fscanf(f, "%3s = (%3s, %3s)\n", &n.name, &n.left, &n.right) == 3:
input->add_node(n)

input->find_node_pointers()

fclose(f)
return input


def main() -> int:
input = parse_input()

zcounts: ZCounts[1000]
nzcounts = 0

for i = 0; i < input->nnodes; i++:
if input->nodes[i].name[2] == 'A':
assert nzcounts < sizeof(zcounts)/sizeof(zcounts[0])
if input->nnodes > 500:
# progress print for the real input
printf("get z counts: %s\n", input->nodes[i].name)
zcounts[nzcounts++] = get_z_counts(input, &input->nodes[i])

# Rest of the solution only deals with zcounts
free(input)

for i = 0; i < nzcounts; i++:
zcounts[i].simplify_cycles()
#zcounts[i].print()

# For some reason, each z count is actually just multiples of one number:
#
# [n, 2n, 3n, ...]
#
# Input is probably chosen so that this happens, to make things nice.
for i = 0; i < nzcounts; i++:
assert zcounts[i].non_repeating.len == 0
assert zcounts[i].first_cycle.len == 1
assert zcounts[i].cycle_offset == zcounts[i].first_cycle.ptr[0]

# Compute the Least Common Multiple of what above comment calls "n".
# We basically do lcm(a,b,c,d) = lcm(lcm(lcm(a,b),c),d).
result = zcounts[0].cycle_offset
for i = 1; i < nzcounts; i++:
result = lcm(result, zcounts[i].cycle_offset)
printf("%lld\n", result) # Output: 6

for i = 0; i < nzcounts; i++:
zcounts[i].free()
return 0
9 changes: 9 additions & 0 deletions examples/aoc2023/day08/sampleinput.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
RL

AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)
5 changes: 5 additions & 0 deletions examples/aoc2023/day08/sampleinput2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
LLR

AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)
10 changes: 10 additions & 0 deletions examples/aoc2023/day08/sampleinput3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
LR

11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)