mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
day 24 hurts my brain, thanks reddit
This commit is contained in:
@@ -0,0 +1,87 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrieNode:
|
||||||
|
char: str
|
||||||
|
is_terminator: bool
|
||||||
|
children: dict[str, Self]
|
||||||
|
|
||||||
|
|
||||||
|
def day19(input: str, part: int) -> int:
|
||||||
|
# trie data structure works nicely... but is it necessary for part 2...
|
||||||
|
input_lines = input.splitlines()
|
||||||
|
|
||||||
|
root = TrieNode("", False, {})
|
||||||
|
for towel in input_lines[0].split(", "):
|
||||||
|
iter = root
|
||||||
|
for i, char in enumerate(towel):
|
||||||
|
if char not in iter.children:
|
||||||
|
iter.children[char] = TrieNode(char, i == len(towel) - 1, {})
|
||||||
|
iter = iter.children[char]
|
||||||
|
# update for matching towels that are smaller than a pervious one
|
||||||
|
# e.g. "towel" then "tow" needs to update the "w" node
|
||||||
|
iter.is_terminator |= i == len(towel) - 1
|
||||||
|
|
||||||
|
possible_towels: int = 0
|
||||||
|
total: int = 0
|
||||||
|
|
||||||
|
memo: dict[str, int] = {}
|
||||||
|
for maybe_towel in input_lines[2:]:
|
||||||
|
combos = is_possible_towel(root, maybe_towel, memo)
|
||||||
|
if combos > 0:
|
||||||
|
possible_towels += 1
|
||||||
|
total += combos
|
||||||
|
|
||||||
|
if part == 1:
|
||||||
|
return possible_towels
|
||||||
|
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
# memo optimization added for part 2
|
||||||
|
def is_possible_towel(trie: TrieNode, towel: str, memo: dict[str, int]) -> int:
|
||||||
|
if towel == "":
|
||||||
|
return 1
|
||||||
|
if towel in memo:
|
||||||
|
return memo[towel]
|
||||||
|
|
||||||
|
iter = trie
|
||||||
|
total: int = 0
|
||||||
|
for i, char in enumerate(towel):
|
||||||
|
if char not in iter.children:
|
||||||
|
break
|
||||||
|
|
||||||
|
iter = iter.children[char]
|
||||||
|
|
||||||
|
# if iterated to a terminator, we can restart recursively and if that
|
||||||
|
# "works" (aka returns positive number of possible combos), then we can
|
||||||
|
# sum the combos and then continue along this for loop to account for
|
||||||
|
# larger towels that do not end at this index
|
||||||
|
if iter.is_terminator:
|
||||||
|
total += is_possible_towel(trie, towel[i + 1 :], memo)
|
||||||
|
|
||||||
|
memo[towel] = total
|
||||||
|
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
example = """r, wr, b, g, bwu, rb, gb, br
|
||||||
|
|
||||||
|
brwrr
|
||||||
|
bggr
|
||||||
|
gbbr
|
||||||
|
rrbgbr
|
||||||
|
ubwu
|
||||||
|
bwurrg
|
||||||
|
brgr
|
||||||
|
bbrgwb"""
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"day19 example: {day19(example, 1)}, want 6")
|
||||||
|
print(f"day19 actual: {day19(input, 1)}, want 216")
|
||||||
|
|
||||||
|
print(f"part2 example: {day19(example ,2)}, want 16")
|
||||||
|
print(f"part2 actual: {day19(input ,2)}, want 603191454138773")
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
def day20(input: str, cheats: int) -> dict[int, int]:
|
||||||
|
# get minimum without cheats, do this backwards by propagating from E to S
|
||||||
|
# to populate a grid of the distance from each cell to E
|
||||||
|
grid: list[list[str]] = [list(line) for line in input.splitlines()]
|
||||||
|
|
||||||
|
row: int = 0
|
||||||
|
col: int = 0
|
||||||
|
|
||||||
|
for r in range(len(grid)):
|
||||||
|
for c in range(len(grid[0])):
|
||||||
|
if grid[r][c] == "E":
|
||||||
|
row = r
|
||||||
|
col = c
|
||||||
|
|
||||||
|
# default value set to 100_000 which is bigger than entire grid's area so
|
||||||
|
# walls will not be usable as paths to finish
|
||||||
|
dp_steps_to_end: list[list[int]] = [
|
||||||
|
[100_000 for _ in range(len(grid[0]))] for _ in range(len(grid))
|
||||||
|
]
|
||||||
|
|
||||||
|
diffs = [(0, 1), (0, -1), (-1, 0), (1, 0)]
|
||||||
|
queue: list[tuple[int, int, int]] = [(row, col, 0)]
|
||||||
|
visited: set[tuple[int, int]] = set()
|
||||||
|
while len(queue) > 0:
|
||||||
|
row, col, steps = queue.pop(0)
|
||||||
|
dp_steps_to_end[row][col] = steps
|
||||||
|
visited.add((row, col))
|
||||||
|
|
||||||
|
for diff in diffs:
|
||||||
|
next_row, next_col = row + diff[0], col + diff[1]
|
||||||
|
if 0 <= next_row < len(grid) and 0 <= next_col < len(grid[0]):
|
||||||
|
if grid[next_row][next_col] != "#":
|
||||||
|
if (next_row, next_col) not in visited:
|
||||||
|
queue.append((next_row, next_col, steps + 1))
|
||||||
|
|
||||||
|
# then starting from S, BFS but from each cell, view all cells <cheats> cells away
|
||||||
|
# if it's a valid shortcut, record it
|
||||||
|
|
||||||
|
second_saved_freqs: dict[int, int] = defaultdict(int)
|
||||||
|
|
||||||
|
reachable_diffs = get_reachable_coords_within_x_steps(cheats)
|
||||||
|
|
||||||
|
# visited contains coords of the entire path, so just use that...
|
||||||
|
for path_coord in visited:
|
||||||
|
r, c = path_coord
|
||||||
|
for diff in reachable_diffs:
|
||||||
|
rr = path_coord[0] + diff[0]
|
||||||
|
cc = path_coord[1] + diff[1]
|
||||||
|
|
||||||
|
if 0 <= rr < len(grid) and 0 <= cc < len(grid[0]):
|
||||||
|
if grid[rr][cc] != "#":
|
||||||
|
savings = dp_steps_to_end[r][c] - (
|
||||||
|
dp_steps_to_end[rr][cc] + abs(diff[0]) + abs(diff[1])
|
||||||
|
)
|
||||||
|
if savings > 0:
|
||||||
|
second_saved_freqs[savings] += 1
|
||||||
|
|
||||||
|
return second_saved_freqs
|
||||||
|
|
||||||
|
|
||||||
|
# helper function to get all coords that are reachable within allotted cheats
|
||||||
|
def get_reachable_coords_within_x_steps(x: int) -> list[tuple[int, int]]:
|
||||||
|
coords_map: dict[tuple[int, int], bool] = {}
|
||||||
|
|
||||||
|
queue: list[tuple[int, int]] = [(0, 0)]
|
||||||
|
for _ in range(x):
|
||||||
|
next_queue: list[tuple[int, int]] = []
|
||||||
|
while len(queue) > 0:
|
||||||
|
front = queue.pop(0)
|
||||||
|
for diff in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
|
||||||
|
coord = (front[0] + diff[0], front[1] + diff[1])
|
||||||
|
if coord not in coords_map:
|
||||||
|
coords_map[coord] = True
|
||||||
|
next_queue.append(coord)
|
||||||
|
queue = next_queue
|
||||||
|
|
||||||
|
return list(coords_map.keys())
|
||||||
|
|
||||||
|
|
||||||
|
example = """###############
|
||||||
|
#...#...#.....#
|
||||||
|
#.#.#.#.#.###.#
|
||||||
|
#S#...#.#.#...#
|
||||||
|
#######.#.#.###
|
||||||
|
#######.#.#...#
|
||||||
|
#######.#.###.#
|
||||||
|
###..E#...#...#
|
||||||
|
###.#######.###
|
||||||
|
#...###...#...#
|
||||||
|
#.#####.#.###.#
|
||||||
|
#.#...#.#.#...#
|
||||||
|
#.#.#.#.#.#.###
|
||||||
|
#...#...#...###
|
||||||
|
###############"""
|
||||||
|
|
||||||
|
# python is weird and we can use == to compare 2 dicts for the same keys and values...
|
||||||
|
example_seconds_saved_to_freq: dict[int, int] = {
|
||||||
|
2: 14,
|
||||||
|
4: 14,
|
||||||
|
6: 2,
|
||||||
|
8: 4,
|
||||||
|
10: 2,
|
||||||
|
12: 3,
|
||||||
|
20: 1,
|
||||||
|
36: 1,
|
||||||
|
38: 1,
|
||||||
|
40: 1,
|
||||||
|
64: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
part1_example_result = day20(example, 2)
|
||||||
|
print(
|
||||||
|
f"part1 example: {part1_example_result == example_seconds_saved_to_freq} want True"
|
||||||
|
)
|
||||||
|
if part1_example_result != example_seconds_saved_to_freq:
|
||||||
|
print(
|
||||||
|
f"\tDEBUG part1 example got: {part1_example_result} want {(example_seconds_saved_to_freq)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
|
||||||
|
def sum_keys_over_x(d: dict[int, int], x: int) -> int:
|
||||||
|
total: int = 0
|
||||||
|
for k, v in d.items():
|
||||||
|
if k >= x:
|
||||||
|
total += v
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
part1_result = day20(input, 2)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"part1 actual: {sum_keys_over_x(part1_result, 100)} cheats save 100+ picosecs want 1422"
|
||||||
|
)
|
||||||
|
|
||||||
|
part2_example_result: dict[int, int] = {
|
||||||
|
50: 32,
|
||||||
|
52: 31,
|
||||||
|
54: 29,
|
||||||
|
56: 39,
|
||||||
|
58: 25,
|
||||||
|
60: 23,
|
||||||
|
62: 20,
|
||||||
|
64: 19,
|
||||||
|
66: 12,
|
||||||
|
68: 14,
|
||||||
|
70: 12,
|
||||||
|
72: 22,
|
||||||
|
74: 4,
|
||||||
|
76: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
part2_result = day20(example, 20)
|
||||||
|
part2_result_50 = {}
|
||||||
|
for k, v in part2_example_result.items():
|
||||||
|
if k >= 50:
|
||||||
|
part2_result_50[k] = v
|
||||||
|
|
||||||
|
print(f"part2 example: {part2_result_50 == part2_example_result} want True")
|
||||||
|
if part2_result_50 != part2_example_result:
|
||||||
|
print(
|
||||||
|
f"DEBUG part2 example 50+ saved: {part2_result_50} want {part2_example_result}"
|
||||||
|
)
|
||||||
|
|
||||||
|
part2_result = day20(input, 20)
|
||||||
|
print(f"part2 actual: {sum_keys_over_x(part2_result, 100)} want 1009299")
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
def day22(input: str, part: int) -> int:
|
||||||
|
total: int = 0
|
||||||
|
|
||||||
|
last_four_diffs_to_price: dict[tuple[int, int, int, int], int] = defaultdict(int)
|
||||||
|
|
||||||
|
for line in input.splitlines():
|
||||||
|
nums: list[int] = [int(line)]
|
||||||
|
diffs: list[int] = []
|
||||||
|
|
||||||
|
seen_last_fours: set[tuple[int, int, int, int]] = set()
|
||||||
|
for _ in range(2000):
|
||||||
|
nums.append(get_next_secret_number(nums[-1]))
|
||||||
|
diff = nums[-1] % 10 - nums[-2] % 10
|
||||||
|
diffs.append(diff)
|
||||||
|
total += nums[-1]
|
||||||
|
|
||||||
|
for i in range(3, len(diffs)):
|
||||||
|
last_four_diffs = (diffs[i - 3], diffs[i - 2], diffs[i - 1], diffs[i])
|
||||||
|
# only count the first time we've seen this set of diffs, aka only sell once
|
||||||
|
if last_four_diffs in seen_last_fours:
|
||||||
|
continue
|
||||||
|
seen_last_fours.add(last_four_diffs)
|
||||||
|
|
||||||
|
last_four_diffs_to_price[last_four_diffs] += nums[i + 1] % 10
|
||||||
|
|
||||||
|
if part == 1:
|
||||||
|
return total
|
||||||
|
|
||||||
|
most_bananas = max(last_four_diffs_to_price.values())
|
||||||
|
return most_bananas
|
||||||
|
|
||||||
|
|
||||||
|
def get_next_secret_number(num: int) -> int:
|
||||||
|
num = (num ^ (num * 64)) % 16777216
|
||||||
|
num = (num ^ (num // 32)) % 16777216
|
||||||
|
num = (num ^ (num * 2048)) % 16777216
|
||||||
|
return num
|
||||||
|
|
||||||
|
|
||||||
|
example = """1
|
||||||
|
10
|
||||||
|
100
|
||||||
|
2024"""
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"part1 example: {day22(example, 1)} want 37327623")
|
||||||
|
print(f"part1 actual: {day22(input, 1)} want 17612566393")
|
||||||
|
|
||||||
|
example_part_2 = """1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
2024"""
|
||||||
|
print(f"part2 example: {day22(example_part_2, 2)} want 23")
|
||||||
|
print(f"part2 actual: {day22(input, 2)} want 1968")
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
def part1(input: str) -> int:
|
||||||
|
graph: dict[str, set[str]] = defaultdict(set)
|
||||||
|
for line in input.splitlines():
|
||||||
|
parts = line.split("-")
|
||||||
|
graph[parts[0]].add(parts[1])
|
||||||
|
graph[parts[1]].add(parts[0])
|
||||||
|
|
||||||
|
ans_groups: set[str] = set()
|
||||||
|
for node in graph:
|
||||||
|
if node[0] != "t":
|
||||||
|
continue
|
||||||
|
neighbors = graph[node]
|
||||||
|
for neighbor in neighbors:
|
||||||
|
for neighbor2 in neighbors:
|
||||||
|
if neighbor == neighbor2:
|
||||||
|
continue
|
||||||
|
if neighbor2 in graph[neighbor]:
|
||||||
|
group: list[str] = [node, neighbor, neighbor2]
|
||||||
|
group.sort()
|
||||||
|
|
||||||
|
ans_groups.add(",".join(group))
|
||||||
|
|
||||||
|
return len(ans_groups)
|
||||||
|
|
||||||
|
|
||||||
|
def part2(input: str) -> str:
|
||||||
|
graph: dict[str, set[str]] = defaultdict(set)
|
||||||
|
for line in input.splitlines():
|
||||||
|
parts = line.split("-")
|
||||||
|
graph[parts[0]].add(parts[1])
|
||||||
|
graph[parts[1]].add(parts[0])
|
||||||
|
|
||||||
|
seen: set[str] = set()
|
||||||
|
largest_group: set[str] = set()
|
||||||
|
for node in graph:
|
||||||
|
if node in seen:
|
||||||
|
continue
|
||||||
|
seen.add(node)
|
||||||
|
|
||||||
|
group: set[str] = {node}
|
||||||
|
for neighbor in graph[node]:
|
||||||
|
if check_group_against_new_node(group, graph, neighbor):
|
||||||
|
group.add(neighbor)
|
||||||
|
seen.add(neighbor)
|
||||||
|
|
||||||
|
if len(group) > len(largest_group):
|
||||||
|
largest_group = group
|
||||||
|
|
||||||
|
final_group: list[str] = list(largest_group)
|
||||||
|
final_group.sort()
|
||||||
|
return ",".join(final_group)
|
||||||
|
|
||||||
|
|
||||||
|
def check_group_against_new_node(
|
||||||
|
group: set[str], graph: dict[str, set[str]], node_to_add: str
|
||||||
|
) -> bool:
|
||||||
|
for node in group:
|
||||||
|
if node not in graph[node_to_add]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
example = """kh-tc
|
||||||
|
qp-kh
|
||||||
|
de-cg
|
||||||
|
ka-co
|
||||||
|
yn-aq
|
||||||
|
qp-ub
|
||||||
|
cg-tb
|
||||||
|
vc-aq
|
||||||
|
tb-ka
|
||||||
|
wh-tc
|
||||||
|
yn-cg
|
||||||
|
kh-ub
|
||||||
|
ta-co
|
||||||
|
de-co
|
||||||
|
tc-td
|
||||||
|
tb-wq
|
||||||
|
wh-td
|
||||||
|
ta-ka
|
||||||
|
td-qp
|
||||||
|
aq-cg
|
||||||
|
wq-ub
|
||||||
|
ub-vc
|
||||||
|
de-ta
|
||||||
|
wq-aq
|
||||||
|
wq-vc
|
||||||
|
wh-yn
|
||||||
|
ka-de
|
||||||
|
kh-ta
|
||||||
|
co-tc
|
||||||
|
wh-qp
|
||||||
|
tb-vc
|
||||||
|
td-yn"""
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"part1 example: {part1(example)} want 7")
|
||||||
|
print(f"part1 actual: {part1(input)} want 1485")
|
||||||
|
|
||||||
|
print(f"part2 example: {part2(example)} want co,de,ka,ta")
|
||||||
|
print(f"part2 actual: {part2(input)} want cc,dz,ea,hj,if,it,kf,qo,sk,ug,ut,uv,wh")
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
import copy
|
||||||
|
|
||||||
|
|
||||||
|
def part1(input: str) -> int:
|
||||||
|
wires, instructions = parse_input(input)
|
||||||
|
run(instructions, wires)
|
||||||
|
return get_num(wires, "z")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_input(input: str) -> tuple[dict[str, int], list[list[str]]]:
|
||||||
|
parts = input.split("\n\n")
|
||||||
|
|
||||||
|
wires: dict[str, int] = defaultdict(int)
|
||||||
|
|
||||||
|
for line in parts[0].splitlines():
|
||||||
|
wires[line[:3]] = int(line[5:])
|
||||||
|
|
||||||
|
instructions: list[list[str]] = []
|
||||||
|
for line in parts[1].splitlines():
|
||||||
|
in1, op, in2, _, out = line.split(" ")
|
||||||
|
instructions.append([in1, op, in2, out])
|
||||||
|
|
||||||
|
return (wires, instructions)
|
||||||
|
|
||||||
|
|
||||||
|
def get_num(wires: dict[str, int], char: str) -> int:
|
||||||
|
if len(char) != 1:
|
||||||
|
raise Exception("exactly one char needed for get_num")
|
||||||
|
keys: list[str] = []
|
||||||
|
for wire in wires:
|
||||||
|
if wire[0] == char:
|
||||||
|
keys.append(wire)
|
||||||
|
keys.sort()
|
||||||
|
binary_num: str = ""
|
||||||
|
for key in reversed(keys):
|
||||||
|
binary_num += str(wires[key])
|
||||||
|
|
||||||
|
return int(binary_num, 2)
|
||||||
|
|
||||||
|
|
||||||
|
def run(instructions: list[list[str]], wires: dict[str, int]):
|
||||||
|
processed_lines: set[int] = set()
|
||||||
|
while len(processed_lines) < len(instructions):
|
||||||
|
processed_before: int = len(processed_lines)
|
||||||
|
|
||||||
|
for i, inst in enumerate(instructions):
|
||||||
|
if i in processed_lines:
|
||||||
|
continue
|
||||||
|
in1, op, in2, out = inst
|
||||||
|
if in1 not in wires or in2 not in wires:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if op == "AND":
|
||||||
|
wires[out] = wires[in1] & wires[in2]
|
||||||
|
elif op == "OR":
|
||||||
|
wires[out] = wires[in1] | wires[in2]
|
||||||
|
elif op == "XOR":
|
||||||
|
wires[out] = wires[in1] ^ wires[in2]
|
||||||
|
|
||||||
|
processed_lines.add(i)
|
||||||
|
|
||||||
|
if len(processed_lines) == processed_before:
|
||||||
|
# print("no new lines processed")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# this code is way too slow, works for the examples but unsurprisingly way too
|
||||||
|
# slow for the actual input. ditching python for a go solution...
|
||||||
|
def part2(input: str) -> str:
|
||||||
|
wires, instructions = parse_input(input)
|
||||||
|
expected_sum: int = get_num(wires, "x") & get_num(wires, "y")
|
||||||
|
|
||||||
|
def any_values_are_equal(*args: int) -> bool:
|
||||||
|
seen: set[int] = set()
|
||||||
|
for val in args:
|
||||||
|
if val in seen:
|
||||||
|
return True
|
||||||
|
seen.add(val)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# generate swaps... maybe 8 nested for loops...
|
||||||
|
for a in range(len(instructions)):
|
||||||
|
print(a)
|
||||||
|
for b in range(a + 1, len(instructions)):
|
||||||
|
for c in range(a + 1, len(instructions)):
|
||||||
|
if any_values_are_equal(b, c):
|
||||||
|
continue
|
||||||
|
for d in range(a + 1, len(instructions)):
|
||||||
|
if any_values_are_equal(b, c, d):
|
||||||
|
continue
|
||||||
|
for e in range(a + 1, len(instructions)):
|
||||||
|
if any_values_are_equal(b, c, d, e):
|
||||||
|
continue
|
||||||
|
for f in range(a + 1, len(instructions)):
|
||||||
|
if any_values_are_equal(b, c, d, e, f):
|
||||||
|
continue
|
||||||
|
for g in range(a + 1, len(instructions)):
|
||||||
|
if any_values_are_equal(b, c, d, e, f, g):
|
||||||
|
continue
|
||||||
|
for h in range(a + 1, len(instructions)):
|
||||||
|
if any_values_are_equal(b, c, d, e, f, g, h):
|
||||||
|
continue
|
||||||
|
|
||||||
|
instructions[a][3], instructions[b][3] = (
|
||||||
|
instructions[b][3],
|
||||||
|
instructions[a][3],
|
||||||
|
)
|
||||||
|
instructions[c][3], instructions[d][3] = (
|
||||||
|
instructions[d][3],
|
||||||
|
instructions[c][3],
|
||||||
|
)
|
||||||
|
|
||||||
|
instructions[e][3], instructions[f][3] = (
|
||||||
|
instructions[f][3],
|
||||||
|
instructions[e][3],
|
||||||
|
)
|
||||||
|
instructions[g][3], instructions[h][3] = (
|
||||||
|
instructions[h][3],
|
||||||
|
instructions[g][3],
|
||||||
|
)
|
||||||
|
|
||||||
|
wires_copy = copy.deepcopy(wires)
|
||||||
|
run(instructions, wires_copy)
|
||||||
|
sum = get_num(wires_copy, "z")
|
||||||
|
if sum == expected_sum:
|
||||||
|
swaps: list[str] = [
|
||||||
|
instructions[a][3],
|
||||||
|
instructions[b][3],
|
||||||
|
instructions[c][3],
|
||||||
|
instructions[d][3],
|
||||||
|
instructions[e][3],
|
||||||
|
instructions[f][3],
|
||||||
|
instructions[g][3],
|
||||||
|
instructions[h][3],
|
||||||
|
]
|
||||||
|
swaps.sort()
|
||||||
|
return ",".join(swaps)
|
||||||
|
# backtrack
|
||||||
|
instructions[a][3], instructions[b][3] = (
|
||||||
|
instructions[b][3],
|
||||||
|
instructions[a][3],
|
||||||
|
)
|
||||||
|
instructions[c][3], instructions[d][3] = (
|
||||||
|
instructions[d][3],
|
||||||
|
instructions[c][3],
|
||||||
|
)
|
||||||
|
instructions[e][3], instructions[f][3] = (
|
||||||
|
instructions[f][3],
|
||||||
|
instructions[e][3],
|
||||||
|
)
|
||||||
|
instructions[g][3], instructions[h][3] = (
|
||||||
|
instructions[h][3],
|
||||||
|
instructions[g][3],
|
||||||
|
)
|
||||||
|
|
||||||
|
raise Exception("should return from loop")
|
||||||
|
|
||||||
|
|
||||||
|
example = """x00: 1
|
||||||
|
x01: 1
|
||||||
|
x02: 1
|
||||||
|
y00: 0
|
||||||
|
y01: 1
|
||||||
|
y02: 0
|
||||||
|
|
||||||
|
x00 AND y00 -> z00
|
||||||
|
x01 XOR y01 -> z01
|
||||||
|
x02 OR y02 -> z02"""
|
||||||
|
|
||||||
|
print(f"part1 example: {part1(example)} want 4")
|
||||||
|
|
||||||
|
big_example = """x00: 1
|
||||||
|
x01: 0
|
||||||
|
x02: 1
|
||||||
|
x03: 1
|
||||||
|
x04: 0
|
||||||
|
y00: 1
|
||||||
|
y01: 1
|
||||||
|
y02: 1
|
||||||
|
y03: 1
|
||||||
|
y04: 1
|
||||||
|
|
||||||
|
ntg XOR fgs -> mjb
|
||||||
|
y02 OR x01 -> tnw
|
||||||
|
kwq OR kpj -> z05
|
||||||
|
x00 OR x03 -> fst
|
||||||
|
tgd XOR rvg -> z01
|
||||||
|
vdt OR tnw -> bfw
|
||||||
|
bfw AND frj -> z10
|
||||||
|
ffh OR nrd -> bqk
|
||||||
|
y00 AND y03 -> djm
|
||||||
|
y03 OR y00 -> psh
|
||||||
|
bqk OR frj -> z08
|
||||||
|
tnw OR fst -> frj
|
||||||
|
gnj AND tgd -> z11
|
||||||
|
bfw XOR mjb -> z00
|
||||||
|
x03 OR x00 -> vdt
|
||||||
|
gnj AND wpb -> z02
|
||||||
|
x04 AND y00 -> kjc
|
||||||
|
djm OR pbm -> qhw
|
||||||
|
nrd AND vdt -> hwm
|
||||||
|
kjc AND fst -> rvg
|
||||||
|
y04 OR y02 -> fgs
|
||||||
|
y01 AND x02 -> pbm
|
||||||
|
ntg OR kjc -> kwq
|
||||||
|
psh XOR fgs -> tgd
|
||||||
|
qhw XOR tgd -> z09
|
||||||
|
pbm OR djm -> kpj
|
||||||
|
x03 XOR y03 -> ffh
|
||||||
|
x00 XOR y04 -> ntg
|
||||||
|
bfw OR bqk -> z06
|
||||||
|
nrd XOR fgs -> wpb
|
||||||
|
frj XOR qhw -> z04
|
||||||
|
bqk OR frj -> z07
|
||||||
|
y03 OR x01 -> nrd
|
||||||
|
hwm AND bqk -> z03
|
||||||
|
tgd XOR rvg -> z12
|
||||||
|
tnw OR pbm -> gnj"""
|
||||||
|
|
||||||
|
print(f"part1 big_example: {part1(big_example)} want 2024")
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
print(f"part1 actual: {part1(input)} want 38869984335432")
|
||||||
|
|
||||||
|
part2_example_2_pairs_swapped = """x00: 0
|
||||||
|
x01: 1
|
||||||
|
x02: 0
|
||||||
|
x03: 1
|
||||||
|
x04: 0
|
||||||
|
x05: 1
|
||||||
|
y00: 0
|
||||||
|
y01: 0
|
||||||
|
y02: 1
|
||||||
|
y03: 1
|
||||||
|
y04: 0
|
||||||
|
y05: 1
|
||||||
|
|
||||||
|
x00 AND y00 -> z05
|
||||||
|
x01 AND y01 -> z02
|
||||||
|
x02 AND y02 -> z01
|
||||||
|
x03 AND y03 -> z03
|
||||||
|
x04 AND y04 -> z04
|
||||||
|
x05 AND y05 -> z00"""
|
||||||
|
|
||||||
|
# print(
|
||||||
|
# f"part2 example with two swapped {part2(part2_example_2_pairs_swapped)} want z00,z01,z02,z05"
|
||||||
|
# )
|
||||||
|
|
||||||
|
print(f"part2 actual: {part2(input)} want QQ")
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alexchao26/advent-of-code-go/cast"
|
||||||
|
"github.com/alexchao26/advent-of-code-go/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed input.txt
|
||||||
|
var input string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// do this in init (not main) so test file has same input
|
||||||
|
input = strings.TrimRight(input, "\n")
|
||||||
|
if len(input) == 0 {
|
||||||
|
panic("empty input.txt file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var part int
|
||||||
|
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||||
|
flag.Parse()
|
||||||
|
fmt.Println("Running part", part)
|
||||||
|
|
||||||
|
if part == 1 {
|
||||||
|
ans := part1(input)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
} else {
|
||||||
|
ans := part2(input)
|
||||||
|
// drg,gvw,jbp,jgc,qjb,z15,z22,z35
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func part1(input string) int {
|
||||||
|
calc := parseInput(input)
|
||||||
|
res := 0
|
||||||
|
|
||||||
|
cache := map[string]int{}
|
||||||
|
|
||||||
|
for k, v := range calc.wires {
|
||||||
|
cache[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range calc.outputToSource {
|
||||||
|
if k[0] != 'z' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := getWireVal(k, calc, cache, 0)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Did not expect error\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if val == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
i := cast.ToInt(k[1:])
|
||||||
|
|
||||||
|
res += 1 << int(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2(input string) string {
|
||||||
|
calc := parseInput(input)
|
||||||
|
|
||||||
|
swaps := map[string]bool{}
|
||||||
|
ans := map[string]bool{}
|
||||||
|
|
||||||
|
fmt.Println("this will take a while to run")
|
||||||
|
backtrack(0, calc, swaps, ans)
|
||||||
|
|
||||||
|
wires := []string{}
|
||||||
|
for wire := range ans {
|
||||||
|
wires = append(wires, wire)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(wires)
|
||||||
|
|
||||||
|
return strings.Join(wires, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWireVal(wire string, calc Calculator, cache map[string]int, depth int) (int, error) {
|
||||||
|
if val, ok := cache[wire]; ok {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if depth > 200 {
|
||||||
|
return -1, fmt.Errorf("likely a loop, exit")
|
||||||
|
}
|
||||||
|
|
||||||
|
op, ok := calc.outputToSource[wire]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return -1, fmt.Errorf("cannot get value for wire %v", wire)
|
||||||
|
}
|
||||||
|
|
||||||
|
v1, _ := getWireVal(op.inputs[0], calc, cache, depth+1)
|
||||||
|
v2, _ := getWireVal(op.inputs[1], calc, cache, depth+1)
|
||||||
|
|
||||||
|
val := 0
|
||||||
|
if op.op == "AND" {
|
||||||
|
val = v1 & v2
|
||||||
|
} else if op.op == "OR" {
|
||||||
|
val = v1 | v2
|
||||||
|
} else if op.op == "XOR" {
|
||||||
|
val = v1 ^ v2
|
||||||
|
}
|
||||||
|
|
||||||
|
cache[wire] = val
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFirstWrong(calc Calculator) (int, map[string]int, error) {
|
||||||
|
cRes := map[string]int{}
|
||||||
|
bRes := 100
|
||||||
|
|
||||||
|
for range 10 {
|
||||||
|
cache := map[string]int{}
|
||||||
|
|
||||||
|
carry := 0
|
||||||
|
for b := range 45 {
|
||||||
|
xStr := fmt.Sprintf("x%02d", b)
|
||||||
|
yStr := fmt.Sprintf("y%02d", b)
|
||||||
|
zStr := fmt.Sprintf("z%02d", b)
|
||||||
|
|
||||||
|
cache[xStr] = rand.Intn(2)
|
||||||
|
cache[yStr] = rand.Intn(2)
|
||||||
|
|
||||||
|
x := cache[xStr]
|
||||||
|
y := cache[yStr]
|
||||||
|
|
||||||
|
zCalc, err := getWireVal(zStr, calc, cache, 0)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return -1, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
zExp := (x + y + carry) % 2
|
||||||
|
carry = (x + y + carry) / 2
|
||||||
|
|
||||||
|
if zCalc != zExp && b < bRes {
|
||||||
|
bRes = b
|
||||||
|
cRes = cache
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bRes == 100 {
|
||||||
|
bRes = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return bRes, cRes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCalc(calc Calculator) bool {
|
||||||
|
bErr, _, err := getFirstWrong(calc)
|
||||||
|
|
||||||
|
return bErr == -1 && err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func backtrack(b int, calc Calculator, swaps map[string]bool, ans map[string]bool) {
|
||||||
|
if len(ans) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// fmt.Printf("Checking %v with swaps\n%v\n", b, swaps)
|
||||||
|
if len(swaps) == 8 {
|
||||||
|
if checkCalc(calc) {
|
||||||
|
for k := range swaps {
|
||||||
|
ans[k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bErr, conns, err := getFirstWrong(calc)
|
||||||
|
|
||||||
|
if bErr < b || err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conns2 := map[string]Connections{}
|
||||||
|
|
||||||
|
for k, v := range calc.outputToSource {
|
||||||
|
conns2[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for c1 := range conns {
|
||||||
|
if swaps[c1] || c1[0] == 'x' || c1[0] == 'y' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for c2 := range conns2 {
|
||||||
|
if c2 == c1 || swaps[c2] || c2[0] == 'x' || c2[0] == 'y' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
calc.outputToSource[c1], calc.outputToSource[c2] = calc.outputToSource[c2], calc.outputToSource[c1]
|
||||||
|
|
||||||
|
swaps[c1] = true
|
||||||
|
swaps[c2] = true
|
||||||
|
|
||||||
|
backtrack(bErr+1, calc, swaps, ans)
|
||||||
|
|
||||||
|
delete(swaps, c2)
|
||||||
|
delete(swaps, c1)
|
||||||
|
|
||||||
|
calc.outputToSource[c1], calc.outputToSource[c2] = calc.outputToSource[c2], calc.outputToSource[c1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Connections struct {
|
||||||
|
inputs []string
|
||||||
|
op string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Calculator struct {
|
||||||
|
outputToSource map[string]Connections
|
||||||
|
wires map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInput(input string) Calculator {
|
||||||
|
parts := strings.Split(input, "\n\n")
|
||||||
|
|
||||||
|
wires := map[string]int{}
|
||||||
|
for _, line := range strings.Split(parts[0], "\n") {
|
||||||
|
wires[line[:3]] = cast.ToInt(line[5:])
|
||||||
|
}
|
||||||
|
|
||||||
|
outputToSource := map[string]Connections{}
|
||||||
|
for _, line := range strings.Split(parts[1], "\n") {
|
||||||
|
lineParts := strings.Split(line, " ")
|
||||||
|
in1, op, in2, out := lineParts[0], lineParts[1], lineParts[2], lineParts[4]
|
||||||
|
outputToSource[out] = Connections{
|
||||||
|
inputs: []string{in1, in2},
|
||||||
|
op: op,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Calculator{outputToSource, wires}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user