Files
newt/aoc2024/Day24.newt
Steve Dunham d6156ebc79 Fix aoc2024 build
- Holes are no longer allowed when building executables
- Stack overflow in mapMaybe (Day15)
2025-04-10 08:50:52 -04:00

212 lines
6.1 KiB
Agda
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
module Day24
import Prelude
import Node
import Aoc
import SortedMap
data Value : U where
One Zero None : Value
data Op : U where
Xor And Or : Op
instance Eq Op where
Xor == Xor = True
And == And = True
Or == Or = True
_ == _ = False
record Gate where
constructor MkG
in1 : String
in2 : String
op : Op
out : String
in1 : Gate String
in1 g = g.in1
in2 : Gate String
in2 g = g.in2
out : Gate String
out g = g.out
op : Gate Op
op g = g.op
Wire : U
Wire = String × Int
parseFile : String Either String (List Wire × List Gate)
parseFile text = do
let (a :: b :: Nil) = split (trim text) "\n\n" | _ => Left "too many parts"
wires <- traverse parseWire $ lines a
gates <- traverse parseGate $ lines b
Right (wires, gates)
where
parseWire : String Either String (String × Int)
parseWire s = let (a :: b :: Nil) = split s ": " | _ => Left $ "bad wire: " ++ s in
Right (a, stringToInt b)
getOp : String Either String Op
getOp "XOR" = Right Xor
getOp "AND" = Right And
getOp "OR" = Right Or
getOp op = Left $ "bad op " ++ op
parseGate : String Either String Gate
parseGate s = do
let (in1 :: op :: in2 :: _ :: out :: Nil) = split s " " | _ => Left $ "bad gate: " ++ s
op <- getOp op
Right $ MkG in1 in2 op out
State : U
State = SortedMap String Int
mkFire : List Gate SortedMap String (List String)
mkFire gates = go EmptyMap gates
where
go : SortedMap String (List String) List Gate SortedMap String (List String)
go acc Nil = acc
go acc (g :: gs) =
let acc = updateMap (in1 g) (out g :: fromMaybe Nil (snd <$> lookupMap (in1 g) acc)) acc in
let acc = updateMap (in2 g) (out g :: fromMaybe Nil (snd <$> lookupMap (in2 g) acc)) acc in
go acc gs
exec : SortedMap String Gate SortedMap String (List String) List String State State
exec gateMap fireMap todo state = go todo state
where
combine : Op Int Int Int
combine And 1 x = x
combine Xor 1 1 = 0
combine Xor 1 0 = 1
combine Xor 0 1 = 1
combine Or 1 _ = 1
combine Or _ 1 = 1
combine _ _ _ = 0
go : List String State State
go Nil st = st
go (x :: xs) st = case lookupMap x st of
-- already done
Just _ => go xs st
Nothing => case lookupMap' x gateMap of
Nothing => go xs st
Just gate => case (lookupMap' (in1 gate) st, lookupMap' (in2 gate) st) of
(Just x, Just y) =>
let v = combine (op gate) x y
st = updateMap (out gate) v st
foo = fromMaybe Nil $ lookupMap' (out gate) fireMap
xs' = foo ++ xs
in go xs' st
_ => go xs st
bitsToInt : Int List Int Int
bitsToInt _ Nil = 0
bitsToInt m (x :: xs) = m * x + bitsToInt (2 * m) xs
-- so I can find the lowest bit that is wrong
-- or trace out the circuit?
infixl 5 _%_
pfunc _%_ : Int Int Int := `(x,y) => x % y`
label : Char -> Int -> String
label c bit = pack $ c :: chr (bit / 10 + 48) :: chr ((bit % 10) + 48) :: Nil
range : Int Int List Int
range a b = if a < b then a :: range (a + 1) b else Nil
swapPins : String String Gate Gate
swapPins a g (MkG i1 i2 op out) =
if out == a then MkG i1 i2 op g
else if out == g then MkG i1 i2 op a
else MkG i1 i2 op out
fail : a. String -> a
fail msg = fatalError msg
check : List Gate List Int String Either (String × String) Unit
check gates Nil carry = Right MkUnit
check gates (bit :: bits) carry = do
let xl = label 'x' bit
let (Just a1) = matchIn xl And | _ => fail $ "no a1 " ++ show bit
let (Just x1) = matchIn xl Xor | _ => fail $ "no x1 " ++ show bit
-- just peel of the carry for bit0
let (False) = bit == 0 | _ => check gates bits (out a1)
let (Just x2) = matchIn carry Xor
| _ => do
-- carry is bad
let (Just x2) = matchOut (label 'z' bit) Xor | _ => fail $ "no x2 for " ++ show bit
if in1 x2 == out x1 then Left (in2 x2, carry) else Left (in1 x2, carry)
let (Just a2) = matchIn carry And | _ => fail $ "no a2 " ++ show bit
checkPins x2 carry (out x1)
checkPins a2 carry (out x1)
case (matchIn (out a1) Or, matchIn (out a2) Or) of
(Nothing, Nothing) => fail "todo2"
(Just g, Nothing) => checkPins g (out a1) (out a2)
(Nothing, Just g) => checkPins g (out a1) (out a2)
(Just g, Just g') =>
if out g == out g'
then check gates bits (out g)
else fail "todo"
where
checkPins : Gate String String Either (String × String) Unit
checkPins g a b =
if a == in1 g && b == in2 g then pure MkUnit
else if a == in2 g && b == in1 g then pure MkUnit
else if a == in1 g then Left (b, in2 g)
else if a == in2 g then Left (b, in1 g)
else if b == in1 g then Left (a, in2 g)
else Left (a, in1 g)
matchOut : String -> Op -> Maybe Gate
matchOut l o = find (\ g => out g == l && op g == o) gates
matchIn : String -> Op -> Maybe Gate
matchIn l o = find (\ g => (in1 g == l || in2 g == l) && op g == o) gates
trampoline : List Gate List String IO (List String)
trampoline gates acc = do
case check gates (range 0 44) "" of
Right _ => pure acc
Left (a,b) => do
putStrLn $ "SWAP " ++ a ++ " and " ++ b
trampoline (map (swapPins a b) gates) (a :: b :: acc)
run : String -> IO Unit
run fn = do
putStrLn fn
text <- readFile fn
let (Right (wires, gates)) = parseFile text | Left msg => putStrLn $ "fail: " ++ msg
let state = foldMap const EmptyMap wires
let gateMap = foldMap const EmptyMap $ map (\ gate => (out gate, gate)) gates
let fireMap = mkFire gates
let init = join $ map snd $ mapMaybe (\ wire => lookupMap wire fireMap) $ map fst wires
let state' = exec gateMap fireMap init state
let bits = map snd $ filter (_<_ "z" fst) $ toList state'
let p1 = bitsToInt 1 bits
putStrLn $ "part1 " ++ show p1
stuff <- trampoline gates Nil
putStrLn $ "part2 " ++ (joinBy "," $ qsort _<_ stuff)
putStrLn ""
main : IO Unit
main = do
-- run "aoc2024/day24/eg.txt"
-- run "aoc2024/day24/eg2.txt"
-- this is the only adder circuit
run "aoc2024/day24/input.txt"