mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-06-13 15:13:31 +02:00
day 24 hurts my brain, thanks reddit
This commit is contained in:
@@ -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