From ffe0f250d66cbd3461a4b376b8a846ef84fbb5b1 Mon Sep 17 00:00:00 2001 From: "Yann Esposito (Yogsototh)" Date: Wed, 13 Dec 2017 08:52:51 +0100 Subject: [PATCH] Day13 with 2 solution2 one naive, the other one faster --- adventofcode.cabal | 7 +- app/Main.hs | 12 ++ inputs/day13.txt | 44 +++++ package.yaml | 3 + src/Day13.hs | 429 +++++++++++++++++++++++++++++++++++++++++++++ test/Spec.hs | 15 +- 6 files changed, 506 insertions(+), 4 deletions(-) create mode 100644 inputs/day13.txt create mode 100644 src/Day13.hs diff --git a/adventofcode.cabal b/adventofcode.cabal index d43590a..b091949 100644 --- a/adventofcode.cabal +++ b/adventofcode.cabal @@ -2,7 +2,7 @@ -- -- see: https://github.com/sol/hpack -- --- hash: 3f3cbe111f1f469760a75a30c1b54e4cf67cf5ca5a968c511d1e0fe4bac65a32 +-- hash: 454d3692225cbc9c59993888515dbf7f9c46a2d886ffebc91072c08157888c4d name: adventofcode version: 0.1.0.0 @@ -42,6 +42,7 @@ library Day10 Day11 Day12 + Day13 other-modules: Paths_adventofcode build-depends: @@ -58,7 +59,7 @@ executable adventofcode-exe hs-source-dirs: app main-is: Main.hs - ghc-options: -threaded -rtsopts -with-rtsopts=-N + ghc-options: -O2 -threaded -rtsopts -with-rtsopts=-N build-depends: adventofcode , base @@ -82,5 +83,5 @@ test-suite adventofcode-test , tasty-hunit other-modules: Paths_adventofcode - ghc-options: -threaded -rtsopts -with-rtsopts=-N + ghc-options: -O2 -threaded -rtsopts -with-rtsopts=-N default-language: Haskell2010 diff --git a/app/Main.hs b/app/Main.hs index 33271ff..f2cf0ee 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -18,6 +18,7 @@ import qualified Day09 import qualified Day10 import qualified Day11 import qualified Day12 +import qualified Day13 showSol :: [Char] -> Doc -> IO () showSol txt d = putText . toS . render $ @@ -41,6 +42,7 @@ solutions = Map.fromList [(["01"], day01) ,(["10"], day10) ,(["11"], day11) ,(["12"], day12) + ,(["13"], day13) ] day01 :: IO () @@ -125,3 +127,13 @@ day12 = do showSol "Solution 1" (int (fromMaybe 0 sol1)) let sol2 = fmap Day12.solution2 input showSol "Solution 2" (int (fromMaybe 0 sol2)) + +day13 :: IO () +day13 = do + putText "Day 13:" + input <- Day13.input + let sol1 = fmap Day13.solution1 input + showSol "Solution 1" (int (fromMaybe 0 sol1)) + input2 <- Day13.parseInput + let sol2 = fmap Day13.solution2 input2 + showSol "Solution 2" (int (fromMaybe 0 sol2)) diff --git a/inputs/day13.txt b/inputs/day13.txt new file mode 100644 index 0000000..269c1e7 --- /dev/null +++ b/inputs/day13.txt @@ -0,0 +1,44 @@ +0: 3 +1: 2 +2: 4 +4: 4 +6: 5 +8: 6 +10: 8 +12: 8 +14: 6 +16: 6 +18: 8 +20: 8 +22: 6 +24: 12 +26: 9 +28: 12 +30: 8 +32: 14 +34: 12 +36: 8 +38: 14 +40: 12 +42: 12 +44: 12 +46: 14 +48: 12 +50: 14 +52: 12 +54: 10 +56: 14 +58: 12 +60: 14 +62: 14 +66: 10 +68: 14 +74: 14 +76: 12 +78: 14 +80: 20 +86: 18 +92: 14 +94: 20 +96: 18 +98: 17 diff --git a/package.yaml b/package.yaml index 2e1a88d..befc5b0 100644 --- a/package.yaml +++ b/package.yaml @@ -25,6 +25,7 @@ library: - Day10 - Day11 - Day12 + - Day13 dependencies: - base >=4.7 && <5 - protolude @@ -38,6 +39,7 @@ executables: main: Main.hs source-dirs: app ghc-options: + - -O2 - -threaded - -rtsopts - -with-rtsopts=-N @@ -52,6 +54,7 @@ tests: main: Spec.hs source-dirs: test ghc-options: + - -O2 - -threaded - -rtsopts - -with-rtsopts=-N diff --git a/src/Day13.hs b/src/Day13.hs new file mode 100644 index 0000000..552b5f6 --- /dev/null +++ b/src/Day13.hs @@ -0,0 +1,429 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} +{-| +description: + +You need to cross a vast firewall. The firewall consists of several layers, each +with a security scanner that moves back and forth across the layer. To succeed, +you must not be detected by a scanner. + +By studying the firewall briefly, you are able to record (in your puzzle input) +the depth of each layer and the range of the scanning area for the scanner +within it, written as depth: range. Each layer has a thickness of exactly 1. A +layer at depth 0 begins immediately inside the firewall; a layer at depth 1 +would start immediately after that. + +For example, suppose you've recorded the following: + +0: 3 +1: 2 +4: 4 +6: 4 + +This means that there is a layer immediately inside the firewall (with range 3), +a second layer immediately after that (with range 2), a third layer which begins +at depth 4 (with range 4), and a fourth layer which begins at depth 6 (also with +range 4). Visually, it might look like this: + + 0 1 2 3 4 5 6 +[ ] [ ] ... ... [ ] ... [ ] +[ ] [ ] [ ] [ ] +[ ] [ ] [ ] + [ ] [ ] + +Within each layer, a security scanner moves back and forth within its range. +Each security scanner starts at the top and moves down until it reaches the +bottom, then moves up until it reaches the top, and repeats. A security scanner +takes one picosecond to move one step. Drawing scanners as S, the first few +picoseconds look like this: + + +Picosecond 0: + + 0 1 2 3 4 5 6 +[S] [S] ... ... [S] ... [S] +[ ] [ ] [ ] [ ] +[ ] [ ] [ ] + [ ] [ ] + +Picosecond 1: + + 0 1 2 3 4 5 6 +[ ] [ ] ... ... [ ] ... [ ] +[S] [S] [S] [S] +[ ] [ ] [ ] + [ ] [ ] + +Picosecond 2: + + 0 1 2 3 4 5 6 +[ ] [S] ... ... [ ] ... [ ] +[ ] [ ] [ ] [ ] +[S] [S] [S] + [ ] [ ] + +Picosecond 3: + + 0 1 2 3 4 5 6 +[ ] [ ] ... ... [ ] ... [ ] +[S] [S] [ ] [ ] +[ ] [ ] [ ] + [S] [S] + +Your plan is to hitch a ride on a packet about to move through the firewall. The +packet will travel along the top of each layer, and it moves at one layer per +picosecond. Each picosecond, the packet moves one layer forward (its first move +takes it into layer 0), and then the scanners move one step. If there is a +scanner at the top of the layer as your packet enters it, you are caught. (If a +scanner moves into the top of its layer while you are there, you are not caught: +it doesn't have time to notice you before you leave.) If you were to do this in +the configuration above, marking your current position with parentheses, your +passage through the firewall would look like this: + +Initial state: + + 0 1 2 3 4 5 6 +[S] [S] ... ... [S] ... [S] +[ ] [ ] [ ] [ ] +[ ] [ ] [ ] + [ ] [ ] + +Picosecond 0: + + 0 1 2 3 4 5 6 +(S) [S] ... ... [S] ... [S] +[ ] [ ] [ ] [ ] +[ ] [ ] [ ] + [ ] [ ] + + 0 1 2 3 4 5 6 +( ) [ ] ... ... [ ] ... [ ] +[S] [S] [S] [S] +[ ] [ ] [ ] + [ ] [ ] + + +Picosecond 1: + + 0 1 2 3 4 5 6 +[ ] ( ) ... ... [ ] ... [ ] +[S] [S] [S] [S] +[ ] [ ] [ ] + [ ] [ ] + + 0 1 2 3 4 5 6 +[ ] (S) ... ... [ ] ... [ ] +[ ] [ ] [ ] [ ] +[S] [S] [S] + [ ] [ ] + + +Picosecond 2: + + 0 1 2 3 4 5 6 +[ ] [S] (.) ... [ ] ... [ ] +[ ] [ ] [ ] [ ] +[S] [S] [S] + [ ] [ ] + + 0 1 2 3 4 5 6 + +[ ] [ ] (.) ... [ ] ... [ ] +[S] [S] [ ] [ ] +[ ] [ ] [ ] + [S] [S] + + +Picosecond 3: + + 0 1 2 3 4 5 6 +[ ] [ ] ... (.) [ ] ... [ ] +[S] [S] [ ] [ ] +[ ] [ ] [ ] + [S] [S] + + 0 1 2 3 4 5 6 +[S] [S] ... (.) [ ] ... [ ] +[ ] [ ] [ ] [ ] +[ ] [S] [S] + [ ] [ ] + + +Picosecond 4: + + 0 1 2 3 4 5 6 +[S] [S] ... ... ( ) ... [ ] +[ ] [ ] [ ] [ ] +[ ] [S] [S] + [ ] [ ] + + 0 1 2 3 4 5 6 +[ ] [ ] ... ... ( ) ... [ ] +[S] [S] [S] [S] +[ ] [ ] [ ] + [ ] [ ] + + +Picosecond 5: + + 0 1 2 3 4 5 6 +[ ] [ ] ... ... [ ] (.) [ ] +[S] [S] [S] [S] +[ ] [ ] [ ] + [ ] [ ] + + 0 1 2 3 4 5 6 +[ ] [S] ... ... [S] (.) [S] +[ ] [ ] [ ] [ ] +[S] [ ] [ ] + [ ] [ ] + + +Picosecond 6: + + 0 1 2 3 4 5 6 +[ ] [S] ... ... [S] ... (S) +[ ] [ ] [ ] [ ] +[S] [ ] [ ] + [ ] [ ] + + 0 1 2 3 4 5 6 +[ ] [ ] ... ... [ ] ... ( ) +[S] [S] [S] [S] +[ ] [ ] [ ] + [ ] [ ] + +In this situation, you are caught in layers 0 and 6, because your packet entered +the layer when its scanner was at the top when you entered it. You are not +caught in layer 1, since the scanner moved into the top of the layer once you +were already there. + +The severity of getting caught on a layer is equal to its depth multiplied by +its range. (Ignore layers in which you do not get caught.) The severity of the +whole trip is the sum of these values. In the example above, the trip severity +is 0*3 + 6*4 = 24. + +Given the details of the firewall you've recorded, if you leave immediately, +what is the severity of your whole trip? + +--- Part Two --- + +Now, you need to pass through the firewall without being caught - easier said +than done. + +You can't control the speed of the packet, but you can delay it any number of +picoseconds. For each picosecond you delay the packet before beginning your +trip, all security scanners move one step. You're not in the firewall during +this time; you don't enter layer 0 until you stop delaying the packet. + +In the example above, if you delay 10 picoseconds (picoseconds 0 - 9), you won't +get caught: + +State after delaying: + + 0 1 2 3 4 5 6 +[ ] [S] ... ... [ ] ... [ ] +[ ] [ ] [ ] [ ] +[S] [S] [S] + [ ] [ ] + +Picosecond 10: + 0 1 2 3 4 5 6 +( ) [S] ... ... [ ] ... [ ] +[ ] [ ] [ ] [ ] +[S] [S] [S] + [ ] [ ] + + 0 1 2 3 4 5 6 +( ) [ ] ... ... [ ] ... [ ] +[S] [S] [S] [S] +[ ] [ ] [ ] + [ ] [ ] + + +Picosecond 11: + 0 1 2 3 4 5 6 +[ ] ( ) ... ... [ ] ... [ ] +[S] [S] [S] [S] +[ ] [ ] [ ] + [ ] [ ] + + 0 1 2 3 4 5 6 +[S] (S) ... ... [S] ... [S] +[ ] [ ] [ ] [ ] +[ ] [ ] [ ] + [ ] [ ] + + +Picosecond 12: + 0 1 2 3 4 5 6 +[S] [S] (.) ... [S] ... [S] +[ ] [ ] [ ] [ ] +[ ] [ ] [ ] + [ ] [ ] + + 0 1 2 3 4 5 6 +[ ] [ ] (.) ... [ ] ... [ ] +[S] [S] [S] [S] +[ ] [ ] [ ] + [ ] [ ] + + +Picosecond 13: + 0 1 2 3 4 5 6 +[ ] [ ] ... (.) [ ] ... [ ] +[S] [S] [S] [S] +[ ] [ ] [ ] + [ ] [ ] + + 0 1 2 3 4 5 6 +[ ] [S] ... (.) [ ] ... [ ] +[ ] [ ] [ ] [ ] +[S] [S] [S] + [ ] [ ] + + +Picosecond 14: + 0 1 2 3 4 5 6 +[ ] [S] ... ... ( ) ... [ ] +[ ] [ ] [ ] [ ] +[S] [S] [S] + [ ] [ ] + + 0 1 2 3 4 5 6 +[ ] [ ] ... ... ( ) ... [ ] +[S] [S] [ ] [ ] +[ ] [ ] [ ] + [S] [S] + + +Picosecond 15: + 0 1 2 3 4 5 6 +[ ] [ ] ... ... [ ] (.) [ ] +[S] [S] [ ] [ ] +[ ] [ ] [ ] + [S] [S] + + 0 1 2 3 4 5 6 +[S] [S] ... ... [ ] (.) [ ] +[ ] [ ] [ ] [ ] +[ ] [S] [S] + [ ] [ ] + + +Picosecond 16: + 0 1 2 3 4 5 6 +[S] [S] ... ... [ ] ... ( ) +[ ] [ ] [ ] [ ] +[ ] [S] [S] + [ ] [ ] + + 0 1 2 3 4 5 6 +[ ] [ ] ... ... [ ] ... ( ) +[S] [S] [S] [S] +[ ] [ ] [ ] + [ ] [ ] + +Because all smaller delays would get you caught, the fewest number of +picoseconds you would need to delay to get through safely is 10. + +What is the fewest number of picoseconds that you need to delay the packet to +pass through the firewall without being caught? + +|-} +module Day13 where + +import Protolude + +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as Map +import qualified Data.Text as T + +testInput :: Text +testInput = "0: 3\n\ + \1: 2\n\ + \4: 4\n\ + \6: 4\n" + +parseInput :: IO (Maybe (Map Int Int)) +parseInput = parseTxt <$> readFile "inputs/day13.txt" + +input :: IO (Maybe AppState) +input = fmap mkAppState <$> parseInput + +parseTxt :: Text -> Maybe (Map Int Int) +parseTxt txt = + txt & T.lines & map T.words & traverse parseLine & fmap Map.fromList + where + parseLine :: [Text] -> Maybe (Int,Int) + parseLine (n:d:_) = (,) <$> txtToInt n <*> txtToInt d + +txtToInt :: Text -> Maybe Int +txtToInt = fmap fst . head . reads . toS + +data AppState = AppState { position :: Int + , severity :: Int + , caughted :: Bool -- ^ Just for solution2 naive + , fwState :: Map Int (Int,Int) + , fwDepth :: Map Int Int + } deriving (Show) + +mkAppState :: Map Int Int -> AppState +mkAppState depths = AppState 0 0 False (fmap (const (0,1)) depths) depths + +oneStep :: AppState -> AppState +oneStep st = + let caught = (== Just 0) (fmap fst (Map.lookup (position st) (fwState st))) + newpos = (+1) (position st) + newfwst = Map.mapWithKey + (\i (v,dir) -> let d = fromMaybe 0 (Map.lookup i (fwDepth st)) in + if (v + dir) >= d || (v+dir) < 0 + then (v - dir,-dir) + else (v+dir,dir)) + (fwState st) + newseverity = if caught + then severity st + + position st * fromMaybe 0 (Map.lookup (position st) (fwDepth st)) + else severity st + in AppState newpos newseverity (caught || caughted st) newfwst (fwDepth st) + +solution1Debug :: Maybe AppState -> IO Int +solution1Debug (Just st) = do + print st + if position st > maximum (Map.keys (fwDepth st)) + then return $ severity st + else solution1Debug (Just (oneStep st)) + +solution1 :: AppState -> Int +solution1 st = + if position st > maximum (Map.keys (fwDepth st)) + then severity st + else solution1 (oneStep st) + + +passes :: AppState -> Bool +passes st = + if caughted st || position st > maximum (Map.keys (fwDepth st)) + then not (caughted st) + else passes (oneStep st) + +-- | TOO LONG +solution2naive :: AppState -> Int +solution2naive st = + if passes st + then - (position st) + else solution2naive (st { position = position st - 1 }) + +caughtToken :: Int -> Int -> Int -> Bool +caughtToken delay x depth = (x + delay) `rem` (2*(depth - 1)) == 0 + +collisions :: Int -> Map Int Int -> [Bool] +collisions delay depths = Map.mapWithKey (caughtToken delay) depths & Map.elems + +solution2 :: Map Int Int -> Int +solution2 depths = go depths 0 + where + go depths delay = + if not (or (collisions delay depths)) + then delay + else go depths (delay + 1) diff --git a/test/Spec.hs b/test/Spec.hs index 3e2bba5..eb7a528 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -13,6 +13,7 @@ import qualified Day08 import qualified Day10 import qualified Day11 import qualified Day12 +import qualified Day13 main :: IO () main = defaultMain $ @@ -116,4 +117,16 @@ main = defaultMain $ fmap Day12.solution2 (Day12.parseTxt Day12.testTxt) @?= Just 2 ] ] - ] + , testGroup "Day 13" + [ testGroup "Solution 1" + [ testCase "Example" $ + fmap Day13.solution1 + (fmap Day13.mkAppState + (Day13.parseTxt Day13.testInput)) @?= Just 24 + ] + , testGroup "Solution 2" + [ testCase "Example" $ + fmap Day13.solution2 (Day13.parseTxt Day13.testInput) @?= Just 10 + ] + ] + ]