From 63b300bcba3dfd6506765ac80012724e86226415 Mon Sep 17 00:00:00 2001 From: Manu Phatak Date: Sat, 19 Dec 2020 20:37:22 -0800 Subject: [PATCH 1/7] Setup Day 15 --- AdventOfCode2020.cabal | 4 +++- src/Day15/README.md | 46 ++++++++++++++++++++++++++++++++++++++ src/Day15/Solution.hs | 7 ++++++ test/Day15/SolutionSpec.hs | 13 +++++++++++ test/Day15/input.txt | 1 + 5 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/Day15/README.md create mode 100644 src/Day15/Solution.hs create mode 100644 test/Day15/SolutionSpec.hs create mode 100644 test/Day15/input.txt diff --git a/AdventOfCode2020.cabal b/AdventOfCode2020.cabal index 48a8d68..c240e57 100644 --- a/AdventOfCode2020.cabal +++ b/AdventOfCode2020.cabal @@ -4,7 +4,7 @@ cabal-version: 1.12 -- -- see: https://github.com/sol/hpack -- --- hash: e78ddf6dce2617f27174da65331db5626d485fd819fb44b56027025d2bc6f425 +-- hash: c844676970a1c0e625e036cde50dfa251899be763618408076ec282f16050ea8 name: AdventOfCode2020 version: 2.0.2.0 @@ -44,6 +44,7 @@ library Day11.Solution Day13.Solution Day14.Solution + Day15.Solution Practice.Foldable Template.Solution other-modules: @@ -81,6 +82,7 @@ test-suite AdventOfCode2020-test Day11.SolutionSpec Day13.SolutionSpec Day14.SolutionSpec + Day15.SolutionSpec Practice.FoldableSpec Template.SolutionSpec Paths_AdventOfCode2020 diff --git a/src/Day15/README.md b/src/Day15/README.md new file mode 100644 index 0000000..f6bbe77 --- /dev/null +++ b/src/Day15/README.md @@ -0,0 +1,46 @@ +## 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?_ + +## 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..572c804 --- /dev/null +++ b/src/Day15/Solution.hs @@ -0,0 +1,7 @@ +module Day15.Solution (part1, part2) where + +part1 :: String -> String +part1 = head . lines + +part2 :: String -> String +part2 = head . lines diff --git a/test/Day15/SolutionSpec.hs b/test/Day15/SolutionSpec.hs new file mode 100644 index 0000000..a06ad22 --- /dev/null +++ b/test/Day15/SolutionSpec.hs @@ -0,0 +1,13 @@ +module Day15.SolutionSpec (spec) where + +import Day15.Solution (part1, part2) +import Test.Hspec + +spec :: Spec +spec = parallel $ do + xit "solves Part 1" $ do + input <- readFile "./test/Day15/input.txt" + part1 input `shouldBe` "hello santa" + xit "solves Part 2" $ do + input <- readFile "./test/Day15/input.txt" + part2 input `shouldBe` "hello santa" 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 From 99e04636c104ba1d696995af9e40b1c45c94e99c Mon Sep 17 00:00:00 2001 From: Manu Phatak Date: Sun, 20 Dec 2020 15:36:58 -0800 Subject: [PATCH 2/7] Solve part 1 --- src/Day15/Solution.hs | 29 +++++++++++++++++++++++++++-- test/Day15/SolutionSpec.hs | 30 +++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/Day15/Solution.hs b/src/Day15/Solution.hs index 572c804..d5ec5c0 100644 --- a/src/Day15/Solution.hs +++ b/src/Day15/Solution.hs @@ -1,7 +1,32 @@ -module Day15.Solution (part1, part2) where +module Day15.Solution where + +import Advent.Utils +import qualified Data.IntMap.Lazy as IntMap +import Text.Parsec hiding (State) part1 :: String -> String -part1 = head . lines +part1 = show . (!! (2020 -1)) . memoryGame . fromRightOrShowError . parseInts part2 :: String -> String part2 = head . lines + +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] +memoryGame xs = xs ++ go (length xs, last xs, initialState) + where + go :: (Int, Int, State) -> [Int] + go (turn, prev, state) = do + let next = maybe 0 (turn -) $ IntMap.lookup prev state + let nextState = IntMap.insert prev turn state + + next : go (succ turn, next, nextState) + + initialState :: State + initialState = IntMap.fromList $ zip (init xs) [1 ..] diff --git a/test/Day15/SolutionSpec.hs b/test/Day15/SolutionSpec.hs index a06ad22..772e91e 100644 --- a/test/Day15/SolutionSpec.hs +++ b/test/Day15/SolutionSpec.hs @@ -1,13 +1,37 @@ module Day15.SolutionSpec (spec) where -import Day15.Solution (part1, part2) +import Data.Foldable +import Data.Function +import Day15.Solution import Test.Hspec spec :: Spec spec = parallel $ do - xit "solves Part 1" $ do + it "solves Part 1" $ do input <- readFile "./test/Day15/input.txt" - part1 input `shouldBe` "hello santa" + part1 input `shouldBe` "694" xit "solves Part 2" $ do input <- readFile "./test/Day15/input.txt" part2 input `shouldBe` "hello santa" + describe "memoryGame" $ do + context "when taking the first 10 results" $ do + let startingNumbers = [0, 3, 6] :: [Int] + let expected = [0, 3, 6, 0, 3, 3, 1, 0, 4, 0] :: [Int] + it ("is " ++ show expected ++ " given starting numbers of " ++ show startingNumbers) $ do + (memoryGame startingNumbers & take 10) `shouldBe` expected + + context "when looking at the 2020th result" $ do + let cases :: [([Int], Int)] + 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 input !! (2020 - 1)) `shouldBe` expected + + for_ cases test From 71edc50ee0bb40b27801de30204ff8323559f4ae Mon Sep 17 00:00:00 2001 From: Manu Phatak Date: Sun, 20 Dec 2020 17:23:21 -0800 Subject: [PATCH 3/7] Solve part 2 --- src/Day15/Solution.hs | 28 ++++++++-------- test/Day15/SolutionSpec.hs | 67 ++++++++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/Day15/Solution.hs b/src/Day15/Solution.hs index d5ec5c0..8dc5f3e 100644 --- a/src/Day15/Solution.hs +++ b/src/Day15/Solution.hs @@ -1,14 +1,15 @@ module Day15.Solution where import Advent.Utils +import Control.Monad import qualified Data.IntMap.Lazy as IntMap import Text.Parsec hiding (State) part1 :: String -> String -part1 = show . (!! (2020 -1)) . memoryGame . fromRightOrShowError . parseInts +part1 = show . memoryGame 2020 . fromRightOrShowError . parseInts part2 :: String -> String -part2 = head . lines +part2 = show . memoryGame 30000000 . fromRightOrShowError . parseInts parseInts :: String -> Either ParseError [Int] parseInts = parse (intsParser <* endOfLine) "" @@ -18,15 +19,16 @@ parseInts = parse (intsParser <* endOfLine) "" type State = IntMap.IntMap Int -memoryGame :: [Int] -> [Int] -memoryGame xs = xs ++ go (length xs, last xs, initialState) +memoryGame :: Int -> [Int] -> Int +memoryGame target = liftM3 go length last initialState where - go :: (Int, Int, State) -> [Int] - go (turn, prev, state) = do - let next = maybe 0 (turn -) $ IntMap.lookup prev state - let nextState = IntMap.insert prev turn state - - next : go (succ turn, next, nextState) - - initialState :: State - initialState = IntMap.fromList $ zip (init xs) [1 ..] + 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 index 772e91e..1666b79 100644 --- a/test/Day15/SolutionSpec.hs +++ b/test/Day15/SolutionSpec.hs @@ -1,10 +1,13 @@ module Day15.SolutionSpec (spec) where import Data.Foldable -import Data.Function import Day15.Solution import Test.Hspec +type ContextCase = (Int, [Case]) + +type Case = ([Int], Int) + spec :: Spec spec = parallel $ do it "solves Part 1" $ do @@ -12,17 +15,11 @@ spec = parallel $ do part1 input `shouldBe` "694" xit "solves Part 2" $ do input <- readFile "./test/Day15/input.txt" - part2 input `shouldBe` "hello santa" + part2 input `shouldBe` "21768614" describe "memoryGame" $ do - context "when taking the first 10 results" $ do - let startingNumbers = [0, 3, 6] :: [Int] - let expected = [0, 3, 6, 0, 3, 3, 1, 0, 4, 0] :: [Int] - it ("is " ++ show expected ++ " given starting numbers of " ++ show startingNumbers) $ do - (memoryGame startingNumbers & take 10) `shouldBe` expected - - context "when looking at the 2020th result" $ do - let cases :: [([Int], Int)] - cases = + let cases2020 :: ContextCase + cases2020 = + ( 2020, [ ([0, 3, 6], 436), ([1, 3, 2], 1), ([2, 1, 3], 10), @@ -31,7 +28,49 @@ spec = parallel $ do ([3, 2, 1], 438), ([3, 1, 2], 1836) ] - let test (input, expected) = it ("is " ++ show expected ++ " for input " ++ show input) $ do - (memoryGame input !! (2020 - 1)) `shouldBe` expected + ) + let cases30000000 :: ContextCase + cases30000000 = + ( 30000000, + [ ([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 testContext :: ContextCase -> SpecWith () + testContext (n, cases) = context ("when looking at the " ++ show n ++ "th result") $ do + let test :: Case -> SpecWith () + test (input, expected) = it ("is " ++ show expected ++ " for input " ++ show input) $ do + memoryGame n input `shouldBe` expected + + for_ cases test + + testContext cases2020 + xcontext "skip" $ testContext cases30000000 + +-- 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 - for_ cases test +-- 3000000 +-- Finished in 30.7137 seconds +-- Finished in 26.4237 seconds +-- Finished in 25.2911 seconds From 3020dcc0627692a42ad4d027a5c1146c1081fb52 Mon Sep 17 00:00:00 2001 From: Manu Phatak Date: Sun, 20 Dec 2020 17:24:23 -0800 Subject: [PATCH 4/7] Use explicit imports --- src/Day15/Solution.hs | 17 +++++++++++++---- test/Day15/SolutionSpec.hs | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Day15/Solution.hs b/src/Day15/Solution.hs index 8dc5f3e..c051368 100644 --- a/src/Day15/Solution.hs +++ b/src/Day15/Solution.hs @@ -1,9 +1,18 @@ -module Day15.Solution where +module Day15.Solution (memoryGame, part1, part2) where -import Advent.Utils -import Control.Monad +import Advent.Utils (fromRightOrShowError, readInt) +import Control.Monad (liftM3) import qualified Data.IntMap.Lazy as IntMap -import Text.Parsec hiding (State) +import Text.Parsec + ( ParseError, + Parsec, + char, + digit, + endOfLine, + many1, + parse, + sepBy, + ) part1 :: String -> String part1 = show . memoryGame 2020 . fromRightOrShowError . parseInts diff --git a/test/Day15/SolutionSpec.hs b/test/Day15/SolutionSpec.hs index 1666b79..39b2555 100644 --- a/test/Day15/SolutionSpec.hs +++ b/test/Day15/SolutionSpec.hs @@ -1,7 +1,7 @@ module Day15.SolutionSpec (spec) where -import Data.Foldable -import Day15.Solution +import Data.Foldable (for_) +import Day15.Solution (memoryGame, part1, part2) import Test.Hspec type ContextCase = (Int, [Case]) From e68166198f5e7e43ab5ce2f7d6345d9ab9327fdb Mon Sep 17 00:00:00 2001 From: Manu Phatak Date: Sun, 20 Dec 2020 17:24:39 -0800 Subject: [PATCH 5/7] update readme to include part 2 --- src/Day15/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Day15/README.md b/src/Day15/README.md index f6bbe77..e710374 100644 --- a/src/Day15/README.md +++ b/src/Day15/README.md @@ -39,6 +39,20 @@ Here are a few more examples: 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] From e794db118bc7dca1486aaddd02bd8b19a1a14400 Mon Sep 17 00:00:00 2001 From: Manu Phatak Date: Sun, 20 Dec 2020 17:31:44 -0800 Subject: [PATCH 6/7] leave todo comments --- test/Day15/SolutionSpec.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/Day15/SolutionSpec.hs b/test/Day15/SolutionSpec.hs index 39b2555..56df874 100644 --- a/test/Day15/SolutionSpec.hs +++ b/test/Day15/SolutionSpec.hs @@ -13,6 +13,7 @@ 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" @@ -51,6 +52,7 @@ spec = parallel $ do for_ cases test testContext cases2020 + -- TODO: figure out how to make this not take 10 minutes xcontext "skip" $ testContext cases30000000 -- 300 @@ -74,3 +76,6 @@ spec = parallel $ do -- Finished in 30.7137 seconds -- Finished in 26.4237 seconds -- Finished in 25.2911 seconds + +-- 30000000 +-- TODO: Need to get here! From 5deecfebe2c132c02b9a8bba847702ba03c5bf45 Mon Sep 17 00:00:00 2001 From: Manu Phatak Date: Sun, 20 Dec 2020 17:37:02 -0800 Subject: [PATCH 7/7] re-dupe specs, too much abstraction --- test/Day15/SolutionSpec.hs | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/test/Day15/SolutionSpec.hs b/test/Day15/SolutionSpec.hs index 56df874..9a95738 100644 --- a/test/Day15/SolutionSpec.hs +++ b/test/Day15/SolutionSpec.hs @@ -4,10 +4,6 @@ import Data.Foldable (for_) import Day15.Solution (memoryGame, part1, part2) import Test.Hspec -type ContextCase = (Int, [Case]) - -type Case = ([Int], Int) - spec :: Spec spec = parallel $ do it "solves Part 1" $ do @@ -18,9 +14,9 @@ spec = parallel $ do input <- readFile "./test/Day15/input.txt" part2 input `shouldBe` "21768614" describe "memoryGame" $ do - let cases2020 :: ContextCase - cases2020 = - ( 2020, + 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), @@ -29,10 +25,15 @@ spec = parallel $ do ([3, 2, 1], 438), ([3, 1, 2], 1836) ] - ) - let cases30000000 :: ContextCase - cases30000000 = - ( 30000000, + 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), @@ -41,19 +42,10 @@ spec = parallel $ do ([3, 2, 1], 18), ([3, 1, 2], 362) ] - ) - - let testContext :: ContextCase -> SpecWith () - testContext (n, cases) = context ("when looking at the " ++ show n ++ "th result") $ do - let test :: Case -> SpecWith () - test (input, expected) = it ("is " ++ show expected ++ " for input " ++ show input) $ do - memoryGame n input `shouldBe` expected + let test (input, expected) = it ("is " ++ show expected ++ " for input " ++ show input) $ do + memoryGame n input `shouldBe` expected - for_ cases test - - testContext cases2020 - -- TODO: figure out how to make this not take 10 minutes - xcontext "skip" $ testContext cases30000000 + for_ cases test -- 300 -- Finished in 0.0017 seconds