diff --git a/AdventOfCode2020.cabal b/AdventOfCode2020.cabal index ddb5e5a..4b9a083 100644 --- a/AdventOfCode2020.cabal +++ b/AdventOfCode2020.cabal @@ -53,6 +53,7 @@ library Day18.Solution Day20.Solution Day21.Solution + Day23.Solution Practice.Foldable Template.Solution other-modules: @@ -99,6 +100,7 @@ test-suite AdventOfCode2020-test Day18.SolutionSpec Day20.SolutionSpec Day21.SolutionSpec + Day23.SolutionSpec Practice.FoldableSpec Template.SolutionSpec Paths_AdventOfCode2020 diff --git a/src/Day23/README.md b/src/Day23/README.md new file mode 100644 index 0000000..eb86ab5 --- /dev/null +++ b/src/Day23/README.md @@ -0,0 +1,97 @@ +## Day 23: Crab Cups + +The small crab challenges _you_ to a game! The crab is going to mix up some cups, and you have to predict where they'll end up. + +The cups will be arranged in a circle and labeled _clockwise_ (your puzzle input). For example, if your labeling were `32415` , there would be five cups in the circle; going clockwise around the circle from the first cup, the cups would be labeled `3` , `2` , `4` , `1` , `5` , and then back to `3` again. + +Before the crab starts, it will designate the first cup in your list as the _current cup_ . The crab is then going to do _100 moves_ . + +Each _move_ , the crab does the following actions: + +- The crab picks up the _three cups_ that are immediately _clockwise_ of the _current cup_ . They are removed from the circle; cup spacing is adjusted as necessary to maintain the circle. +- The crab selects a _destination cup_ : the cup with a _label_ equal to the _current cup's_ label minus one. If this would select one of the cups that was just picked up, the crab will keep subtracting one until it finds a cup that wasn't just picked up. If at any point in this process the value goes below the lowest value on any cup's label, it _wraps around_ to the highest value on any cup's label instead. +- The crab places the cups it just picked up so that they are _immediately clockwise_ of the destination cup. They keep the same order as when they were picked up. +- The crab selects a new _current cup_ : the cup which is immediately clockwise of the current cup. + +For example, suppose your cup labeling were `389125467` . If the crab were to do merely 10 moves, the following changes would occur: + +``` +-- move 1 -- +cups: (3) 8 9 1 2 5 4 6 7 +pick up: 8, 9, 1 +destination: 2 + +-- move 2 -- +cups: 3 (2) 8 9 1 5 4 6 7 +pick up: 8, 9, 1 +destination: 7 + +-- move 3 -- +cups: 3 2 (5) 4 6 7 8 9 1 +pick up: 4, 6, 7 +destination: 3 + +-- move 4 -- +cups: 7 2 5 (8) 9 1 3 4 6 +pick up: 9, 1, 3 +destination: 7 + +-- move 5 -- +cups: 3 2 5 8 (4) 6 7 9 1 +pick up: 6, 7, 9 +destination: 3 + +-- move 6 -- +cups: 9 2 5 8 4 (1) 3 6 7 +pick up: 3, 6, 7 +destination: 9 + +-- move 7 -- +cups: 7 2 5 8 4 1 (9) 3 6 +pick up: 3, 6, 7 +destination: 8 + +-- move 8 -- +cups: 8 3 6 7 4 1 9 (2) 5 +pick up: 5, 8, 3 +destination: 1 + +-- move 9 -- +cups: 7 4 1 5 8 3 9 2 (6) +pick up: 7, 4, 1 +destination: 5 + +-- move 10 -- +cups: (5) 7 4 1 8 3 9 2 6 +pick up: 7, 4, 1 +destination: 3 + +-- final -- +cups: 5 (8) 3 7 4 1 9 2 6 +``` + +In the above example, the cups' values are the labels as they appear moving clockwise around the circle; the _current cup_ is marked with `( )` . + +After the crab is done, what order will the cups be in? Starting _after the cup labeled `1`_ , collect the other cups' labels clockwise into a single string with no extra characters; each number except `1` should appear exactly once. In the above example, after 10 moves, the cups clockwise from `1` are labeled `9` , `2` , `6` , `5` , and so on, producing _`92658374`_ . If the crab were to complete all 100 moves, the order after cup `1` would be _`67384529`_ . + +Using your labeling, simulate 100 moves. _What are the labels on the cups after cup `1` ?_ + +## Part Two + +Due to what you can only assume is a mistranslation (you're not exactly fluent in Crab ), you are quite surprised when the crab starts arranging _many_ cups in a circle on your raft - _one million_ ( `1000000` ) in total. + +Your labeling is still correct for the first few cups; after that, the remaining cups are just numbered in an increasing fashion starting from the number after the highest number in your list and proceeding one by one until one million is reached. (For example, if your labeling were `54321` , the cups would be numbered `5` , `4` , `3` , `2` , `1` , and then start counting up from `6` until one million is reached.) In this way, every number from one through one million is used exactly once. + +After discovering where you made the mistake in translating Crab Numbers, you realize the small crab isn't going to do merely 100 moves; the crab is going to do _ten million_ ( `10000000` ) moves! + +The crab is going to hide your _stars_ \- one each - under the _two cups that will end up immediately clockwise of cup `1`_ . You can have them if you predict what the labels on those cups will be when the crab is finished. + +In the above example ( `389125467` ), this would be `934001` and then `159792` ; multiplying these together produces _`149245887792`_ . + +Determine which two cups will end up immediately clockwise of cup `1` . _What do you get if you multiply their labels together?_ + +## Link + +[https://adventofcode.com/2020/day/23][1] + +[1]: https://adventofcode.com/2020/day/23 diff --git a/src/Day23/Solution.hs b/src/Day23/Solution.hs new file mode 100644 index 0000000..f88bbd3 --- /dev/null +++ b/src/Day23/Solution.hs @@ -0,0 +1,84 @@ +module Day23.Solution where + +import Advent.Utils (readInt) +import Data.Bifunctor (Bifunctor (first)) +import qualified Data.IntMap.Lazy as IntMap + +part1 :: String -> String +part1 = cupOrder . moves 100 . fromList . parseInts + +part2 :: String -> String +part2 = show . product . adjacentTo 1 . moves (10 * oneMillion) . fillCups . parseInts + +oneMillion :: Int +oneMillion = 1000000 + +type CircularList = (Int, IntMap.IntMap Int) + +moves :: Int -> CircularList -> CircularList +moves n + | n < 0 = undefined + | n == 0 = id + | otherwise = moves (pred n) . move + +move :: CircularList -> CircularList +move xss@(pointer, xs) = + ( d, + (IntMap.insert c c' . IntMap.insert destination a . IntMap.insert pointer d) xs + ) + where + [a, b, c, d] = take 4 . drop 1 . toList $ xss + c' = xs ! destination + destination = findDestination (pred pointer) + + findDestination candidate + | candidate < 0 = undefined + | candidate == 0 = (findDestination . maximum . IntMap.keys) xs + | candidate `elem` [a, b, c] = findDestination (pred candidate) + | otherwise = candidate + +fromList :: [Int] -> CircularList +fromList xss@(x : xs) = + ( x, + IntMap.fromList (zip xss (xs ++ [x])) + ) +fromList _ = undefined + +toUniqList :: CircularList -> [Int] +toUniqList = takeWhileUniq . toList + where + takeWhileUniq :: Eq a => [a] -> [a] + takeWhileUniq = foldr go [] + where + go x r = x : takeWhile (/= x) r + +toList :: CircularList -> [Int] +toList = map fst . iterate go + where + go :: CircularList -> CircularList + go (pointer, cl) = (cl ! pointer, cl) + +(!) :: IntMap.IntMap Int -> Int -> Int +m ! k = IntMap.findWithDefault (succ k) k m + +parseInts :: String -> [Int] +parseInts = map (readInt . pure) . head . lines + +cupOrder :: CircularList -> String +cupOrder = concatMap show . tail . toUniqList . goTo 1 + +fillCups :: [Int] -> CircularList +fillCups xs = + ( pointer, + (IntMap.insert oneMillion pointer . IntMap.insert lastKey (succ maxKey)) xs' + ) + where + (pointer, xs') = fromList xs + maxKey = maximum xs + lastKey = last xs + +goTo :: Int -> CircularList -> CircularList +goTo n = first (const n) + +adjacentTo :: Int -> CircularList -> [Int] +adjacentTo n = take 2 . drop 1 . toList . goTo n diff --git a/test/Day23/SolutionSpec.hs b/test/Day23/SolutionSpec.hs new file mode 100644 index 0000000..660c3a5 --- /dev/null +++ b/test/Day23/SolutionSpec.hs @@ -0,0 +1,95 @@ +module Day23.SolutionSpec (spec) where + +import Data.Foldable (for_) +import qualified Data.IntMap.Lazy as IntMap +import Day23.Solution +import Test.Hspec + +spec :: Spec +spec = parallel $ do + it "solves Part 1" $ do + input <- readFile "./test/Day23/input.txt" + part1 input `shouldBe` "98752463" + + it "solves Part 2" $ do + pendingWith "takes about 105 seconds to run" + + input <- readFile "./test/Day23/input.txt" + part2 input `shouldBe` "2000455861" + + let exampleCircularList = fromList [3, 8, 9, 1, 2, 5, 4, 6, 7] + describe "fromList" $ do + it "creates a circular list" $ do + let expected = + ( 3, + IntMap.fromList + [ (3, 8), + (8, 9), + (9, 1), + (1, 2), + (2, 5), + (5, 4), + (4, 6), + (6, 7), + (7, 3) + ] + ) + exampleCircularList `shouldBe` expected + + describe "toUniqList" $ do + it "creates a flat list" $ do + toUniqList exampleCircularList `shouldBe` [3, 8, 9, 1, 2, 5, 4, 6, 7] + it "can be wrapped" $ do + (toUniqList . goTo 1) exampleCircularList `shouldBe` [1, 2, 5, 4, 6, 7, 3, 8, 9] + + describe "moves" $ do + let cases = + [ (1, [2, 8, 9, 1, 5, 4, 6, 7, 3]), + (2, [5, 4, 6, 7, 8, 9, 1, 3, 2]), + (3, [8, 9, 1, 3, 4, 6, 7, 2, 5]), + (4, [4, 6, 7, 9, 1, 3, 2, 5, 8]), + (5, [1, 3, 6, 7, 9, 2, 5, 8, 4]), + (6, [9, 3, 6, 7, 2, 5, 8, 4, 1]), + (7, [2, 5, 8, 3, 6, 7, 4, 1, 9]), + (8, [6, 7, 4, 1, 5, 8, 3, 9, 2]), + (9, [5, 7, 4, 1, 8, 3, 9, 2, 6]), + (10, [8, 3, 7, 4, 1, 9, 2, 6, 5]) + ] + let test (input, expected) = it ("walks through move " ++ show input) $ do + (toUniqList . moves input) exampleCircularList `shouldBe` expected + + for_ cases test + + describe "cupOrder" $ do + it "is 92658374 after 10 moves" $ do + (cupOrder . moves 10) exampleCircularList `shouldBe` "92658374" + it "is 67384529 after 100 moves" $ do + (cupOrder . moves 100) exampleCircularList `shouldBe` "67384529" + + let exampleCircularListFilled = fillCups [3, 8, 9, 1, 2, 5, 4, 6, 7] + + describe "fillCups" $ do + it "creates a million cups" $ do + let expected = + ( 3, + IntMap.fromList + [ (3, 8), + (8, 9), + (9, 1), + (1, 2), + (2, 5), + (5, 4), + (4, 6), + (6, 7), + (7, 10), + (oneMillion, 3) + ] + ) + exampleCircularListFilled `shouldBe` expected + + describe "adjacentTo" $ do + context "when running 10 000 000 rounds" $ do + it "get's the pair next to 1" $ do + pendingWith "takes too long to run" + + (adjacentTo 1 . moves (10 * oneMillion)) exampleCircularListFilled `shouldBe` [934001, 159792] diff --git a/test/Day23/input.txt b/test/Day23/input.txt new file mode 100644 index 0000000..85e58d4 --- /dev/null +++ b/test/Day23/input.txt @@ -0,0 +1 @@ +789465123