Day13 with 2 solution2 one naive, the other one faster

This commit is contained in:
Yann Esposito (Yogsototh) 2017-12-13 08:52:51 +01:00
parent 5467cee5e8
commit ffe0f250d6
Signed by untrusted user who does not match committer: yogsototh
GPG key ID: 7B19A4C650D59646
6 changed files with 506 additions and 4 deletions

View file

@ -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

View file

@ -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))

44
inputs/day13.txt Normal file
View file

@ -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

View file

@ -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

429
src/Day13.hs Normal file
View file

@ -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)

View file

@ -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
]
]
]