Files
newt/aoc2024/Day24.newt
Steve Dunham 3ec2f90770 sugar for data and other improvements
- parse types in let (everything but parser was there)
- add sugar for `data`
- move `joinBy` to prelude
- fix highlighting for char in vscode
- better errors for missing imports
2024-12-28 09:45:21 -08:00

201 lines
6.0 KiB
Agda
Raw 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 GT
in1 : String
in2 : String
op : Op
out : String
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 $ GT 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 (GT i1 i2 op out) =
if out == a then GT i1 i2 op g
else if out == g then GT i1 i2 op a
else GT i1 i2 op out
fail : a. String -> a
fail msg = let x = trace "FAIL" msg in ?
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
putStrLn text
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"