Skip to content

Commit

Permalink
Aoc day 8 (#432)
Browse files Browse the repository at this point in the history
  • Loading branch information
Akuli authored Dec 8, 2023
1 parent 44e6505 commit 4801a60
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 0 deletions.
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)

0 comments on commit 4801a60

Please sign in to comment.