import Control.Arrow
import Data.Char
import Data.List
import Text.ParserCombinators.ReadP
op "*" = product
op "+" = sum
part1 s = sum $ zipWith ($) (op <$> a) (transpose $ fmap read <$> as)
where
(a : as) = reverse . fmap words . lines $ s
parseGroups = fst . last . readP_to_S (sepBy (endBy int eol) eol) . filter (/= ' ')
where
eol = char '\n'
int = read <$> munch1 isDigit :: ReadP Int
part2 s = sum $ zipWith ($) (op <$> words a) (parseGroups . unlines $ reverse <$> transpose as)
where
(a : as) = reverse $ lines s
main = getContents >>= print . (part1 &&& part2)
I think I could have avoided the minimumBy hack by doing another reverse and changing the indices.
import Data.List
import Data.Function
import Control.Arrow
parse = fmap (fmap (read . pure)) . lines
solve n = sum . fmap (sum . zipWith (*) (iterate (*10) 1) . reverse . go n)
where
go :: Int -> [Int] -> [Int]
go 0 l = pure $ maximum l
go n l = mx : go (n-1) (drop idx l)
where
-- use minimumBy since if there are multiple least elements, we want the leftmost one.
(idx, mx) = minimumBy (compare `on` (negate . snd)) . zip [1..] . take (length l - n) $ l
main = getContents >>= print . (solve 1 &&& solve 11) . parse
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Either
import Data.Text hiding (all, head, zipWith)
import Data.Text qualified as T
import Data.Text.IO as TIO
type Pins = [Int]
toKeyLock :: [Text] -> Either Pins Pins
toKeyLock v = (if T.head (head v) == '#' then Left else Right) $ fmap (pred . count "#") v
solve keys locks = sum [1 | k <- keys, l <- locks, fit k l]
where
fit a b = all (<= 5) $ zipWith (+) a b
main = TIO.getContents >>= print . uncurry solve . partitionEithers . fmap (toKeyLock . transpose . T.lines) . splitOn "\n\n"
For part2 I compared the bits in the solution of part1 with the sum of x and y. With that, I could check the bits that did not match in a graphviz diagram and work from there.
::: spoiler code
import Control.Arrow
import Control.Monad.RWS
import Data.Bits (shiftL)
import Data.Char (digitToInt)
import Data.Functor
import Data.List
import Data.Map qualified as M
import Data.Tuple
import Text.ParserCombinators.ReadP hiding (get)
import Text.ParserCombinators.ReadP qualified as ReadP
type Cable = String
data Connection = And Cable Cable | Or Cable Cable | Xor Cable Cable deriving (Show)
cable = count 3 ReadP.get
eol = char '\n'
initial :: ReadP (M.Map Cable Bool)
initial = M.fromList <$> endBy ((,) <$> cable <*> (string ": " *> (toEnum . digitToInt <$> ReadP.get))) eol
wires = M.fromList <$> endBy wire eol
wire = do
a <- cable <* char ' '
op <- choice [string "AND" $> And, string "OR" $> Or, string "XOR" $> Xor]
b <- char ' ' *> cable
c <- string " -> " *> cable
return (c, op a b)
parse = fst . last . readP_to_S ((,) <$> initial <*> (eol *> wires <* eof))
type Problem = RWS (M.Map Cable Connection) () (M.Map Cable Bool)
getConnection :: Connection -> Problem Bool
getConnection (And a b) = (&&) <$> getWire a <*> getWire b
getConnection (Or a b) = (||) <$> getWire a <*> getWire b
getConnection (Xor a b) = xor <$> getWire a <*> getWire b
xor True False = True
xor False True = True
xor _ _ = False
getWire :: Cable -> Problem Bool
getWire cable = do
let computed = do
a <- asks (M.! cable) >>= getConnection
modify (M.insert cable a)
return a
gets (M.!? cable) >>= maybe computed return
fromBin :: [Bool] -> Int
fromBin = sum . fmap fst . filter snd . zip (iterate (`shiftL` 1) 1)
toBin :: Int -> [Bool]
toBin = unfoldr (\v -> if v == 0 then Nothing else Just (first (== 1) (swap (divMod v 2))))
part1 initial wiring = fst $ evalRWS (mapM getWire zs) wiring initial
where
zs = filter ((== 'z') . head) . sort $ M.keys wiring
part2 initial wiring = fmap fst . filter snd $ zip [0..] (zipWith (/=) p1 expect)
where
xs = fromBin . fmap (initial M.!) . filter ((== 'x') . head) $ sort $ M.keys initial
ys = fromBin . fmap (initial M.!) . filter ((== 'y') . head) $ sort $ M.keys initial
zs = filter ((== 'z') . head) . sort $ M.keys wiring
p1 = part1 initial wiring
expect = toBin $ xs + ys
main = getContents >>= print . (fromBin . uncurry part1 &&& uncurry part2) . parse
Spent a lot of time trying to find symmetric quadrants. In the end made an interactive visualization and found that a weird pattern appeared on iterations (27 + 101k) and (75 + 103k'). Put those congruences in an online Chinese remainder theorem calculator and go to the answer: x ≡ 8006 (mod 101*103)
import Data.Bifunctor
import Data.Char
import qualified Data.Set as S
import Data.Functor
import Data.List
import Control.Monad
import Text.ParserCombinators.ReadP
import Data.IORef
bounds = (101, 103)
parseInt :: ReadP Int
parseInt = (*) <$> option 1 (char '-' $> (-1)) <*> (read <$> munch1 isDigit)
parseTuple = (,) <$> parseInt <*> (char ',' *> parseInt)
parseRow = (,) <$> (string "p=" *> parseTuple) <*> (string " v=" *> parseTuple)
parse = fst . last . readP_to_S (endBy parseRow (char '\n'))
move t (x, y) (vx, vy) = bimap (mod (x + vx * t)) (mod (y + vy * t)) bounds
getQuadrant :: (Int, Int) -> Int
getQuadrant (x, y)
| x == mx || y == my = 0
| otherwise = case (x > mx, y > my) of
(True, True) -> 1
(True, False) -> 2
(False, True) -> 3
(False, False) -> 4
where
(mx, my) = bimap (`div` 2) (`div` 2) bounds
step (x, y) (vx, vy) = (,(vx, vy)) $ bimap (mod (x + vx)) (mod (y + vy)) bounds
main = do
p <- parse <$> readFile "input14"
print . product . fmap length . group . sort . filter (/=0) . fmap (getQuadrant . uncurry (move 100)) $ p
let l = iterate (fmap (uncurry step)) p
current <- newIORef 0
actions <- lines <$> getContents
forM_ actions $ \a -> do
case a of
"" -> modifyIORef current (+1)
"+" -> modifyIORef current (+1)
"-" -> modifyIORef current (subtract 1)
n -> writeIORef current (read n)
pos <- readIORef current
putStr "\ESC[2J" -- clear screen
print pos
visualize $ fst <$> l !! pos
visualize :: [(Int, Int)] -> IO ()
visualize pos = do
let p = S.fromList pos
forM_ [1..(snd bounds)] $ \y -> do
forM_ [1..(fst bounds)] $ \x -> do
putChar $ if S.member (x, y) p then '*' else '.'
putChar '\n'
import Data.Monoid
import Control.Arrow
data Tree v = Tree (Tree v) v (Tree v)
-- https://stackoverflow.com/questions/3208258
memo1 f = index nats
where
nats = go 0 1
go i s = Tree (go (i + s) s') (f i) (go (i + s') s')
where
s' = 2 * s
index (Tree l v r) i
| i < 0 = f i
| i == 0 = v
| otherwise = case (i - 1) `divMod` 2 of
(i', 0) -> index l i'
(i', 1) -> index r i'
memo2 f = memo1 (memo1 . f)
blink = memo2 blink'
where
blink' c n
| c == 0 = 1
| n == 0 = blink c' 1
| even digits = blink c' l <> blink c' r
| otherwise = blink c' $ n * 2024
where
digits = succ . floor . logBase 10 . fromIntegral $ n
(l, r) = n `divMod` (10 ^ (digits `div` 2))
c' = pred c
doBlinks n = getSum . mconcat . fmap (blink n)
part1 = doBlinks 25
part2 = doBlinks 75
main = getContents >>= print . (part1 &&& part2) . fmap read . words
Haskell
Haskell
Haskell
Haskell
Haskell
Haskell
I think I could have avoided the minimumBy hack by doing another reverse and changing the indices.
Haskell
Haskell
Merry Christmas!
Haskell
For part2 I compared the bits in the solution of part1 with the sum of x and y. With that, I could check the bits that did not match in a graphviz diagram and work from there.
::: spoiler code
:::
Haskell
::: spoiler solution
:::
Haskell
::: spoiler solution
:::
Haskell
::: spoiler solution
:::
Haskell
::: spoiler solution
:::
Haskell
::: spoiler code
Haskell
Spent a lot of time trying to find symmetric quadrants. In the end made an interactive visualization and found that a weird pattern appeared on iterations (27 + 101k) and (75 + 103k'). Put those congruences in an online Chinese remainder theorem calculator and go to the answer:
x ≡ 8006 (mod 101*103)Haskell
Haskell
Haskell
Quite messy
Haskell
Love the fold on the list monad to apply the operations.