diff --git a/AdventOfCode2020.cabal b/AdventOfCode2020.cabal index ddb5e5a..cbd2276 100644 --- a/AdventOfCode2020.cabal +++ b/AdventOfCode2020.cabal @@ -45,6 +45,7 @@ library Day12.Solution Day13.Solution Day14.Solution + Day15.Solution Day16.Parser Day16.Solution Day16.Utils @@ -93,6 +94,7 @@ test-suite AdventOfCode2020-test Day12.SolutionSpec Day13.SolutionSpec Day14.SolutionSpec + Day15.SolutionSpec Day16.SolutionSpec Day16.UtilsSpec Day17.SolutionSpec diff --git a/src/Day15/README.md b/src/Day15/README.md new file mode 100644 index 0000000..e710374 --- /dev/null +++ b/src/Day15/README.md @@ -0,0 +1,60 @@ +## Day 15: Rambunctious Recitation + +You catch the airport shuttle and try to book a new flight to your vacation island. Due to the storm, all direct flights have been cancelled, but a route is available to get around the storm. You take it. + +While you wait for your flight, you decide to check in with the Elves back at the North Pole. They're playing a _memory game_ and are ever so excited to explain the rules! + +In this game, the players take turns saying _numbers_ . They begin by taking turns reading from a list of _starting numbers_ (your puzzle input). Then, each turn consists of considering the _most recently spoken number_ : + +- If that was the _first_ time the number has been spoken, the current player says _`0`_ . +- Otherwise, the number had been spoken before; the current player announces _how many turns apart_ the number is from when it was previously spoken. + +So, after the starting numbers, each turn results in that player speaking aloud either _`0`_ (if the last number is new) or an _age_ (if the last number is a repeat). + +For example, suppose the starting numbers are `0,3,6` : + +- _Turn 1_ : The `1` st number spoken is a starting number, _`0`_ . +- _Turn 2_ : The `2` nd number spoken is a starting number, _`3`_ . +- _Turn 3_ : The `3` rd number spoken is a starting number, _`6`_ . +- _Turn 4_ : Now, consider the last number spoken, `6` . Since that was the first time the number had been spoken, the `4` th number spoken is _`0`_ . +- _Turn 5_ : Next, again consider the last number spoken, `0` . Since it _had_ been spoken before, the next number to speak is the difference between the turn number when it was last spoken (the previous turn, `4` ) and the turn number of the time it was most recently spoken before then (turn `1` ). Thus, the `5` th number spoken is `4 - 1` , _`3`_ . +- _Turn 6_ : The last number spoken, `3` had also been spoken before, most recently on turns `5` and `2` . So, the `6` th number spoken is `5 - 2` , _`3`_ . +- _Turn 7_ : Since `3` was just spoken twice in a row, and the last two turns are `1` turn apart, the `7` th number spoken is _`1`_ . +- _Turn 8_ : Since `1` is new, the `8` th number spoken is _`0`_ . +- _Turn 9_ : `0` was last spoken on turns `8` and `4` , so the `9` th number spoken is the difference between them, _`4`_ . +- _Turn 10_ : `4` is new, so the `10` th number spoken is _`0`_ . + +(The game ends when the Elves get sick of playing or dinner is ready, whichever comes first.) + +Their question for you is: what will be the _`2020` th_ number spoken? In the example above, the `2020` th number spoken will be `436` . + +Here are a few more examples: + +- Given the starting numbers `1,3,2` , the `2020` th number spoken is `1` . +- Given the starting numbers `2,1,3` , the `2020` th number spoken is `10` . +- Given the starting numbers `1,2,3` , the `2020` th number spoken is `27` . +- Given the starting numbers `2,3,1` , the `2020` th number spoken is `78` . +- Given the starting numbers `3,2,1` , the `2020` th number spoken is `438` . +- Given the starting numbers `3,1,2` , the `2020` th number spoken is `1836` . + +Given your starting numbers, _what will be the `2020` th number spoken?_ + +## Part Two + +Impressed, the Elves issue you a challenge: determine the `30000000` th number spoken. For example, given the same starting numbers as above: + +- Given `0,3,6` , the `30000000` th number spoken is `175594` . +- Given `1,3,2` , the `30000000` th number spoken is `2578` . +- Given `2,1,3` , the `30000000` th number spoken is `3544142` . +- Given `1,2,3` , the `30000000` th number spoken is `261214` . +- Given `2,3,1` , the `30000000` th number spoken is `6895259` . +- Given `3,2,1` , the `30000000` th number spoken is `18` . +- Given `3,1,2` , the `30000000` th number spoken is `362` . + +Given your starting numbers, _what will be the `30000000` th number spoken?_ + +## Link + +[https://adventofcode.com/2020/day/15][1] + +[1]: https://adventofcode.com/2020/day/15 diff --git a/src/Day15/Solution.hs b/src/Day15/Solution.hs new file mode 100644 index 0000000..c051368 --- /dev/null +++ b/src/Day15/Solution.hs @@ -0,0 +1,43 @@ +module Day15.Solution (memoryGame, part1, part2) where + +import Advent.Utils (fromRightOrShowError, readInt) +import Control.Monad (liftM3) +import qualified Data.IntMap.Lazy as IntMap +import Text.Parsec + ( ParseError, + Parsec, + char, + digit, + endOfLine, + many1, + parse, + sepBy, + ) + +part1 :: String -> String +part1 = show . memoryGame 2020 . fromRightOrShowError . parseInts + +part2 :: String -> String +part2 = show . memoryGame 30000000 . fromRightOrShowError . parseInts + +parseInts :: String -> Either ParseError [Int] +parseInts = parse (intsParser <* endOfLine) "" + where + intsParser :: Parsec String () [Int] + intsParser = (readInt <$> many1 digit) `sepBy` char ',' + +type State = IntMap.IntMap Int + +memoryGame :: Int -> [Int] -> Int +memoryGame target = liftM3 go length last initialState + where + go :: Int -> Int -> State -> Int + go turn prev state + | turn == target = prev + | otherwise = go (succ turn) next nextState + where + next = (maybe 0 (turn -) . IntMap.lookup prev) state + nextState = IntMap.insert prev turn state + + initialState :: [Int] -> State + initialState = IntMap.fromList . flip zip [1 ..] . init diff --git a/test/Day15/SolutionSpec.hs b/test/Day15/SolutionSpec.hs new file mode 100644 index 0000000..9a95738 --- /dev/null +++ b/test/Day15/SolutionSpec.hs @@ -0,0 +1,73 @@ +module Day15.SolutionSpec (spec) where + +import Data.Foldable (for_) +import Day15.Solution (memoryGame, part1, part2) +import Test.Hspec + +spec :: Spec +spec = parallel $ do + it "solves Part 1" $ do + input <- readFile "./test/Day15/input.txt" + part1 input `shouldBe` "694" + -- TODO: this runs in ~40 seconds with production optimizations + xit "solves Part 2" $ do + input <- readFile "./test/Day15/input.txt" + part2 input `shouldBe` "21768614" + describe "memoryGame" $ do + context "when looking at the 2020th result" $ do + let n = 2020 + let cases = + [ ([0, 3, 6], 436), + ([1, 3, 2], 1), + ([2, 1, 3], 10), + ([1, 2, 3], 27), + ([2, 3, 1], 78), + ([3, 2, 1], 438), + ([3, 1, 2], 1836) + ] + let test (input, expected) = it ("is " ++ show expected ++ " for input " ++ show input) $ do + memoryGame n input `shouldBe` expected + + for_ cases test + + -- TODO: figure out how to make this not take 10 minutes + xcontext "when looking at the 30000000th result" $ do + let n = 30000000 + let cases = + [ ([0, 3, 6], 175594), + ([1, 3, 2], 2578), + ([2, 1, 3], 3544142), + ([1, 2, 3], 261214), + ([2, 3, 1], 6895259), + ([3, 2, 1], 18), + ([3, 1, 2], 362) + ] + let test (input, expected) = it ("is " ++ show expected ++ " for input " ++ show input) $ do + memoryGame n input `shouldBe` expected + + for_ cases test + +-- 300 +-- Finished in 0.0017 seconds +-- Finished in 0.0011 seconds + +-- 3000 +-- Finished in 0.0061 seconds +-- Finished in 0.0052 seconds + +-- 30000 +-- Finished in 0.1399 seconds +-- Finished in 0.1418 seconds + +-- 300000 +-- Finished in 1.7987 seconds +-- Finished in 1.8572 seconds +-- Finished in 1.7550 seconds + +-- 3000000 +-- Finished in 30.7137 seconds +-- Finished in 26.4237 seconds +-- Finished in 25.2911 seconds + +-- 30000000 +-- TODO: Need to get here! diff --git a/test/Day15/input.txt b/test/Day15/input.txt new file mode 100644 index 0000000..904566a --- /dev/null +++ b/test/Day15/input.txt @@ -0,0 +1 @@ +17,1,3,16,19,0