-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrun.hs
125 lines (108 loc) · 3.79 KB
/
run.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
import Control.Monad (forM_)
import Control.Monad.ST (ST, runST)
import Data.List
import Data.Maybe (mapMaybe)
import Text.Read (readMaybe)
import Data.Vector.Unboxed (Vector)
import qualified Data.Vector.Unboxed as V
import Data.Vector.Unboxed.Mutable (MVector)
import qualified Data.Vector.Unboxed.Mutable as VM
type Cup = Int
data Cups = Cups { cups :: !(Vector Int)
, currentCup :: !Cup
, lastCup :: !Cup }
deriving (Show, Eq)
parseAll :: String -> [Cup]
parseAll = mapMaybe (readMaybe @Cup . pure)
setup :: Cup -> [Cup] -> Cups
setup maxCup cs =
let l = maxCup - 1
c = head cs - 1
wrap | length cs == maxCup = (last shifted, head shifted)
| otherwise = (l, c)
glue | length cs == maxCup = (last shifted, head shifted)
| otherwise = (last shifted, maximum shifted + 1)
base = V.enumFromN 1 maxCup
shifted = map (\x -> x - 1) cs
firstPart =
wrap:glue:zip shifted (drop 1 shifted)
v = V.modify (flip update firstPart) base
in Cups v c (fst wrap)
cupsToList :: Cups -> [Cup]
cupsToList c = cupsToList' (1 + currentCup c) c
cupsToList' :: Cup -> Cups -> [Cup]
cupsToList' root Cups {..} =
let cs = map (+1) $ iterate' (cups V.!) (root - 1)
in root:takeWhile (/= root) (drop 1 cs)
update :: MVector s Int -> [(Int, Int)] -> ST s ()
update mv toUpdate =
forM_ toUpdate $ \(from, to) -> VM.write mv from to
readN :: Int -> Int -> MVector s Int -> ST s [Int]
readN n sidx v = go [] sidx n v
where go acc _ 0 _ = pure (sidx:reverse acc)
go acc idx i cs = do
idx' <- VM.read cs idx
go (idx':acc) idx' (i - 1) cs
moveN :: Int -> Cup -> Cups -> ST s Cups
moveN n cupMax Cups {..} = V.thaw cups >>= go n currentCup lastCup
where go :: Int -> Int -> Int -> MVector s Int -> ST s Cups
go 0 curr l cs = Cups <$> V.freeze cs <*> pure curr <*> pure l
go i curr l cs =
readN 4 curr cs >>= \case
_:c1:c2:c3:_ -> do
let dests = filter (\x -> x /= c1 && x /= c2 && x /= c3)
. drop 1
$ iterate (\case 0 -> cupMax - 1
x -> x - 1) curr
dest = head dests
afterDest <- VM.read cs dest
afterC3 <- VM.read cs c3
let toInsert :: [(Int, Int)]
toInsert
| dest == l =
[ (dest, c1)
, (c3, curr)
, (curr, afterC3) ]
| otherwise =
[ (dest, c1)
, (c3, afterDest)
, (l, curr)
, (curr, afterC3) ]
last' = curr
curr' = afterC3
update cs toInsert
go (i - 1) curr' last' cs
_ -> error "unreachable"
part1 :: [Cup] -> String
part1 input =
let final = runST $ moveN 100
(maximum input)
(setup (length input) input)
result = concatMap show
. drop 1
$ cupsToList' 1 final
in result
part2 :: [Cup] -> Int
part2 input =
let end = 1_000_000
final = runST $ moveN 10_000_000
end
(setup end input)
result = take 2
. drop 1
$ cupsToList' 1 final
in product result
main :: IO ()
main = main' "input.txt"
exampleMain :: IO ()
exampleMain = main' "example.txt"
main' :: FilePath -> IO ()
main' file = do
input <- parseAll <$> readFile file
putStrLn (part1 input)
print (part2 input)