├── .github └── workflows │ └── haddock.yml ├── .gitignore ├── Build.hs ├── CHANGELOG.md ├── LICENSE ├── README.md ├── Setup.hs ├── app ├── aoc.hs └── day17.hs ├── bench └── Bench.hs ├── cabal.project ├── feed.xml ├── package.yaml ├── reflections-out ├── day01.md ├── day02.md ├── day03.md ├── day04.md ├── day05.md ├── day06.md ├── day07.md ├── day08.md ├── day09.md ├── day10.md ├── day11.md ├── day12.md ├── day13.md ├── day14.md ├── day15.md ├── day16.md ├── day17.md ├── day18.md ├── day19.md ├── day20.md ├── day21.md ├── day22.md ├── day23.md ├── day24.md └── day25.md ├── reflections.md ├── reflections ├── day01.md ├── day02.md ├── day03.md ├── day04.md ├── day05.md ├── day06.md ├── day07.md ├── day08.md ├── day09.md ├── day10.md ├── day11.md ├── day12.md ├── day13.md ├── day14.md ├── day15.md ├── day16.md ├── day17.md ├── day18.md ├── day19.md ├── day20.md ├── day21.md ├── day22.md ├── day23.md ├── day24.md └── day25.md ├── script ├── generate_days.hs └── open_files.vim ├── src ├── AOC.hs └── AOC │ ├── Challenge.hs │ ├── Challenge │ ├── Day01.hs │ ├── Day02.hs │ ├── Day03.hs │ ├── Day04.hs │ ├── Day05.hs │ ├── Day06.hs │ ├── Day07.hs │ ├── Day08.hs │ ├── Day09.hs │ ├── Day10.hs │ ├── Day11.hs │ ├── Day12.hs │ ├── Day13.hs │ ├── Day14.hs │ ├── Day15.hs │ ├── Day16.hs │ ├── Day17.hs │ ├── Day18.hs │ ├── Day19.hs │ ├── Day20.hs │ ├── Day21.hs │ ├── Day22.hs │ ├── Day23.hs │ ├── Day24.hs │ └── Day25.hs │ ├── Common.hs │ ├── Common │ ├── DLLC.hs │ ├── FinitarySet.hs │ ├── Numeric.hs │ ├── Point.hs │ └── Search.hs │ ├── Discover.hs │ ├── Prelude.hs │ ├── Run.hs │ ├── Run │ ├── Config.hs │ ├── Interactive.hs │ └── Load.hs │ ├── Solver.hs │ ├── Util.hs │ └── Util │ └── DynoMap.hs ├── stack.yaml ├── stack.yaml.lock ├── template ├── DayXX.hs ├── README.md.template ├── feed-item.xml.template ├── feed.xml.template ├── reflection.md.template ├── reflections.md.template └── standalone-reflection.md.template ├── test-data ├── 2016 │ ├── 01a.txt │ ├── 01b.txt │ ├── 02a.txt │ ├── 02b.txt │ ├── 03a.txt │ ├── 04a.txt │ ├── 05a.txt │ ├── 05b.txt │ ├── 06a.txt │ ├── 06b.txt │ ├── 07a.txt │ ├── 07b.txt │ ├── 08a.txt │ ├── 09a.txt │ └── 09b.txt ├── 2017 │ ├── 01a.txt │ ├── 01b.txt │ ├── 02a.txt │ ├── 02b.txt │ ├── 03a.txt │ ├── 03b.txt │ ├── 04a.txt │ ├── 04b.txt │ ├── 05a.txt │ ├── 05b.txt │ ├── 06a.txt │ ├── 06b.txt │ ├── 07a.txt │ ├── 07b.txt │ ├── 08a.txt │ ├── 08b.txt │ ├── 09a.txt │ ├── 09b.txt │ ├── 10b.txt │ ├── 11a.txt │ ├── 11b.txt │ ├── 13a.txt │ ├── 13b.txt │ ├── 14a.txt │ ├── 14b.txt │ ├── 17a.txt │ ├── 18b.txt │ ├── 19a.txt │ ├── 19b.txt │ ├── 20a.txt │ └── 20b.txt ├── 2018 │ ├── 01a.txt │ ├── 01b.txt │ ├── 02a.txt │ ├── 02b.txt │ ├── 03a.txt │ ├── 03b.txt │ ├── 04a.txt │ ├── 04b.txt │ ├── 05a.txt │ ├── 05b.txt │ ├── 06a.txt │ ├── 06b.txt │ ├── 07a.txt │ ├── 07b.txt │ ├── 08a.txt │ ├── 08b.txt │ ├── 09a.txt │ ├── 10b.txt │ ├── 11a.txt │ ├── 11b.txt │ ├── 14a.txt │ ├── 14b.txt │ ├── 15a.txt │ ├── 15b.txt │ ├── 17a.txt │ ├── 18a.txt │ ├── 20a.txt │ ├── 22a.txt │ ├── 22b.txt │ ├── 23a.txt │ ├── 23b.txt │ ├── 24a.txt │ └── 24b.txt ├── 2019 │ ├── 01a.txt │ ├── 01b.txt │ ├── 02a.txt │ ├── 03a.txt │ ├── 03b.txt │ ├── 04a.txt │ ├── 04b.txt │ ├── 05a.txt │ ├── 06a.txt │ ├── 06b.txt │ ├── 07a.txt │ ├── 07b.txt │ ├── 08a.txt │ ├── 09a.txt │ ├── 10a.txt │ ├── 10b.txt │ ├── 12a.txt │ ├── 12b.txt │ ├── 14a.txt │ ├── 14b.txt │ ├── 16a.txt │ ├── 16b.txt │ ├── 18a.txt │ ├── 18b.txt │ ├── 20a.txt │ ├── 20b.txt │ └── 24b.txt └── 2020 │ ├── 01a.txt │ ├── 01b.txt │ ├── 02a.txt │ ├── 02b.txt │ ├── 04a.txt │ ├── 04b.txt │ ├── 05a.txt │ ├── 07a.txt │ ├── 07b.txt │ ├── 09a.txt │ ├── 09b.txt │ ├── 10a.txt │ ├── 10b.txt │ ├── 11a.txt │ ├── 11b.txt │ ├── 12a.txt │ ├── 12b.txt │ ├── 13a.txt │ ├── 13b.txt │ ├── 14a.txt │ ├── 14b.txt │ ├── 15a.txt │ ├── 15b.txt │ ├── 16a.txt │ ├── 16b.txt │ ├── 17a.txt │ ├── 17b.txt │ ├── 18a.txt │ ├── 18b.txt │ ├── 19a.txt │ ├── 19b.txt │ ├── 20a.txt │ ├── 20b.txt │ ├── 21a.txt │ ├── 21b.txt │ ├── 22a.txt │ ├── 22b.txt │ ├── 23a.txt │ ├── 23b.txt │ ├── 24a.txt │ └── 24b.txt └── test └── Spec.hs /.github/workflows/haddock.yml: -------------------------------------------------------------------------------- 1 | name: Haddock 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - '**/*.hs' 9 | - package.yaml 10 | - stack.* 11 | 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | with: 21 | path: src 22 | - uses: actions/checkout@v2 23 | with: 24 | ref: gh-pages 25 | path: gh-pages 26 | - uses: actions/setup-haskell@v1 27 | with: 28 | stack-version: latest 29 | - uses: actions/cache@v2 30 | with: 31 | key: ${{ runner.os }}-${{ hashFiles('src/stack.*') }}-${{ hashFiles('src/package.yaml') }} 32 | restore-keys: ${{ runner.os }}-${{ hashFiles('src/stack.*') }}- 33 | path: ~/.stack 34 | - run: stack build --haddock --no-haddock-deps 35 | working-directory: src 36 | - run: rsync --archive --delete --exclude=.git --verbose --whole-file src/.stack-work/dist/*/*/doc/html/aoc2020/ gh-pages/ 37 | - uses: EndBug/add-and-commit@v5 38 | with: 39 | branch: gh-pages 40 | cwd: gh-pages 41 | add: . 42 | message: 'Update gh-pages at ${{ github.sha }}' 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | /*.cabal 3 | *~ 4 | /data 5 | /prompt 6 | /aoc-conf.yaml 7 | /logs 8 | /bench-out 9 | /tmp 10 | /scratch 11 | /dist-newstyle 12 | _* 13 | /tags 14 | /scratch 15 | /cache 16 | /out 17 | *.prof 18 | *.prof.html 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Justin Le (c) 2018 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Justin Le nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /bench/Bench.hs: -------------------------------------------------------------------------------- 1 | 2 | import AOC 3 | import Control.Monad.Except 4 | 5 | main :: IO () 6 | main = do 7 | cfg <- configFile defConfPath 8 | void . runExceptT . mainRun cfg $ (defaultMRO TSAll) 9 | { _mroBench = True 10 | } 11 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | packages: *.cabal 2 | 3 | allow-newer: newtype:base 4 | , monoidal-containers:lens 5 | , monoidal-containers:semialign 6 | , these-skinny:base 7 | 8 | source-repository-package 9 | type: git 10 | location: https://github.com/nikita-volkov/refined.git 11 | tag: 240dd2b6fb5787cb6a079f4ab42674cb1ea7fd3b 12 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: aoc2020 2 | version: 0.1.0.0 3 | github: "mstksg/advent-of-code-2020" 4 | license: BSD3 5 | author: "Justin Le" 6 | maintainer: "justin@jle.im" 7 | copyright: "(c) Justin Le 2018" 8 | 9 | extra-source-files: 10 | - README.md 11 | - CHANGELOG.md 12 | 13 | synopsis: "Development environment for Advent of Code challenges" 14 | category: Web 15 | 16 | description: | 17 | Scaffolding for an integrated development environment for Advent of Code 18 | challenges. Includes auto-runner, prompt displays and countdowns, and 19 | integrated submissions. 20 | 21 | ghc-options: 22 | - -Wall 23 | - -Wcompat 24 | - -Wno-partial-type-signatures 25 | - -Wredundant-constraints 26 | - -O2 27 | 28 | default-extensions: 29 | - AllowAmbiguousTypes 30 | - ApplicativeDo 31 | - BangPatterns 32 | - BlockArguments 33 | - DataKinds 34 | - DeriveAnyClass 35 | - DeriveFoldable 36 | - DeriveFunctor 37 | - DeriveGeneric 38 | - DeriveTraversable 39 | - DerivingVia 40 | - EmptyCase 41 | - FlexibleContexts 42 | - FlexibleInstances 43 | - FunctionalDependencies 44 | - GADTs 45 | - GeneralizedNewtypeDeriving 46 | - ImplicitParams 47 | - KindSignatures 48 | - LambdaCase 49 | - MonadComprehensions 50 | - MultiParamTypeClasses 51 | - MultiWayIf 52 | - NumDecimals 53 | - NumericUnderscores 54 | - OverloadedLabels 55 | - PartialTypeSignatures 56 | - PatternGuards 57 | - PatternSynonyms 58 | - PolyKinds 59 | - RankNTypes 60 | - RecordWildCards 61 | - ScopedTypeVariables 62 | - StandaloneDeriving 63 | - TemplateHaskell 64 | - TupleSections 65 | - TypeApplications 66 | - TypeFamilyDependencies 67 | - TypeInType 68 | - TypeOperators 69 | - UndecidableInstances 70 | - ViewPatterns 71 | 72 | dependencies: 73 | - base >= 4.7 && < 5 74 | - mtl 75 | 76 | library: 77 | source-dirs: src 78 | dependencies: 79 | - advent-of-code-api >= 0.2.7 80 | - aeson 81 | - ansi-terminal 82 | - arithmoi 83 | - async 84 | - barbies 85 | - bitvec 86 | - bytestring 87 | - comonad 88 | - conduino 89 | - containers 90 | - criterion 91 | - data-default-class 92 | - data-interval 93 | - data-memocombinators 94 | - deepseq 95 | - deriving-compat 96 | - directory 97 | - distributive 98 | - extended-reals 99 | - fgl 100 | - filepath 101 | - fin 102 | - finitary 103 | - finite-typelits 104 | - foldl 105 | - free 106 | - generic-lens 107 | - ghc-typelits-knownnat 108 | - ghc-typelits-natnormalise 109 | - grid 110 | - groups 111 | - hashable 112 | - haskeline 113 | - haskell-names 114 | - haskell-src-exts 115 | - heredoc 116 | - hpack 117 | - lens 118 | - linear 119 | - megaparsec >= 8.0 120 | - monad-loops 121 | - monoidal-containers 122 | - mtl 123 | - mutable 124 | - nonempty-containers 125 | - one-liner-instances 126 | - pandoc 127 | - parallel 128 | - parser-combinators >= 1.2.0 129 | - pointedlist 130 | - primitive 131 | - profunctors 132 | - psqueues 133 | - recursion-schemes 134 | - refined 135 | - resourcet 136 | - rev-state 137 | - safe 138 | - semialign 139 | - semigroupoids 140 | - split 141 | - sqlite-simple 142 | - strict-tuple 143 | - template-haskell 144 | - text 145 | - th-abstraction 146 | - these 147 | - time 148 | - transformers 149 | - typelits-witnesses 150 | - unordered-containers 151 | - vec 152 | - vector 153 | - vector-algorithms 154 | - vector-sized 155 | - vinyl 156 | - witherable 157 | - yaml 158 | 159 | executables: 160 | aoc2020: 161 | main: aoc.hs 162 | source-dirs: app 163 | ghc-options: 164 | - -threaded 165 | - -rtsopts 166 | - -with-rtsopts=-N 167 | - -O2 168 | dependencies: 169 | - ansi-terminal 170 | - aoc2020 171 | - containers 172 | - deepseq 173 | - finite-typelits 174 | - lens 175 | - optparse-applicative 176 | aoc2020-day17: 177 | main: day17.hs 178 | source-dirs: app 179 | ghc-options: 180 | - -threaded 181 | - -rtsopts 182 | - -with-rtsopts=-N 183 | - -O2 184 | dependencies: 185 | - aeson 186 | - aoc2020 187 | - bytestring 188 | - cereal 189 | - conduit 190 | - conduit-extra 191 | - containers 192 | - deepseq 193 | - filepath 194 | - hvega 195 | - text 196 | - unliftio-core 197 | 198 | tests: 199 | aoc2020-test: 200 | main: Spec.hs 201 | source-dirs: test 202 | ghc-options: 203 | - -threaded 204 | - -rtsopts 205 | - -with-rtsopts=-N 206 | - -O2 207 | dependencies: 208 | - aoc2020 209 | - ansi-terminal 210 | 211 | benchmarks: 212 | aoc2020-bench: 213 | main: Bench.hs 214 | source-dirs: bench 215 | ghc-options: 216 | - -threaded 217 | - -rtsopts 218 | - -with-rtsopts=-N 219 | - -O2 220 | dependencies: 221 | - aoc2020 222 | -------------------------------------------------------------------------------- /reflections/day01.md: -------------------------------------------------------------------------------- 1 | So there's a simple-ish Haskell solution for these problems, 2 | 3 | `tails` lets you separate out each item in a list with the list of items after 4 | it: 5 | 6 | ```haskell 7 | ghci> tails [1,2,3,4] 8 | [1:[2,3,4], 2:[3,4], 3:[4], 4:[]] 9 | ``` 10 | 11 | ```haskell 12 | findPair :: [Int] -> Maybe Int 13 | findPair xs = listToMaybe $ do 14 | x:ys <- tails xs 15 | y <- ys 16 | guard (x + y == 2020) 17 | pure (x*y) 18 | 19 | findTriple :: [Int] -> Maybe Int 20 | findTriple xs = listToMaybe $ do 21 | x:ys <- tails xs 22 | y:zs <- tails ys 23 | z <- zs 24 | guard (x + y + z == 2020) 25 | pure (x*y*z) 26 | ``` 27 | 28 | But this method is a little bit "extra", since we actually don't need to search 29 | all of `ys` for the proper sum...if we pick `x` as `500`, then we really only 30 | need to check if `1520` is a part of `ys`. 31 | 32 | So we really only need to check for set inclusion: 33 | 34 | ```haskell 35 | import qualified Data.IntSet as IS 36 | 37 | findPair :: Int -> IS.IntSet -> Maybe Int 38 | findPair goal xs = listToMaybe $ do 39 | x <- IS.toList xs 40 | let y = goal - x 41 | guard (y `IS.member` xs) 42 | pure (x * y) 43 | ``` 44 | 45 | And our first part will be `findPair 2020`! 46 | 47 | You could even implement `findTriple` in terms of `findPair`, using `IS.split` 48 | to partition a set into all items smaller than and larger than a number. 49 | Splitting is a very efficient operation on a binary search tree like `IntSet`: 50 | 51 | ```haskell 52 | findTriple :: Int -> IS.IntSet -> Maybe Int 53 | findTriple goal xs = listToMaybe $ do 54 | x <- IS.toList xs 55 | let (_, ys) = IS.split x xs 56 | goal' = goal - x 57 | case findPair goal' ys of 58 | Nothing -> empty 59 | Just yz -> pure (x*yz) 60 | ``` 61 | 62 | But hey...this recursive descent is kind of neat. We could write a general 63 | function to find any goal in any number of items! 64 | 65 | ```haskell 66 | -- | Given a number n of items and a goal sum and a set of numbers to 67 | -- pick from, finds the n numbers in the set that add to the goal sum. 68 | knapsack 69 | :: Int -- ^ number of items n to pick 70 | -> Int -- ^ goal sum 71 | -> IS.IntSet -- ^ set of options 72 | -> Maybe [Int] -- ^ resulting n items that sum to the goal 73 | knapsack 0 _ _ = Nothing 74 | knapsack 1 goal xs 75 | | goal `IS.member` xs = Just [goal] 76 | | otherwise = Nothing 77 | knapsack n goal xs = listToMaybe $ do 78 | x <- IS.toList xs 79 | let goal' = goal - x 80 | (_, ys) = IS.split x xs 81 | case knapsack (n - 1) goal' ys of 82 | Nothing -> empty 83 | Just rs -> pure (x:rs) 84 | ``` 85 | 86 | And so we have: 87 | 88 | ```haskell 89 | part1 :: [Int] -> Maybe Int 90 | part1 = knapsack 2 2020 . IS.fromList 91 | 92 | part2 :: [Int] -> Maybe Int 93 | part2 = knapsack 3 2020 . IS.fromList 94 | ``` 95 | 96 | And we could go on, and on, and on! 97 | 98 | Definitely very unnecessary, but it does shave my time on Part 2 down from 99 | around 2ms to around 20μs :) 100 | -------------------------------------------------------------------------------- /reflections/day02.md: -------------------------------------------------------------------------------- 1 | Day 2, not too bad for Haskell either :) 2 | 3 | There is some fun in parsing here: 4 | 5 | ```haskell 6 | data Policy = P 7 | { pIx1 :: Int 8 | , pIx2 :: Int 9 | , pChar :: Char 10 | , pPass :: String 11 | } 12 | 13 | parsePolicy :: String -> Maybe Policy 14 | parsePolicy str = do 15 | [ixes,c:_,pwd] <- pure $ words str 16 | [ix1,ix2] <- pure $ splitOn "-" ixes 17 | P <$> readMaybe ix1 18 | <*> readMaybe ix2 19 | <*> pure c 20 | <*> pure pwd 21 | ``` 22 | 23 | I used one of my more regular do-block tricks: if you pattern match in a 24 | `Maybe` do-block, then failed pattern matches will turn the whole thing into a 25 | `Nothing`. So if any of those list literal pattern matches failed, the whole 26 | block will return `Nothing`. 27 | 28 | In any case, we just need to write a function to check if a given policy is 29 | valid for either criteria: 30 | 31 | ```haskell 32 | countTrue :: (a -> Bool) -> [a] -> Int 33 | countTrue p = length . filter p 34 | 35 | validate1 :: Policy -> Bool 36 | validate1 P{..} = n >= pIx1 && n <= pIx2 37 | where 38 | n = countTrue (== pChar) pPass 39 | 40 | validate2 :: Policy -> Bool 41 | validate2 P{..} = n == 1 42 | where 43 | n = countTrue (== pChar) [pPass !! (pIx1 - 1), pPass !! (pIx2 - 1)] 44 | ``` 45 | 46 | And so parts 1 and 2 are just a count of how many policies are true :) 47 | 48 | ```haskell 49 | part1 :: [Policy] -> Int 50 | part1 = countTrue validate1 51 | 52 | part2 :: [Policy] -> Int 53 | part2 = countTrue validate2 54 | ``` 55 | -------------------------------------------------------------------------------- /reflections/day05.md: -------------------------------------------------------------------------------- 1 | So, compared to yesterday's, this was decently chill :) 2 | 3 | The main insight here probably is that the puzzle is just describing that the 4 | seat ID's are straight up binary notation for numerals, with F/L representing 5 | what is traditionally 0, and B/R representing what is traditionally 1. So we 6 | can use any of our binary parsers from the standard libraries, or we can just 7 | directly pull it into binary. 8 | 9 | ```haskell 10 | seatId :: String -> Int 11 | seatId = foldl' iGuessWe'reDoingThis 0 12 | where 13 | iGuessWe'reDoingThis n = \case 14 | 'B' -> 2*n+1 15 | 'R' -> 2*n+1 16 | _ -> 2*n 17 | ``` 18 | 19 | A nice one-pass way to find the missing seat ID is to realize that if we sum 20 | all the numbers from min to max, and sum all of our lists's seat id's, then the 21 | difference is the missing number. Luckily there's a nice closed-form solution 22 | for the sum of all numbers in a given range (the sum of numbers from `a` to `b` 23 | is ``b*(b+1)`div`2 - a*(a-1)`div`2``), so we can do all of this in a single 24 | pass using the *[foldl][]* library 25 | 26 | [foldl]: https://hackage.haskell.org/package/foldl 27 | 28 | ```haskell 29 | {-# LANGUAGE ApplicativeDo #-} 30 | import qualified Control.Foldl as F 31 | 32 | findHole :: F.Fold Int (Maybe Int) 33 | findHole = do 34 | mn <- F.minimum 35 | mx <- F.maximum 36 | sm <- F.sum 37 | pure $ 38 | missingItem <$> mn <*> mx <*> pure sm 39 | where 40 | missingItem mn mx sm = totalSum - sm 41 | where 42 | totalSum = mx*(mx+1)`div`2 - mn*(mn-1)`div`2 43 | ``` 44 | 45 | A `F.Fold Int (Maybe Int)` folds a list of `Int`s into a `Maybe Int`. You can 46 | run it with `F.fold :: F.Fold a b -> [a] -> b`. 47 | 48 | I really like the *foldl* library because it lets you build a complex 49 | single-pass fold by combining multiple simple single-pass folds (like 50 | `F.minimum`, `F.maximum`, `F.sum`) using an Applicative interface. We need to 51 | do a bit of wrangling with the `Maybe`s because `F.minimum` and `F.maximum` 52 | each return `Maybe Int`. 53 | 54 | And that's more or less it! We can actually represent the entire thing as a 55 | fold if we use `F.premap`, to pre-map a fold... 56 | 57 | 58 | ```haskell 59 | F.premap :: (c -> a) -> F.Fold a b -> F.Fold c b 60 | 61 | -- "pre-apply" `setId` so we fold over a string instead 62 | F.premap seatId findHole :: F.Fold String (Maybe Int) 63 | ``` 64 | 65 | And...that's enough to do it all in a single pass! 66 | 67 | ```haskell 68 | part1 :: [String] -> Maybe Int 69 | part1 = F.fold $ F.premap seatId F.maximum 70 | 71 | part2 :: [String] -> Maybe Int 72 | part2 = F.fold $ F.premap seatId findHole 73 | ``` 74 | 75 | Bonus: I was tipped off that the 3rd from last digit of F/L are 1, while the 76 | same digit of B/R are 0: 77 | 78 | ```haskell 79 | ghci> (.&. 1) . (`shiftR` 2) . ord <$> "FLBR" 80 | [1,1,0,0] 81 | ``` 82 | 83 | So we can actually use this for `seatId` to get a slight speed boost and help 84 | out the branch predictor maybe: 85 | 86 | ```haskell 87 | import Data.Bits 88 | 89 | seatId :: String -> Int 90 | seatId = foldl' iGuessWe'reDoingThis 0 91 | where 92 | iGuessWe'reDoingThis n c = 93 | 2 * n + (complement (ord c) `shiftR` 2) .&. 1 94 | ``` 95 | -------------------------------------------------------------------------------- /reflections/day06.md: -------------------------------------------------------------------------------- 1 | Another day that is fairly straightforward in Haskell, I feel! But in other 2 | languages that support functional approaches, it should be straightforward as 3 | well. 4 | 5 | The answer involves lists of groups of responses: 6 | 7 | ```haskell 8 | import Data.List.NonEmpty 9 | import Data.Set 10 | import qualified Data.List.NonEmpty as NE 11 | import qualified Data.Set as S 12 | 13 | type Response = Set Char 14 | type Group = NonEmpty Response 15 | 16 | parseAnswers :: Set Char -> [Group] 17 | parseAnswers = mapMaybe ((fmap . fmap) S.fromList . NE.nonEmpty . lines) 18 | . splitOn "\n\n" 19 | ``` 20 | 21 | And now we just need to decide how to aggregate each group. For part 1, this 22 | requires a set union between every `Response` in a `Group`: 23 | 24 | ```haskell 25 | part1 :: [Group] -> Int 26 | part1 = sum . map (S.size . foldr1 S.union) 27 | ``` 28 | 29 | (`foldr1` here is safe because we have a non-empty container) 30 | 31 | And for part 2, this requires a set intersection between every `Response` in a 32 | `Group`: 33 | 34 | 35 | ```haskell 36 | part2 :: [Group] -> Int 37 | part2 = sum . map (S.size . foldr1 S.intersection) 38 | ``` 39 | 40 | That's it! 41 | -------------------------------------------------------------------------------- /reflections/day07.md: -------------------------------------------------------------------------------- 1 | Another AoC staple, a graph search that can be solved with recursive knot 2 | tying! The last one I remember off the top of my head was [2019 Day 3 | 6][2019d06]. 4 | 5 | [2019d06]: https://github.com/mstksg/advent-of-code-2019/blob/master/reflections.md#day-6 6 | 7 | Here we can represent a graph as a map of vertices to other vertices, with an 8 | edge value: 9 | 10 | ```haskell 11 | type Graph v e = Map v (Map v e) 12 | ``` 13 | 14 | Exercise is left to the reader to parse our dataset into a `Graph String Int`, 15 | a graph of bags to bags with `Int` edges. 16 | 17 | Because our map has no cycles, we can take advantage of recursive knot tying to 18 | "fold up" all children and sub-children. 19 | 20 | For example, part 1 can be written as: 21 | 22 | ```haskell 23 | allDescendants :: Ord v => Graph v e -> Map v (Set v) 24 | allDescendants gr = descendantMap 25 | where 26 | descendantMap = gr <&> 27 | M.foldMapWithKey (\v _ -> S.insert v (M.findWithDefault S.empty v descendantMap)) 28 | 29 | -- note: (<&>) is flip fmap 30 | ``` 31 | 32 | Here we "assume" we already have a fully-featured `Map v (Set v)` map of 33 | vertices to all their descendants, and then build `descendantMap` in terms of 34 | it. For every vertex `v` in the `Map v e` directly underneath a given vertex, 35 | `v` is a descendant, and also all of `v`'s descendants (which we find by 36 | looking things up in `descendantMap`, the map of all descendants). 37 | 38 | Oh, um...oops, this found all the descendants, but we want all of the 39 | ancestors. So we have to flip the graph if we want to use this. 40 | 41 | ```haskell 42 | flipGraph :: Ord v => Graph v e -> Graph v e 43 | flipGraph mp = M.fromListWith M.union 44 | [ (m, M.singleton n e) 45 | | (n, ms) <- M.toList mp 46 | , (m, e ) <- M.toList ms 47 | ] 48 | 49 | allAncestors :: Ord v => Graph v e -> Map v (Set v) 50 | allAncestors = allDescendants . flipGraph 51 | ``` 52 | 53 | And so that leaves Part 1 as: 54 | 55 | ```haskell 56 | part1 :: Graph String (String Int) -> Maybe (Set String) 57 | part1 = M.lookup "shiny gold" . allAncestors 58 | ``` 59 | 60 | Part 2 we can do a similar way, by "assuming" we have a map of all vertices to 61 | their "usage count", and looking things up to build it: 62 | 63 | ```haskell 64 | usageCounts :: Ord v => Graph v Int -> Map v Int 65 | usageCounts gr = usageMap 66 | where 67 | usageMap = gr <&> \neighbors -> sum 68 | [ n * (M.findWithDefault 0 v usageMap + 1) 69 | | (v, n) <- M.toList neighbors 70 | ] 71 | ``` 72 | 73 | So to find the total usage of each bag, we look under each `(v, Int)` pair in the 74 | `Map v Int` underneath a given vertex, look up the usage of that `v` (by 75 | looking it up in `usageMap`), add 1 (because the bag itself is used), and 76 | multiply by `n`, the number of times the full contents of the bag is used. 77 | 78 | And so Part 2 is: 79 | 80 | ```haskell 81 | part2 :: Graph String (String Int) -> Maybe Int 82 | part2 = M.lookup "shiny gold" . usageCounts 83 | ``` 84 | 85 | If we stare at the two implementations, we note that both are pretty much the 86 | same overall structure: we are accumulating some sort of fold over all 87 | descendants of a given node. If we "outsource" this accumulation as a monoidal 88 | one (for part 1, it's `Set` union, and for part 2, it's `Sum Int` addition), we 89 | can needlessly hyper-generalize this to fold over any `Monoid` instance. 90 | 91 | ```haskell 92 | -- | Recursively fold up a monoid value for each vertex and all of its 93 | -- children's monoid values. You can transform the value in-transit before it 94 | -- is accumulated if you want. 95 | foldMapGraph 96 | :: (Ord v, Monoid m) 97 | => (v -> m) -- ^ embed the vertex 98 | -> (e -> m -> m) -- ^ transform with edge before it is accumulated 99 | -> Graph v e 100 | -> Map v m 101 | foldMapGraph f g gr = res 102 | where 103 | res = gr <&> 104 | M.foldMapWithKey (\s v -> f s <> foldMap (g v) (M.lookup s res)) 105 | 106 | allDescendants :: Ord v => Graph v e -> Map v (Set v) 107 | allDescendants = foldMapGraph 108 | S.singleton -- the node is embedded as itself 109 | (\_ -> id) -- ignore the edge 110 | 111 | usageCounts :: Ord v => Graph v Int -> Map v (Sum Int) 112 | usageCounts = foldMapGraph 113 | (const 0) -- ignore the nodes 114 | (\n x -> Sum n * (x + 1)) -- the edge multiplies the accumulator plus one 115 | ``` 116 | 117 | That's the curse of Haskell, I guess? If you write these things you can't help 118 | but notice the common patterns, and you somehow wind up trying to figure out 119 | the higher-order function that can abstract over them, even though you know you 120 | don't need to :) 121 | -------------------------------------------------------------------------------- /reflections/day09.md: -------------------------------------------------------------------------------- 1 | Let's tackle day 9! 2 | 3 | A good way to check if a sequence of 25 numbers can add to the 26th number is 4 | to just iterate over everything, like we might have done in day 1: 5 | 6 | ```haskell 7 | -- | check if, for ([x,y,z] ++ [a]), no pair in xyz can add to 'a'. If it's 8 | -- bad, it returns 'Just a'. 9 | isBad :: [Int] -> Maybe Int 10 | isBad xs0 = do 11 | x : xs <- Just $ reverse xs0 12 | let badCheck = null do 13 | y:ys <- tails (toList xs) 14 | z <- ys 15 | guard $ (y + z) == x 16 | x <$ guard badCheck 17 | ``` 18 | 19 | I use my favorite `Maybe` do-notation trick of pattern matching within the 20 | block to take advantage of do block short circuiting for `Maybe` with its 21 | `MonadFail` instance. If you reverse `xs0` then you can get the last item as 22 | the head, and the rest as the tail :) 23 | 24 | In `badCheck` we do a list-monad powered search (see my [Day 1 25 | Reflections](https://github.com/mstksg/advent-of-code-2020/blob/master/reflections-out/day01.md)) 26 | for more details on how it works. `badCheck` will return `True` if the search 27 | is empty (with `null`). `guard badCheck` will be Nothing if `badCheck` fails 28 | (and our list is good) and `Just x` if `badCheck` succeeds (and our list is 29 | bad). 30 | 31 | Part 1 is then just finding the first bad sequence: 32 | 33 | ```haskell 34 | part1 :: [Int] -> Maybe Int 35 | part1 xs = listToMaybe 36 | [ y 37 | | ys <- tails xs 38 | , Just y <- [isBad (take 26 ys)] 39 | ] 40 | ``` 41 | 42 | For part 2, there's a nice-ish way to do it in constant-time. First, we can 43 | generate a cumulative sum `cumSum` for the *entire* list. Then we know that 44 | `sumFrom(i,j)` in our original list is just `cumSum(j) - cumSum(i)`. This is 45 | similar to how definite integrals work, or also how you can find the area under 46 | a probability density function by subtracting two points from its cumulative 47 | distribution function. 48 | 49 | So now the problem just becomes finding `i,j` where `cumSum(j) - cumSum(i) == 50 | goal`. There's a clean imperative-ish way to do this that involves just 51 | "sliding" your window `i,j` up from `0,1`. If `cumSum(j) - cumSum(i)` is too 52 | small, increase `j` by 1 to open the window up a bit. If it's too big, 53 | increase `i` by 1 to close the window up a bit. 54 | 55 | ```haskell 56 | findBounds :: V.Vector Int -> Int -> Maybe (Int, Int) 57 | findBounds ns goal = go 0 1 58 | where 59 | go !i !j = do 60 | x <- ns V.!? i 61 | y <- ns V.!? j 62 | case compare (y - x) goal of 63 | LT -> go i (j + 1) 64 | EQ -> pure (i, j) 65 | GT -> go (i + 1) j 66 | ``` 67 | 68 | And there you go! 69 | 70 | ```haskell 71 | part2 :: [Int] -> Maybe Int 72 | part2 xs = do 73 | goal <- part1 xs 74 | let cumSum = V.fromList (scanl' (+) 0 xs) -- cumulative sum 75 | (i, j) <- findBounds cumSum goal 76 | let xs = take (j - i) . drop i $ ns 77 | pure $ minimum xs + maximum xs 78 | ``` 79 | 80 | If anything, maybe the implementation of `findBounds` shows how one might 81 | directly translate a tight mutable loop in an imperative language into a 82 | tail-recursive function in Haskell! 83 | 84 | We do often like to avoid explicitly writing recursive functions when we can, 85 | but in this case I'm not sure if there's a way to get around it other than 86 | switching to a full on mutable answer, or in a very complex way that is 87 | extremely specific to the situation. If you think of one, let me know! :D 88 | -------------------------------------------------------------------------------- /reflections/day10.md: -------------------------------------------------------------------------------- 1 | Today is another day where the "automatically build a memoized recursive map" 2 | in Haskell really shines :) It's essentially the same problem as Day 7. 3 | 4 | For the first part, once you sort the list, you can compute the differences and 5 | then build a frequency map 6 | 7 | ```haskell 8 | -- | Build a frequency map 9 | freqs :: Ord a => [a] -> Map a Int 10 | freqs = M.fromListWith (+) . map (,1) . toList 11 | 12 | diffs :: [Int] -> [Int] 13 | diffs xs@(_:ys) = zipWith (-) ys xs 14 | ``` 15 | 16 | ```haskell 17 | ghci> diffs [1,3,4,7] 18 | [2,1,3] 19 | ``` 20 | 21 | And so part 1 can be done with: 22 | 23 | ```haskell 24 | part1 :: [Int] -> Int 25 | part1 xs = (stepFreqs M.! 1) * (stepFreqs M.! 3) 26 | where 27 | xs' = 0 : xs ++ [maximum xs + 3] 28 | stepFreqs = freqs (diffs (sort xs')) 29 | ``` 30 | 31 | 32 | For part 2, if we get an `IntSet` of all of your numbers (and adding the zero, 33 | and the goal, the maximum + 3), then we can use it to build our `IntMap` of all 34 | the number of paths from a given number. 35 | 36 | ```haskell 37 | import Data.IntMap (IntMap) 38 | import Data.IntSet (IntSet) 39 | import qualified Data.IntMap as IM 40 | import qualified Data.IntSet as IS 41 | 42 | -- | A map of numbers to the count of how many paths from that number to 43 | -- the goal 44 | pathsToGoal :: IntSet -> IntMap Int 45 | pathsToGoal xs = res 46 | where 47 | res = flip IM.fromSet xs $ \i -> 48 | if i == goal 49 | then 1 50 | else sum [ IM.findWithDefault 0 (i + j) res 51 | | j <- [1,2,3] 52 | ] 53 | goal = IS.findMax is 54 | ``` 55 | 56 | Our answer is `res`, the map of numbers to the count of how many paths exist 57 | from that number to the goal. To generate the count for a given number `i`, we 58 | add the number of paths from `i+1`, `i+2`, and `i+3`. We get that count by 59 | looking it up in `res`! 60 | 61 | ```haskell 62 | part2 :: [Int] -> Int 63 | part2 xs = pathsToGoal xs IM.! 0 64 | where 65 | xs' = IS.fromList (0 : xs ++ [maximum xs + 3]) 66 | ``` 67 | 68 | A quick note --- after some discussion on the irc, we did [find a closed-form 69 | solution][d10cfs]...I might be editing this to implement it in Haskell 70 | eventually :) 71 | 72 | [d10cfs]: https://www.reddit.com/r/adventofcode/comments/kabi91/2020_day_10_closedform_mathematical_solution/ 73 | -------------------------------------------------------------------------------- /reflections/day11.md: -------------------------------------------------------------------------------- 1 | My first day on the leaderboard! :D 21 / 352. Had a big dip on my second part 2 | because I had some silly typos that were difficult to catch in the moment D: 3 | 4 | After refactoring things, I realized that part 1 and part 2 are really the 5 | same, with only two differences: 6 | 7 | 1. Each point as a different neighborhood set (in part 1, it's the immediate 8 | neighbors; in part 2, it's all of the line-of-sights in each direction). 9 | 2. Threshold for seats unseating is 4 for part 1 and 5 for part 2. 10 | 11 | So let's write our function parameterized on those two. We'll be storing our 12 | world as a `Map Point Bool`, where `False` represents an empty seat and `True` 13 | represents a full one. Floor points are not included in the map. 14 | 15 | ```haskell 16 | -- | A 2-vector type from the linear library, with a very convenient Num 17 | -- instance. 18 | data V2 a = V2 a a 19 | 20 | type Point = V2 Int 21 | 22 | -- | A useful utility function I keep around that counts the number of items in 23 | -- a container matching a predicate 24 | countTrue :: Foldable f => (a -> Bool) -> f a -> Int 25 | countTrue p = length . filter p . toList 26 | 27 | seatRule 28 | :: Int -- ^ exit seat threshold 29 | -> Map Point (Set Point) -- ^ neighbors for each point 30 | -> Map Point Bool 31 | -> Map Point Bool 32 | seatRule thr nmp mp = M.intersectionWith go nmp mp 33 | where 34 | go neighbs = \case 35 | Empty -> not (all (mp M.!) neighbs) 36 | Full -> 37 | let onNeighbs = countTrue (mp M.!) neighbs 38 | in not (onNeighbs >= thr) 39 | ``` 40 | 41 | Now we just need to create our neighborhood maps. 42 | 43 | ```haskell 44 | -- | The eight immediate neighbors around 0,0 45 | immediateNeighbs :: [Point] 46 | immediateNeighbs = 47 | [ V2 dx dy 48 | | dx <- [-1 .. 1] 49 | , dy <- if dx == 0 then [-1,1] else [-1..1] 50 | ] 51 | 52 | -- | From a set of seat locations, get a map of points to all of those points' 53 | -- neighbors where there is a seat. Should only need to be computed once. 54 | lineOfSights1 55 | :: Set Point 56 | -> Map Set (Set Point) 57 | lineOfSeights1 pts = M.fromSet go mp 58 | where 59 | go p _ = S.fromList 60 | . filter (`S.member` pts) 61 | . (+ p) 62 | $ immediateNeighbs 63 | 64 | -- | From a set of seat locations, Get a map of points to all of those points' 65 | -- visible neighbors. Should only need to be computed once. 66 | lineOfSights2 67 | :: Set Point 68 | -> Map Point (Set Point) 69 | lineOfSights2 bb pts = M.mapWithKey go pts 70 | where 71 | go p _ = S.fromList 72 | . mapMaybe (los p) 73 | $ immediateNeighbs 74 | los p d = find (`S.member` pts) 75 | . takeWhile inBoundingBox 76 | . tail 77 | $ iterate (+ d) p 78 | inBoundingBox = all (inRange (0, 99)) 79 | -- inRange from Data.Ix 80 | -- all from Data.Foldable and V2's Foldable instance 81 | ``` 82 | 83 | (I hard-coded the bounds here, but in my actual solution I inferred it from the 84 | input.) 85 | 86 | Now to solve! 87 | 88 | ```haskell 89 | -- | Handy utility function I have; repeat a function until you get the same 90 | -- result twice. 91 | fixedPoint :: Eq a => (a -> a) -> a -> a 92 | fixedPoint f = go 93 | where 94 | go !x 95 | | x == y = x 96 | | otherwise = go y 97 | where 98 | y = f x 99 | 100 | solveWith 101 | :: Int -- ^ exit seat threshold 102 | -> Map Point (Set Point) -- ^ neighbors for each point 103 | -> Map Point Bool -- ^ initial state 104 | -> Int -- ^ equilibrium size 105 | solveWith thr neighbs = countTrue id . fixedPoint (seatRule thr neighbs) 106 | 107 | part1 108 | :: Map Point Bool 109 | -> Int 110 | part1 mp = solveWith 4 los mp 111 | where 112 | los = lineOfSight1 (M.keysSet mp) 113 | 114 | part2 115 | :: Map Point Bool 116 | -> Int 117 | part2 mp = solveWith 5 los mp 118 | where 119 | los = lineOfSight2 (M.keysSet mp) 120 | ``` 121 | -------------------------------------------------------------------------------- /reflections/day13.md: -------------------------------------------------------------------------------- 1 | Aw man, I feel like I would have leaderboarded today had I not been busy :'( 2 | These type of number theory problems are the ones I usually do well on. 3 | 4 | Oh well! Silly internet points, right? 5 | 6 | For part 1, you just need to minimize a function on each bus ID: 7 | 8 | ```haskell 9 | part1 :: Int -> [Int] -> (Int, Int) 10 | part1 t0 xs = minimumBy (comparing snd) 11 | [ (x, waitTime) 12 | | x <- xs 13 | , let waitTime = x - (t0 `mod` x) 14 | ] 15 | ``` 16 | 17 | Part 2 is where things get interesting! Let's try to think of things 18 | inductively: start with small lists, and see how we would "add one more". 19 | 20 | Let's say we had `(offset, id)` pairs `(0,7)` and `(1,13)`, like in the 21 | example. This means that we want to find times where ``t `mod` 7 == 0`` and 22 | ``(t + 1) `mod` 13 == 0``. 23 | 24 | We can sort of do a manual search by hand to get `14` as our lowest candidate. 25 | But also, note that `14 + (7*13)n` for any integer `n` would preserve the offset 26 | property. `14`, `14 + 91`, `14 + 182`, etc. So the family of all "valid" 27 | numbers are `14 + (7*13)n`. 28 | 29 | Next, what if we wanted to find the situation for pairs `(0,7)`, `(1,13)`, and 30 | `(4,15)`? Well, we already know that any solution that includes `(0,7)` and 31 | `(1,13)` will be of the form `14 + (7*13)n`. So now we just need to find the 32 | *first* one of those that also matches `(4,15)` 33 | 34 | ```haskell 35 | -- 'until' repeatedly applies a function until it finds a value that matches a 36 | -- predicate 37 | ghci> until (\t -> (t + 4) `mod` 15 == 0) (+ (7*13)) 14 38 | 1106 39 | ``` 40 | 41 | Ah hah, good ol' `1106`. Well, `1106` isn't the only number that works. 42 | We can see that `1106 + (7*13*15)n` for any integer n would *also* work, since 43 | it preserves that mod property. 44 | 45 | And so, we can repeat this process over and over again for each new number we 46 | see. 47 | 48 | 1. Keep track of the current "lowest match" (`14`) and the current "search 49 | step" (`7*13`). 50 | 2. When you see a number, search that family until you find a new lowest match 51 | that includes the new number. 52 | 3. Use that new number as the next lowest match, and multiply it to get the 53 | new search step. 54 | 4. Rinse and repeat. 55 | 56 | Overall, this works pretty well as a `foldl`, where we keep this `(lowest 57 | match, search step)` pair as an accumulator, and update it as we see each new 58 | value in our list. 59 | 60 | ```haskell 61 | part2 :: [(Int, Int)] -> Int 62 | part2 = fst . foldl' go (0, 1) 63 | where 64 | go (!base, !step) (offset, i) = (base', step * i) 65 | where 66 | base' = until (\n -> (n + offset) `mod` i == 0) 67 | (+ step) 68 | base 69 | ``` 70 | -------------------------------------------------------------------------------- /reflections/day14.md: -------------------------------------------------------------------------------- 1 | I guess today is a "here's the algorithm, now implement it" puzzle, to 2 | contrast/take a break from yesterday's "here's the goal, figure out the 3 | algorithm" :) 4 | 5 | First, let's start with an intermediate data type representing the actions 6 | possible on each line: 7 | 8 | ```haskell 9 | data Instr = 10 | Mask [Maybe Bool] 11 | | Write Int Int 12 | ``` 13 | 14 | The mask will be a list of `Maybe Bool`, where `X` is `Nothing`, `0` is `Just 15 | False`, and `1` is `Just True`. However, it's important to reverse the string 16 | when parsing it from the input, because we want index `0` to correspond to bit 17 | `0`, index `1` to correspond to bit `1`, etc., to make our lives easier. 18 | 19 | That's because we can implement the application of a mask (for part 1) using 20 | [`ifoldl'`](https://hackage.haskell.org/package/lens-4.19.2/docs/Control-Lens-Indexed.html#v:ifoldl-39-), 21 | a version of `foldl'` that gives you an item's index as you are folding it: 22 | 23 | ```haskell 24 | import Data.Bits (clearBit, setBit) 25 | import Control.Lens.Indexed (ifoldl') 26 | 27 | applyMask1 :: Int -> [Maybe Bool] -> Int 28 | applyMask1 = ifoldl' $ \i x -> \case 29 | Nothing -> x 30 | Just False -> clearBit x i 31 | Just True -> setBit x i 32 | ``` 33 | 34 | If the bit list contains a `Nothing` in a given index, leave the item 35 | unchanged. If it contains a `Just False`, clear that index's bit (set it to 36 | zero). If it contains a `Just Nothing`, set that index's bit (set it to one). 37 | 38 | And that leaves part 1 as a foldl through all the instructions, keeping the 39 | current map and mask as state: 40 | 41 | ```haskell 42 | import Data.IntMap (IntMap) 43 | import qualified Data.IntMap as IM 44 | 45 | part1 :: [Instr] -> (IntMap Int, [Maybe Bool]) 46 | part1 = foldl' go (IM.empty, []) 47 | where 48 | go :: (IntMap Int, [Maybe Bool]) -> Instr -> (IntMap Int, [Maybe Bool]) 49 | go (!mp, !msk) = \case 50 | Mask msk' -> (mp, msk') 51 | Write addr n -> 52 | let mp' = IM.insert addr (applyMask1 n msk) mp 53 | in (mp', msk) 54 | ``` 55 | 56 | Part 2's mask application is interesting, because it lives in 57 | "non-determinancy". Basically, each bit mask bit application could potentially 58 | yield multiple possibilities. We have to accumulate every nested possibility. 59 | This feature is given to us by list's `Monad` instance, so we can swap 60 | `ifoldl'` for 61 | [`ifoldM`](https://hackage.haskell.org/package/lens-4.19.2/docs/Control-Lens-Indexed.html#v:ifoldlM): 62 | 63 | ```haskell 64 | ifoldl' :: (Int -> b -> a -> b) -> b -> [a] -> b 65 | ifoldlM :: (Int -> b -> a -> m b) -> b -> [a] -> m b 66 | ``` 67 | 68 | For `ifoldlM`, each result lives in monad `m`, so the semantics of "proceeding 69 | along the fold" are deferred to the `Monad` instance for `m`. If `m` is 70 | `Maybe`, it means that you only proceed if you get a `Just`, or else 71 | short-circuit with `Nothing`. If `m` is `IO`, it means that proceeding 72 | involves chaining the IO action's execution and binding the result to give it 73 | to the function's next iteration. If `m` is `[]` (list), it means that 74 | subsequent chaining will run the function on every *possibility* returned by 75 | the function's previous call, accumulating every possible way of choosing every 76 | possible choice. (I talked about this in more depth in [one of my first ever 77 | Haskell blog 78 | posts](https://blog.jle.im/entries/series/+monadplus-success-failure-monads.html)). 79 | 80 | ```haskell 81 | import Control.Lens.Indexed (ifoldlM) 82 | 83 | applyMask2 :: Int -> [Maybe Bool] -> [Int] 84 | applyMask2 = ifoldlM $ \i x -> \case 85 | Nothing -> [clearBit x i, setBit x i] 86 | Just False -> [x] 87 | Just True -> [setBit x i] 88 | ``` 89 | 90 | For these, we return a list of every possible change from a given bit mask bit. 91 | For the `Nothing` "floating" case, there are two possibilities; for the other 92 | two, there is only one. We trust list's `Monad` instance to properly thread 93 | over all possible results into a list of all possible changes that that `Int` 94 | could have been subjected to. 95 | 96 | And so, part 2 looks a lot like part 1! 97 | 98 | ```haskell 99 | part2 :: [Instr] -> (IntMap Int, [Maybe Bool]) 100 | part2 = foldl' go (IM.empty, []) 101 | where 102 | go :: (IntMap Int, [Maybe Bool]) -> Instr -> (IntMap Int, [Maybe Bool]) 103 | go (!mp, !msk) = \case 104 | Mask msk' -> (mp, msk') 105 | Write addr n -> 106 | let newMp = IM.fromList ((,n) <$> applyMask2 addr msk) 107 | in (newMp <> mp, msk) 108 | ``` 109 | 110 | `(<>)` here is a left-biased merger, so it merges in all of the newly seen 111 | indices into the existing ones. 112 | -------------------------------------------------------------------------------- /reflections/day15.md: -------------------------------------------------------------------------------- 1 | So it is yet another "here's the algorithm, implement it" days again! Only the 2 | challenge this time is...you should probably implement it to be really fast! 3 | 4 | I don't think there is *too* much wiggle room in how to implement things here; 5 | my original solution basically kept an `IntMap` to the last seen time of any 6 | value, and just repeatedly looked things up and modified the (current time, 7 | last said) tuple. 8 | 9 | My original solution took around 70 seconds to run, and that was what I used to 10 | submit things originally. But let's see if we can get it down to something a 11 | little less...perceptible :) This reflection can be a deep dive into writing 12 | tight, performant Haskell. 13 | 14 | The data type we'll be using is an *[unboxed mutable 15 | array](https://hackage.haskell.org/package/vector/docs/Data-Vector-Unboxed-Mutable.html)*. 16 | There's a trick we can use because we have a map from integers to values, we 17 | can just use the integer keys as the index to an array. This is usually a bad 18 | idea but for the fact that the keys we'll be using are bounded within a 19 | decently small range (we won't ever say a number that is greater than 30 20 | million), so we can definitely accumulate 30 million-item array into memory 21 | without any major problems. We'll also store our last-said times as `Int32` to 22 | be a little bit more efficient since we're trying to eek out every last bit of 23 | perf. 24 | 25 | So overall we still keep some state: the current time and the last said item. 26 | Since those are just integers, we can keep that as pure in memory using 27 | `StateT` running over `ST s` (the mutable state monad, where our mutable 28 | vectors will live). 29 | 30 | ```haskell 31 | import Control.Monad.ST 32 | import Control.Monad.State 33 | import GHC.Int (Int32) 34 | import qualified Data.Vector.Unboxed.Mutable as MV 35 | 36 | data LoopState = LS 37 | { lsLastSaid :: !Int 38 | , lsCurrTime :: !Int32 39 | } 40 | 41 | sayNext 42 | :: MV.MVector s Int32 -- ^ the mutable vector of last-seen times 43 | -> StateT (T2 Int32 Int) (ST s) () -- ^ an 'ST s' action with some pure (T2 Int32 Int) state 44 | sayNext v = do 45 | L s i <- get -- get the current pure state 46 | lst <- MV.read v x -- our last said is x, so look up the last time we saw it 47 | MV.write v x i -- update the last-time-seen 48 | let j | lst == 0 = 0 -- we haven't seen it 49 | | otherwise = i - lst -- we have seen it 50 | put (LS (fromIntegral j) (i + 1)) -- update last seen and current time 51 | {-# INLINE sayNext #-} 52 | ``` 53 | 54 | We will want to INLINE this so that it gets inlined directly into our main loop 55 | code. 56 | 57 | Oh, let's also write a function to initialize our sequence with starting 58 | inputs: 59 | 60 | ```haskell 61 | saySomething 62 | :: MV.MVector s Int32 -- ^ the mutable vector of last-seen times 63 | -> Int -- ^ a number to "say" 64 | -> StateT (T2 Int32 Int) (ST s) () -- ^ an 'ST s' action with some pure (T2 Int32 Int) state 65 | saySomething v y = do 66 | LS x i <- get 67 | MV.unsafeWrite v x i -- write the last seen number with the right time 68 | put (LS y (i + 1)) -- queue up the write of the number to say 69 | {-# INLINE saySomething #-} 70 | ``` 71 | 72 | And now we're good to go to put it all together! We can use `whileM_` from 73 | *[Control.Monad.Loops](https://hackage.haskell.org/package/monad-loops/docs/Control-Monad-Loops.html)* 74 | to emulate a while loop, where our condition is whenever `lsCurrTime` reaches 75 | the maximum value. 76 | 77 | ```haskell 78 | -- | Returns 'True' until we need to stop 79 | stopCond :: Int32 -> StateT (T2 Int32 Int) m Bool 80 | stopCond n = gets $ \(LS _ i) -> i < n 81 | {-# INLINE stopCond #-} 82 | -- gets f = f <$> get, it maps a function on top of a get 83 | 84 | looper :: Int -> [Int] -> Int 85 | looper n xs = runST $ flip evalStateT (LS 0 0) $ do 86 | v <- MV.replicate n 0 -- initialize our vector with zeros 87 | traverse_ (saySomething v) xs 88 | whileM_ (stopCond n) (sayNext v) 89 | gets lsLastSaid 90 | ``` 91 | 92 | On my machine (with some minor optimizations, like using 93 | `unsafeRead`/`unsafeWrite`), this runs in 230ms for part 2...a much more 94 | reasonable improvement over my original 70 seconds! :) 95 | 96 | ```haskell 97 | part1 :: [Int] -> Int 98 | part1 = looper 2020 99 | 100 | part2 :: [Int] -> Int 101 | part2 = looper 30000000 102 | ``` 103 | -------------------------------------------------------------------------------- /reflections/day16.md: -------------------------------------------------------------------------------- 1 | Today was a nice little self-contained constraint satisfaction problem! Well, 2 | it didn't have to be (apparently), but it was fun as one :) 3 | 4 | First, our data type: 5 | 6 | ```haskell 7 | type Passport = [Int] 8 | 9 | data Info = Info 10 | { iFields :: IntervalMap Int (Set Text) 11 | , iYours :: Passport 12 | , iTheirs :: [Passport] 13 | } 14 | ``` 15 | 16 | Here we're using `IntervalMap` from the *[data-interval][]* package, which 17 | makes it easy to store data at different intervals with easy lookups. For 18 | example, if we have `["class"]` at interval `(1,5)`, and we had `["row"]` at 19 | interval `(3,7)`, `IntervalMap` will merge them together (with `<>`, if we 20 | choose) to get `["class"]` at `(1,3)`, `["class","row"]` at `(3,5)`, and 21 | `["row"]` at `(5,7)`. 22 | 23 | [data-interval]: https://hackage.haskell.org/package/data-interval 24 | 25 | If we have this `IntervalMap`, part 1 becomes straightforward enough with the 26 | efficient `IM.notMember`: 27 | 28 | ```haskell 29 | import qualified Data.IntervalMap.Lazy as IM 30 | 31 | part1 :: Info -> Int 32 | part1 info = sum 33 | [ n 34 | | ns <- iTheirs info 35 | , n <- ns 36 | , n `IM.notMember` iFields info 37 | ] 38 | ``` 39 | 40 | So now let's move on to the search for part 2! 41 | 42 | Our goal is to get a list `[(Int, Set Text)]` of a column number (in the 43 | passport) with the set of all valid field names for that position. And because 44 | we are going to be doing a search, we want this list in order of smallest to 45 | largest valid-name sets. 46 | 47 | First, we can replace the `Int`s in each passport instead with the set of 48 | fields they are valid for 49 | 50 | ```haskell 51 | validate :: IntervalMap Int (Set Text) -> [Int] -> Maybe [Set Text] 52 | validate flds = traverse (`IM.lookup` flds) 53 | 54 | validateAll :: IntervalMap Int (Set Text) -> [Passport] -> [[Set Text]] 55 | validateAll flds = mapMaybe (validate flds) 56 | ``` 57 | 58 | Here ``(`IM.lookup` flds)`` is `Int -> Set Text`: it'll look up the `Set Text` 59 | corresponding to the interval that the `Int` falls under in the `IntervalMap`. 60 | It'll return `Nothing` if *any* of the `Int`s are invalid, and `Just` if *all* 61 | of the `Int`s are valid. 62 | 63 | Next we want to build our `[(Int, Set Text)]`. The `Set Text` is a set of what 64 | is valid for that column number, so to get the `Set Text` for `0`, for 65 | instance, we need to `S.intersection` all of the first `Set Text`s in our list,; 66 | to get the `Set Text` for `1`, we need to `S.intersection` all of the second 67 | `Set Text`s in our lists, etc. This can be done succinctly with a `transpose` 68 | (`transpose [[1,2,3],[4,5,6]] == [[1,4],[2,5],[3,6]]`). Then we can use 69 | `sortOn` to sort by the size of the valids set. 70 | 71 | ```haskell 72 | columnSets :: [[Set Text]] -> [(Int, Set Text)] 73 | columnSets = sortOn (S.size . snd) 74 | . zip [0..] 75 | . map (foldl1' S.intersection) 76 | . transpose 77 | ``` 78 | 79 | Now we're ready for our search! We'll be using `StateT` over list, to get a 80 | backtracking search with backtracking state (I described this technique in [a 81 | constraint solving blog 82 | post](https://blog.jle.im/entry/unique-sample-drawing-searches-with-list-and-statet.html)). 83 | Our state will be the `Set Text` of all the "committed" fields so far. 84 | 85 | ```haskell 86 | search :: [(Int, Set Text)] -> Maybe [(Int, Text)] 87 | search candidateMap = listToMaybe . flip evalStateT S.empty $ do 88 | for candidates $ \(i, cands) -> do -- for each (Int, Set Text): 89 | soFar <- get -- get the seen candidates 90 | pick <- lift . toList $ cands S.\\ soFar -- pick from the Set Text not including seens 91 | (i, pick) <$ modify (S.insert pick) -- propose this index/pick, inserting into seens 92 | ``` 93 | 94 | And that should be it for our search! In the end this gets the first `[(Int, 95 | Text)]` that is valid, matching a column ID to the field at that column. Our 96 | search supports backtracking through the list monad, but it should be noted 97 | that we actually don't end up needing it for the way the puzzle input is 98 | structured. But, because we sort our lists first from smallest to largest 99 | valid-sets, our solution ends up being equivalent to the non-backtracking 100 | method and backtracking is never actually triggered. 101 | 102 | And we can wrap it all up: 103 | 104 | ```haskell 105 | part2 :: Info -> Int 106 | part2 = product 107 | [ iYours info !! i 108 | | (i, fld) <- res 109 | , "departure" `isPrefixOf` fld 110 | ] 111 | where 112 | cSets = columnSets $ validateAll (iFields info) (iTheirs info) 113 | Just res = search cSets 114 | ``` 115 | -------------------------------------------------------------------------------- /reflections/day17.md: -------------------------------------------------------------------------------- 1 | Neat, Game of Life! :D Actually, the 3D/4D twist does make a big impact for 2 | the best method we'd pick: we run into the [curse of 3 | dimensionality](https://en.wikipedia.org/wiki/Curse_of_dimensionality). It 4 | means that when we get to 3D and 4D, our world will become vanishingly sparse. 5 | In my own input, only about 4% of the 3D space ended up being active, and 2% of 6 | my 4D space ends up being active. This means that holding a dense vector of 7 | all possible active points (which will be `(6+8+6)^n`) is up to 98% wasteful. 8 | And because of the way this process works, we have to completely copy our 9 | entire space at every iteration. 10 | 11 | In these times, I'm happy that Haskell has a nice immutable sparse 12 | data structure like `Set`. Sparse being beneficial in that we can easily look up and process 13 | only the 2% of active squares, and immutable being beneficial in that each step 14 | already requires a full copy in any case, so immutability doesn't give us any 15 | drawback. 16 | 17 | First a function to get all neighbors of a point, using the `V3` type from the 18 | *[linear](https://hackage.haskell.org/package/linear)* library, which I've used 19 | many times already for its convenient `Num` and `Applicative` instances: 20 | 21 | ```haskell 22 | import Data.Set (Set) 23 | import qualified Data.Set as S 24 | 25 | -- from linear 26 | data V3 a = V3 a a a 27 | -- its Applicative instance 28 | pure x = V3 x x x 29 | 30 | neighbsSet :: V3 Int -> Set (V3 Int) 31 | neighbsSet p = S.fromList 32 | [ p + d 33 | | d <- sequence (pure [-1,0,1]) 34 | , d /= pure 0 35 | ] 36 | ``` 37 | 38 | Just as a reminder, `pure [0,1]` for `V3 Int` gives us `V3 [0,1] [0,1] [0,1]`, 39 | and if we `sequence` that we get a cartesian N-product of all combinations `[V3 40 | 0 0, V3 0 0 1, V3 0 1 0, V3 0 1 1, V3 1 0 0, .. etc.]`. We add each of those 41 | to `p`, except for the one that is `V3 0 0 0`. 42 | 43 | Now we can write our stepper, which takes a `Set (V3 Int)` and returns the next 44 | `Set (V3 Int)` after applying the rules. We can do that first by making a `Map 45 | (V3 Int) Int`, where `Int` is the number of neighbors at a given point. This 46 | can be done by "exploding" every `V3 Int` in our set to a `Map (V3 Int) Int`, 47 | a map of all its neighbors keyed to values 1, and then using `M.unionsWith (+)` 48 | to union together all of those exploded neighbors, adding any overlapping keys. 49 | 50 | ```haskell 51 | import Data.Map (Map) 52 | import qualified Data.Map as M 53 | 54 | neighborMap :: Set (V3 Int) -> Map (V3 Int) Int 55 | neighborMap ps = M.unionsWith (+) 56 | [ M.fromSet (const 1) (neighbsSet p) 57 | | p <- S.toList ps 58 | ] 59 | ``` 60 | 61 | Now to implement the rules: 62 | 63 | ```haskell 64 | stepper 65 | :: Set (V3 Int) 66 | -> Set (V3 Int) 67 | stepper ps = stayAlive <> comeAlive 68 | where 69 | neighborCounts = neighborMap ps 70 | stayAlive = M.keysSet . M.filter (\n -> n == 2 || n == 3) $ 71 | neighborCounts `M.restrictKeys` ps 72 | comeAlive = M.keysSet . M.filter (== 3) $ 73 | neighborCounts `M.withoutKeys` ps 74 | ``` 75 | 76 | `stayAlive` is all of the `neighborCounts` keys that correspond to already-alive 77 | points (``neighborCounts `M.restrictKeys` ps``), but filtered to the counts 78 | that are 2 or 3. `comeAlive` is all of the `neighborCounts` keys that 79 | correspond to dead points (``neighborCounts `M.withoutKeys` ps``), but filtered 80 | to only counts that are exactly 3. And our result is the set union of both of 81 | those. 82 | 83 | 84 | So our part 1 becomes: 85 | 86 | ```haskell 87 | part1 :: Set (V3 Int) -> Int 88 | part1 = S.size . (!! 6) . iterate stepper 89 | ``` 90 | 91 | And for part 2...notice that all of our code actually never does anything 92 | *specific* to `V3`! In fact, if we leave the type signatures of `neighbsSet` 93 | and `neighborMap` and `stepper` off, GHC will actually suggest more general 94 | type signatures for us. 95 | 96 | ```haskell 97 | neighbsSet 98 | :: (Applicative f, Num a, Ord (f a), Traversable f) 99 | => f a -> Set (f a) 100 | 101 | neighborMap 102 | :: (Applicative f, Num a, Ord (f a), Traversable f) 103 | => Set (f a) 104 | -> Map (f a) Int 105 | 106 | stepper 107 | :: (Applicative f, Num a, Ord (f a), Traversable f) 108 | => Set (f a) 109 | -> Set (f a) 110 | ``` 111 | 112 | Neat! This means that our code *already works* for any other fixed-sized 113 | `Vector` type with a `Num` instance. Like, say...`V4`, also from *linear*? 114 | 115 | ```haskell 116 | -- also from the Linear library, with all the same instances 117 | data V4 a = V4 a a a a 118 | 119 | part1 :: Set (V3 Int) -> Int 120 | part1 = S.size . (!! 6) . iterate stepper 121 | 122 | part2 :: Set (V4 Int) -> Int 123 | part2 = S.size . (!! 6) . iterate stepper 124 | ``` 125 | 126 | And that's it --- code that should work for both parts :) 127 | -------------------------------------------------------------------------------- /reflections/day18.md: -------------------------------------------------------------------------------- 1 | Let's parse with parser combinators! 2 | 3 | The main way I have learned how to deal with these binary-operation parsers is 4 | to separate out the stages into a "bottom" level containing only the leaves 5 | (here, the int literals) and parentheses, and then build up layers of 6 | precedence one-by-one from highest to lowest. For the first part we only have 7 | two layers, then, since we only have one level of precedence. 8 | 9 | ```haskell 10 | {-# LANGUAGE OverloadedStrings #-} 11 | 12 | import qualified Text.Megaparsec as P 13 | import qualified Text.Megaparsec.Char as P 14 | import qualified Text.Megaparsec.Char.Lexer as PP 15 | 16 | type Parser = P.Parsec Void String 17 | 18 | parseBottom1 :: Parser Int 19 | parseBottom1 = P.choice 20 | [ PP.decimal 21 | , P.between "(" ")" parseTop1 -- use -XOverloadedStrings to get parsers that match strings 22 | ] 23 | 24 | parseTop1 :: Parser Int 25 | parseTop1 = do 26 | leftOfOp <- parseBottom1 -- parse the left hand side of a possible binary operator 27 | doNext acc 28 | where 29 | doNext acc = P.choice -- once we parse a left hand side, pick from: 30 | [ do " * " -- either it's a * 31 | rightOfOp <- parseBottom1 -- ... so we parse the right hand side and multiply 32 | doNext (acc * rightOfOp) 33 | , do " + " -- or it's a + 34 | rightOfOp <- parseBottom1 -- ... so we parse the right hand side and add 35 | doNext (acc + rightOfOp) 36 | , pure acc -- otherwise that was it, no operator 37 | ] 38 | ``` 39 | 40 | Remember that `leftOfOp` could either come from a leaf literal number or from a 41 | parenthesized equation. In the end, we get an `Int`, representing whatever 42 | number was on the left hand side of our operator. Then we move into `doNext`, 43 | which continually accumulates new operations after that first `leftOfOp` parse. 44 | 45 | If we see a `*`, we parse the right hand side, fold that into our accumulator 46 | and repeat until we hit a dead end and yield our accumulated value; same for 47 | `+`. 48 | 49 | So there's this sort of "cycle" that `parseTop` defers to `parseBottom` for its 50 | underlying things "in between" the operators, but `parseBottom` loops back up 51 | to `parseTop` to handle what is in the parentheses. 52 | 53 | ```haskell 54 | part1 :: String -> Maybe Int 55 | part1 = P.parseMaybe $ 56 | sum <$> P.many parseTop1 57 | ``` 58 | 59 | The twist for part 2 is that now we have to have another layer of precedence, 60 | so we split things out: 61 | 62 | ```haskell 63 | parseBottom2 :: Parser Int 64 | parseBottom2 = P.choice 65 | [ PP.decimal 66 | , P.between "(" ")" parseTop2 67 | ] 68 | 69 | parseMiddle2 :: Parser Int 70 | parseMiddle2 = do 71 | leftOfOp <- parseBottom2 72 | doNext leftOfOp 73 | where 74 | doNext acc = P.choice 75 | [ do " + " 76 | rightOfOp <- parseBottom2 77 | doNext (acc + rightOfOp) 78 | , pure acc 79 | ] 80 | 81 | parseTop2 :: Parser Int 82 | parseTop2 = do 83 | leftOfOp <- parseMiddle2 84 | doNext leftOfOp 85 | where 86 | doNext acc = P.choice 87 | [ do " * " 88 | rightOfOp <- parseMiddle2 89 | doNext (acc * rightOfOp) 90 | , pure acc 91 | ] 92 | ``` 93 | 94 | So the parser dependency again is kind of interesting: `parseTop2` is built up 95 | of chained `parseMiddle2`s, which is built up of chained `parseBottom2`, which 96 | could loop back up with `parseTop2` if detect parentheses. 97 | 98 | ```haskell 99 | part2 :: String -> Maybe Int 100 | part2 = P.parseMaybe $ 101 | sum <$> (parseTop2 `P.sepBy` P.newline) 102 | ``` 103 | 104 | Note that this chaining and looping behavior can be abstracted out --- that's 105 | essentially what I wrote in my [cleaned up solution][d18g]. But also the 106 | *[Control.Monad.Combinators.Expr](https://hackage.haskell.org/package/parser-combinators-1.2.1/docs/Control-Monad-Combinators-Expr.html)* 107 | module also abstracts over this pattern, letting you specify the "layers" you 108 | want, and it'll generate the right parser for you with the correct weaving of 109 | dependencies like I described here. But still, I think it's fun to see how 110 | these things end up looking like under the hood :) 111 | -------------------------------------------------------------------------------- /reflections/day21.md: -------------------------------------------------------------------------------- 1 | Another nice self-contained constraint satisfaction problem, along the lines of 2 | [Day 3 | 16](https://github.com/mstksg/advent-of-code-2020/blob/master/reflections-out/day16.md) 4 | :) Actually, after solving this one, I went back and rewrote my day 16 5 | solution in terms of a common solver function that works for both! 6 | 7 | ```haskell 8 | -- | Given a map of @k@ to possible @a@s for that @k@, find possible 9 | -- configurations where each @k@ is given its own unique @a@. 10 | pickUnique :: (Ord k, Ord a) => [(k, Set a)] -> [Map k a] 11 | pickUnique mp = flip evalStateT S.empty $ do 12 | fmap M.fromList . for opts . traverse $ \poss -> do 13 | seen <- get 14 | pick <- lift $ S.toList (poss `S.difference` seen) 15 | pick <$ modify (S.insert pick) 16 | where 17 | opts = sortOn (S.size . snd) mp 18 | ``` 19 | 20 | It uses `StateT` over list, like I described in [a constraint solving blog 21 | post](https://blog.jle.im/entry/unique-sample-drawing-searches-with-list-and-statet.html). 22 | Basically it explores all of the possibilities of drawing from a state of 23 | "items left-over to assign". The state is a `Set a` of items not yet picked, 24 | and at every step we non-deterministically `pick` an `a` out of the given `(k, 25 | Set a)` of options that hasn't already been chosen. We use that pick and 26 | add that picked item to the picked item set along that branch. 27 | 28 | We also sort by the size of the possibility set for each `k`, because starting 29 | with smaller possibilities keeps our tree tight at the top, instead of wide --- 30 | we can eliminate options much more quickly. 31 | 32 | Now all we need to do is to get our information into a `[(k, Set a)]`. In our 33 | case, this is `[(String, Set String)]` -- with each allergen, associate a set 34 | of possible foods they might be associated with. 35 | 36 | We can do this by just taking an intersection of all the possibilities on each 37 | line: 38 | 39 | ```haskell 40 | assembleOptions 41 | :: (Ord k, Ord a) 42 | => [(Set a, Set k)] -- set of foods, set of allergens 43 | -> Map k (Set a) -- each allergen with the foods they were seen with in all occurrences 44 | assembleOptions info = M.unionsWith S.intersection $ 45 | [ M.fromSet (const igr) alg -- a map of allergens to all foods they were seen with in this item 46 | | (igr, alg) <- info 47 | ] 48 | ``` 49 | 50 | We generate a list of allergens to all foods they were seen with on each item, 51 | and then `intersect` all of those foods within an allergen, so that our final 52 | `Map k (Set a)` matches each `k` allergen with a set ofall foods that were 53 | present in *all* of the occurrences of each allergen. 54 | 55 | 56 | Now part 2 is basically just reading off the results of `pickUnique` 57 | 58 | ```haskell 59 | part2 :: [(Set String, Set String)] -> Maybe [String] 60 | part2 = fmap M.elems . listToMaybe . pickUnique . assembleOptions 61 | ``` 62 | 63 | We definitely have a nice advantage here in that the `Map String String` (the 64 | result map of allergens to foods) already is sorted in order of allergens 65 | (alphabetically), so no need to do anything other than just `M.elems` :) 66 | 67 | Part 1 is definitely slightly more complicated: not only do we need to find the 68 | allergenic foods, we have to count the occurrences of non-allergenic foods in 69 | all the items: 70 | 71 | ```haskell 72 | part2 :: [(Set String, Set String)] -> Maybe Int 73 | part2 info = do 74 | allergenicFoods <- fmap (S.fromList . M.elems) 75 | . listToMaybe 76 | . pickUnique 77 | . assembleOptions 78 | $ info 79 | pure . sum $ 80 | [ length $ filter (`S.notMember` allergenicFoods) foods 81 | | (foods, _) <- info 82 | ] 83 | where 84 | allFoodOccurrences :: [String] 85 | allFoodOccurrences = concatMap (S.toList . fst) info 86 | ``` 87 | -------------------------------------------------------------------------------- /reflections/day24.md: -------------------------------------------------------------------------------- 1 | Day 24 brings us our third cellular automata puzzle of the year! :D The other 2 | ones were [Day 3 | 11](https://github.com/mstksg/advent-of-code-2020/blob/master/reflections-out/day11.md) 4 | and [Day 5 | 17](https://github.com/mstksg/advent-of-code-2020/blob/master/reflections-out/day17.md). 6 | In fact, I was able to mostly copy and paste my stepper code for Day 17 :) 7 | 8 | The main twist here is that we'd have to use hexy stepping and hexy neighbors. 9 | My initial solve used the *[grid](https://hackage.haskell.org/package/grid)* 10 | library to get the hexy steps neighbors, but I did go back and [implement the 11 | tiling 12 | myself](https://github.com/mhwombat/grid/wiki/Implementation%3A-Hexagonal-tiles) because 13 | it wasn't too bad :) 14 | 15 | For part 1, it can be nice to have some intermediate data types 16 | 17 | ```haskell 18 | data HexDirection = West 19 | | Northwest 20 | | Northeast 21 | | East 22 | | Southeast 23 | | Southwest 24 | 25 | toDirs :: String -> Maybe [HexDirection] 26 | toDirs = \case 27 | [] -> Just [] 28 | 'w':ds -> (West:) <$> toDirs ds 29 | 'e':ds -> (East:) <$> toDirs ds 30 | 'n':'e':ds -> (Northeast:) <$> toDirs ds 31 | 'n':'w':ds -> (Northwest:) <$> toDirs ds 32 | 's':'e':ds -> (Southeast:) <$> toDirs ds 33 | 's':'w':ds -> (Southwest:) <$> toDirs ds 34 | _ -> Nothing 35 | 36 | hexOffset :: HexDirection -> Point 37 | hexOffset = \case 38 | West -> V2 (-1) 0 39 | Northwest -> V2 (-1) 1 40 | Northeast -> V2 0 1 41 | East -> V2 1 0 42 | Southeast -> V2 1 (-1) 43 | Southwest -> V2 0 (-1) 44 | ``` 45 | 46 | So we can parse into a list of `[HexDirection]` paths, and then we can get our 47 | starting points by xoring all of the final points: 48 | 49 | ```haskell 50 | import Data.Bits 51 | 52 | initialize :: [[HexDirection]] -> Set Point 53 | initialize = M.keysSet . M.filter id . M.fromListWith xor 54 | . map (\steps -> (sum (map hexOffset steps), True)) 55 | ``` 56 | 57 | And this gives us the set of all active points, which we can use to answer part 58 | one. But now, on to the simulation! 59 | 60 | First, we can expand the neighbors of a given point in our hexy coords: 61 | 62 | ```haskell 63 | neighbors :: Point -> Set Point 64 | neighbors (V2 x y) = S.fromDistinctAscList 65 | [ V2 (x-1) y 66 | , V2 (x-1) (y+1) 67 | , V2 x (y-1) 68 | , V2 x (y+1) 69 | , V2 (x+1) (y-1) 70 | , V2 (x+1) y 71 | ] 72 | ``` 73 | 74 | And our step function looks more or less the same as day 17: 75 | 76 | ```haskell 77 | step :: Set Point -> Set Point 78 | step ps = stayAlive <> comeAlive 79 | where 80 | neighborCounts :: Map Point Int 81 | neighborCounts = M.unionsWith (+) 82 | [ M.fromSet (const 1) (neighbors p) 83 | | p <- S.toList ps 84 | ] 85 | stayAlive = M.keysSet . M.filter (\n -> n == 1 || n == 2) $ 86 | neighborCounts `M.restrictKeys` ps 87 | comeAlive = M.keysSet . M.filter (== 2) $ 88 | neighborCounts `M.withoutKeys` ps 89 | ``` 90 | 91 | First we collect a `Map Point Int` of each point to how many live neighbors it 92 | has. Then the *live* points (``neighborCounts `M.restrictKeys` ps``) are 93 | filtered for only the ones with 1 or 2 live neighbors, and the *dead* points 94 | (``neighborCounts `M.withoutKeys` ps``) are filtered for only the ones with 2 95 | live neighbors. And the resulting new set of live points is `stayAlive <> 96 | comeAlive`. 97 | 98 | ```haskell 99 | part1 :: [[HexDirection]] -> Int 100 | part1 = S.size . initialize 101 | 102 | part2 :: [[HexDirection]] -> Int 103 | part2 paths = S.size (iterate step pts !!! 100) 104 | where 105 | pts = initialize paths 106 | ``` 107 | -------------------------------------------------------------------------------- /reflections/day25.md: -------------------------------------------------------------------------------- 1 | Merry Christmas everyone, it's December 25th :D 2 | 3 | The Christmas Problem is usually supposed to be a quick and concise one, since 4 | Eric wants people to spend the holiday with their family. This one is a bit 5 | obscured in the jargon, but once you sort through it, the solution ends up 6 | being pretty tidy :) 7 | 8 | In the end you are exponentiating the number 7 by a given number of times (the 9 | loop count) to get the number you see. So you're solving `7^x = `...so that's basically a logarithm! 11 | 12 | The *[arithmoi](https://hackage.haskell.org/package/arithmoi)* library (which I 13 | previously used in problems like Day 13) offers a nice discrete logarithm 14 | function, so that's really all we need to use: 15 | 16 | 17 | ```haskell 18 | type Magic = 20201227 19 | 20 | magicGroup :: CyclicGroup Integer Magic 21 | Just magicGroup = cyclicGroup 22 | 23 | primBase :: PrimitiveRoot Magic 24 | Just primBase = isPrimitiveRoot magicGroup 7 25 | 26 | findSecret :: Mod Magic -> Maybe Natural 27 | findSecret = fmap (discreteLogarithm magicGroup primBase) 28 | . isMultElement 29 | ``` 30 | 31 | And so our final solution is just (after converting the input numbers to the 32 | `Mod Magic` data type)... 33 | 34 | ```haskell 35 | day25 :: Mod Magic -> Mod Magic -> Maybe Integer 36 | day52 x y = do 37 | secret <- findSecret x 38 | pure . getVal $ y ^% secret -- exponentiate by the loop count 39 | ``` 40 | 41 | Merry Christmas to everyone, and happy New Years too. Thank you for reading 42 | these reflections, and I hope they have been helpful in some way :) Special 43 | thanks to Eric Wastl too for such a great event as always. Until next year! 44 | -------------------------------------------------------------------------------- /script/generate_days.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env stack 2 | -- stack --install-ghc runghc --resolver lts-16 --package template --package text --package filepath --package directory -- -Wall 3 | 4 | {-# LANGUAGE LambdaCase #-} 5 | {-# LANGUAGE OverloadedStrings #-} 6 | 7 | import Control.Monad 8 | import Data.Text.Template 9 | import System.FilePath 10 | import System.Directory 11 | import Text.Printf 12 | import qualified Data.Text as T 13 | import qualified Data.Text.IO as T 14 | import qualified Data.Text.Lazy.IO as TL 15 | 16 | outRoot :: FilePath 17 | outRoot = "src/AOC/Challenge" 18 | 19 | main :: IO () 20 | main = do 21 | temp <- template <$> T.readFile "template/DayXX.hs" 22 | forM_ [1..25] $ \i -> do 23 | let newFilePath = outRoot printf "Day%02d.hs" i 24 | Just newFile = renderA temp (ctx i) 25 | skip <- doesFileExist newFilePath 26 | unless skip $ 27 | TL.writeFile newFilePath newFile 28 | 29 | ctx :: Int -> ContextA Maybe 30 | ctx i = \case 31 | "day" -> Just . T.pack $ printf "%02d" i 32 | "day_short" -> Just . T.pack $ printf "%d" i 33 | _ -> Nothing 34 | -------------------------------------------------------------------------------- /script/open_files.vim: -------------------------------------------------------------------------------- 1 | " 2 | " open_files.vim 3 | " ============== 4 | " 5 | " use: 6 | " 7 | " :source script/open_files.vim 8 | " 9 | " to load the function into scope, where you can call with: 10 | " 11 | " :call OpenAoC(day) 12 | " 13 | " If you use the :source command in a buffer where the filename has a number 14 | " in it (like Day16.hs), this will automatically open all the files associated 15 | " with that day. 16 | " 17 | " Change s:year below to open test data for a different year 18 | " 19 | 20 | let s:year = 2020 21 | 22 | function! OpenAoC(day) 23 | let l:daystr = printf("%02d",a:day) 24 | let l:yearstr = printf("%04d",s:year) 25 | let l:files = ["src/AOC/Challenge/Day" . l:daystr . ".hs", 26 | \"data/" . l:daystr . ".txt", 27 | \"prompt/" . l:daystr . "a.md", 28 | \"prompt/" . l:daystr . "b.md", 29 | \"test-data/" . l:yearstr . "/" . l:daystr . "a.txt", 30 | \"test-data/" . l:yearstr . "/" . l:daystr . "b.txt", 31 | \"reflections/day" . l:daystr . ".md", 32 | \"bench-out/day" . l:daystr . ".txt" 33 | \] 34 | 35 | for fn in reverse(l:files) 36 | execute "e " . fnameescape(fn) 37 | endfor 38 | endfunction 39 | 40 | let s:buffday = str2nr(matchstr(expand('%:t:r'), '\d\+')) 41 | 42 | if (s:buffday == 0) 43 | echo "no valid file found in buffer; use :call OpenAoC(day) to open a day" 44 | else 45 | echo "found day" . string(s:buffday) 46 | call OpenAoC(s:buffday) 47 | endif 48 | -------------------------------------------------------------------------------- /src/AOC.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC 3 | -- Copyright : (c) Justin Le 2018 4 | -- License : BSD3 5 | -- 6 | -- Maintainer : justin@jle.im 7 | -- Stability : experimental 8 | -- Portability : non-portable 9 | -- 10 | -- Single-stop entry point for the library's functionality and all 11 | -- challenge solutions. 12 | -- 13 | 14 | module AOC ( 15 | module AOC 16 | ) where 17 | 18 | import AOC.Challenge as AOC 19 | import AOC.Run as AOC 20 | import AOC.Run.Config as AOC 21 | import AOC.Run.Interactive as AOC 22 | import AOC.Run.Load as AOC 23 | import AOC.Solver as AOC 24 | import AOC.Util as AOC 25 | 26 | -------------------------------------------------------------------------------- /src/AOC/Challenge.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | {-# OPTIONS_GHC -Wno-dodgy-exports #-} 3 | {-# OPTIONS_GHC -Wno-unused-imports #-} 4 | 5 | -- | 6 | -- Module : AOC.Challenge 7 | -- Copyright : (c) Justin Le 2018 8 | -- License : BSD3 9 | -- 10 | -- Maintainer : justin@jle.im 11 | -- Stability : experimental 12 | -- Portability : non-portable 13 | -- 14 | -- Gather together all challenges and collect them into a single map. 15 | -- 16 | 17 | module AOC.Challenge ( 18 | module AOC 19 | , ChallengeMap 20 | , ChallengeSpec(..), Part(..) 21 | , challengeMap 22 | , lookupSolution 23 | , Day(..), dayInt, mkDay, mkDay_ 24 | , solSpec 25 | , charPart 26 | ) where 27 | 28 | import AOC.Challenge.Day01 as AOC 29 | import AOC.Challenge.Day02 as AOC 30 | import AOC.Challenge.Day03 as AOC 31 | import AOC.Challenge.Day04 as AOC 32 | import AOC.Challenge.Day05 as AOC 33 | import AOC.Challenge.Day06 as AOC 34 | import AOC.Challenge.Day07 as AOC 35 | import AOC.Challenge.Day08 as AOC 36 | import AOC.Challenge.Day09 as AOC 37 | import AOC.Challenge.Day10 as AOC 38 | import AOC.Challenge.Day11 as AOC 39 | import AOC.Challenge.Day12 as AOC 40 | import AOC.Challenge.Day13 as AOC 41 | import AOC.Challenge.Day14 as AOC 42 | import AOC.Challenge.Day15 as AOC 43 | import AOC.Challenge.Day16 as AOC 44 | import AOC.Challenge.Day17 as AOC 45 | import AOC.Challenge.Day18 as AOC 46 | import AOC.Challenge.Day19 as AOC 47 | import AOC.Challenge.Day20 as AOC 48 | import AOC.Challenge.Day21 as AOC 49 | import AOC.Challenge.Day22 as AOC 50 | import AOC.Challenge.Day23 as AOC 51 | import AOC.Challenge.Day24 as AOC 52 | import AOC.Challenge.Day25 as AOC 53 | 54 | import AOC.Discover 55 | import AOC.Solver 56 | import Advent 57 | import Control.Monad 58 | import Data.Finite 59 | import Data.Map (Map) 60 | import qualified Data.Map as M 61 | 62 | -- | A map of all challenges. 63 | challengeMap :: ChallengeMap 64 | challengeMap = mkChallengeMap $$(solutionList "src/AOC/Challenge") 65 | 66 | -- | Lookup up a solution from a 'ChallengeMap' 67 | lookupSolution :: ChallengeSpec -> Map Day (Map Part a) -> Maybe a 68 | lookupSolution CS{..} = M.lookup _csPart <=< M.lookup _csDay 69 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day01.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day01 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 1. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day01 ( 11 | day01a 12 | , day01b 13 | , knapsack 14 | ) where 15 | 16 | import AOC.Common (firstJust) 17 | import AOC.Solver ((:~>)(..)) 18 | import Data.IntSet (IntSet) 19 | import Data.Type.Nat (Nat(..), Nat1, Nat2, SNat(..), SNatI(..), snat) 20 | import Text.Read (readMaybe) 21 | import qualified Data.IntSet as IS 22 | import qualified Data.Vec.Lazy as Vec 23 | 24 | -- | Given a goal sum and a set of numbers to pick from, finds the @n@ 25 | -- numbers in the set that add to the goal sum. The number of items 26 | -- desired is inferred from the desired length of the return type. 27 | knapsack 28 | :: forall n. SNatI n 29 | => Int -- ^ goal sum 30 | -> IntSet -- ^ set of options 31 | -> Maybe (Vec.Vec ('S n) Int) -- ^ resulting n items that sum to the goal 32 | knapsack = case snat :: SNat n of 33 | SZ -> \goal xs -> 34 | if goal `IS.member` xs 35 | then Just $ Vec.singleton goal 36 | else Nothing 37 | SS -> \goal xs -> flip firstJust (IS.toList xs) $ \x -> 38 | let goal' = goal - x 39 | (_, ys) = IS.split x xs 40 | in (x Vec.:::) <$> knapsack goal' ys 41 | 42 | day01a :: [Int] :~> Int 43 | day01a = MkSol 44 | { sParse = traverse readMaybe . lines 45 | , sShow = show 46 | , sSolve = fmap product . knapsack @Nat1 2020 . IS.fromList 47 | } 48 | 49 | day01b :: [Int] :~> Int 50 | day01b = MkSol 51 | { sParse = traverse readMaybe . lines 52 | , sShow = show 53 | , sSolve = fmap product . knapsack @Nat2 2020 . IS.fromList 54 | } 55 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day02.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day02 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 2. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day02 ( 11 | day02a 12 | , day02b 13 | ) where 14 | 15 | import AOC.Common (countTrue, CharParser, parseLines) 16 | import AOC.Solver ((:~>)(..)) 17 | import Control.DeepSeq (NFData) 18 | import Control.Monad.Combinators (some) 19 | import GHC.Generics (Generic) 20 | import Text.Megaparsec (anySingle) 21 | import Text.Megaparsec.Char (char, space) 22 | import Text.Megaparsec.Char.Lexer (decimal) 23 | 24 | data Policy = P 25 | { pIx1 :: Int 26 | , pIx2 :: Int 27 | , pChar :: Char 28 | , pPass :: String 29 | } 30 | deriving (Show, Eq, Ord, Generic) 31 | instance NFData Policy 32 | 33 | policy :: CharParser Policy 34 | policy = P <$> decimal 35 | <*> (char '-' *> decimal) 36 | <*> (space *> anySingle) 37 | <*> (char ':' *> space *> some anySingle) 38 | 39 | validate1 :: Policy -> Bool 40 | validate1 P{..} = n >= pIx1 && n <= pIx2 41 | where 42 | n = countTrue (== pChar) pPass 43 | 44 | validate2 :: Policy -> Bool 45 | validate2 P{..} = n == 1 46 | where 47 | n = countTrue (== pChar) [pPass !! (pIx1 - 1), pPass !! (pIx2 - 1)] 48 | 49 | day02a :: [Policy] :~> Int 50 | day02a = MkSol 51 | { sParse = parseLines policy 52 | , sShow = show 53 | , sSolve = Just . countTrue validate1 54 | } 55 | 56 | day02b :: [Policy] :~> Int 57 | day02b = MkSol 58 | { sParse = parseLines policy 59 | , sShow = show 60 | , sSolve = Just . countTrue validate2 61 | } 62 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day03.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day03 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | 8 | module AOC.Challenge.Day03 ( 9 | day03a 10 | , day03b 11 | , validCoord 12 | ) where 13 | 14 | import AOC.Common (countTrue) 15 | import AOC.Solver ((:~>)(..)) 16 | import Data.Char (isSpace) 17 | import Data.Finite (Finite, modulo) 18 | 19 | type Coord = (Finite 31, Int) 20 | 21 | validCoord 22 | :: Finite 31 -- ^ dx 23 | -> Int -- ^ dy 24 | -> Coord 25 | -> Bool 26 | validCoord dx dy = \(x,y) -> 27 | let (i,r) = y `divMod` dy 28 | in r == 0 && dx * modulo (fromIntegral i) == x 29 | 30 | countLine :: Finite 31 -> Int -> String -> Int 31 | countLine dx dy = countTrue (uncurry tree) 32 | . zip (splitOut <$> [0..]) 33 | where 34 | checkCoord = validCoord dx dy 35 | tree xy c = c == '#' && checkCoord xy 36 | splitOut i = (fromIntegral x, y) 37 | where 38 | (!y, !x) = i `divMod` 31 39 | 40 | day03a :: String :~> Int 41 | day03a = MkSol 42 | { sParse = Just . filter (not . isSpace) 43 | , sShow = show 44 | , sSolve = Just . countLine 3 1 45 | } 46 | 47 | day03b :: String :~> Int 48 | day03b = MkSol 49 | { sParse = Just . filter (not . isSpace) 50 | , sShow = show 51 | , sSolve = \s -> Just . product $ 52 | [ countLine 1 1 53 | , countLine 3 1 54 | , countLine 5 1 55 | , countLine 7 1 56 | , countLine 1 2 57 | ] <*> [s] 58 | } 59 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day04.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day04 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 4. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day04 ( 11 | day04a 12 | , day04b 13 | ) where 14 | 15 | import AOC.Common (hexDigit, decimalDigit) 16 | import AOC.Solver ((:~>)(..)) 17 | import Control.Applicative (Const(..)) 18 | import Control.Lens (preview) 19 | import Control.Monad ((<=<)) 20 | import Data.Char (isDigit, toUpper) 21 | import Data.Finite (Finite) 22 | import Data.Functor.Identity (Identity(..)) 23 | import Data.List.Split (splitOn) 24 | import Data.Maybe (mapMaybe) 25 | import Data.Monoid (First(..)) 26 | import Data.Monoid.OneLiner (GMonoid(..)) 27 | import GHC.Generics (Generic) 28 | import Refined (Refined, FromTo, SizeEqualTo, refineThrow) 29 | import Text.Read (readMaybe) 30 | import qualified Barbies as B 31 | 32 | type a <-> b = Refined (FromTo a b) Int 33 | type n ** a = Refined (SizeEqualTo n) [a] 34 | type FirstRaw = Const (First String) 35 | type Raw = Const String 36 | 37 | data Height = 38 | HCm (150 <-> 193) 39 | | HIn ( 59 <-> 76) 40 | deriving (Show, Read, Eq, Ord) 41 | 42 | data Eye = AMB | BLU | BRN | GRY | GRN | HZL | OTH 43 | deriving (Show, Read, Eq, Ord, Enum) 44 | 45 | data Passport f = Passport 46 | { pByr :: f (1920 <-> 2002) 47 | , pIyr :: f (2010 <-> 2020) 48 | , pEyr :: f (2020 <-> 2030) 49 | , pHgt :: f Height 50 | , pHcl :: f (6 ** Finite 16) 51 | , pEcl :: f Eye 52 | , pPid :: f (9 ** Finite 10) 53 | } 54 | deriving (Generic) 55 | 56 | instance B.FunctorB Passport 57 | instance B.ApplicativeB Passport 58 | instance B.TraversableB Passport 59 | instance B.ConstraintsB Passport 60 | deriving instance B.AllBF Show f Passport => Show (Passport f) 61 | deriving via GMonoid (Passport f) instance B.AllBF Semigroup f Passport => Semigroup (Passport f) 62 | deriving via GMonoid (Passport f) instance B.AllBF Monoid f Passport => Monoid (Passport f) 63 | 64 | newtype Parser a = Parser { runParser :: String -> Maybe a } 65 | 66 | passportParser :: Passport Parser 67 | passportParser = Passport 68 | { pByr = Parser $ refineThrow <=< readMaybe 69 | , pIyr = Parser $ refineThrow <=< readMaybe 70 | , pEyr = Parser $ refineThrow <=< readMaybe 71 | , pHgt = Parser $ \str -> 72 | let (x, u) = span isDigit str 73 | in case u of 74 | "cm" -> fmap HCm . refineThrow =<< readMaybe x 75 | "in" -> fmap HIn . refineThrow =<< readMaybe x 76 | _ -> Nothing 77 | , pHcl = Parser $ \case 78 | '#':n -> refineThrow =<< traverse (preview hexDigit) n 79 | _ -> Nothing 80 | , pEcl = Parser $ readMaybe . map toUpper 81 | , pPid = Parser $ refineThrow <=< traverse (preview decimalDigit) 82 | } 83 | 84 | loadPassportField :: String -> Passport FirstRaw 85 | loadPassportField str = case splitOn ":" str of 86 | [k,v] -> case k of 87 | "byr" -> mempty { pByr = Const (pure v) } 88 | "iyr" -> mempty { pIyr = Const (pure v) } 89 | "eyr" -> mempty { pEyr = Const (pure v) } 90 | "hgt" -> mempty { pHgt = Const (pure v) } 91 | "hcl" -> mempty { pHcl = Const (pure v) } 92 | "ecl" -> mempty { pEcl = Const (pure v) } 93 | "pid" -> mempty { pPid = Const (pure v) } 94 | _ -> mempty 95 | _ -> mempty 96 | 97 | loadPassport :: String -> Maybe (Passport Raw) 98 | loadPassport = B.btraverse (\(Const (First x)) -> Const <$> x) 99 | . foldMap loadPassportField 100 | . words 101 | 102 | parsePassportField :: String -> Passport First 103 | parsePassportField = B.bzipWith go passportParser . loadPassportField 104 | where 105 | go p (Const (First x)) = First $ runParser p =<< x 106 | 107 | parsePassport :: String -> Maybe (Passport Identity) 108 | parsePassport = B.btraverse (fmap Identity . getFirst) 109 | . foldMap parsePassportField 110 | . words 111 | 112 | day04a :: [String] :~> Int 113 | day04a = MkSol 114 | { sParse = Just . splitOn "\n\n" 115 | , sShow = show 116 | , sSolve = Just . length . mapMaybe loadPassport 117 | } 118 | 119 | day04b :: [String] :~> Int 120 | day04b = MkSol 121 | { sParse = Just . splitOn "\n\n" 122 | , sShow = show 123 | , sSolve = Just . length . mapMaybe parsePassport 124 | } 125 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day05.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day05 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 5. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day05 ( 11 | day05a 12 | , day05b 13 | ) where 14 | 15 | import AOC.Solver ((:~>)(..)) 16 | import Data.Bits (complement, shiftR, (.&.)) 17 | import Data.Char (ord) 18 | import Data.List (foldl') 19 | import qualified Control.Foldl as F 20 | 21 | seatId :: String -> Int 22 | seatId = foldl' iGuessWe'reDoingThis 0 23 | where 24 | -- heh 25 | iGuessWe'reDoingThis n c = 26 | 2 * n + (complement (ord c) `shiftR` 2) .&. 1 27 | 28 | -- | Find the first missing item in the collection, in a single pass 29 | findHole :: F.Fold Int (Maybe Int) 30 | findHole = do 31 | mn <- F.minimum 32 | mx <- F.maximum 33 | sm <- F.sum 34 | pure $ 35 | missingItem <$> mn <*> mx <*> pure sm 36 | where 37 | missingItem mn mx sm = totalSum - sm 38 | where 39 | totalSum = mx*(mx+1)`div`2 - mn*(mn-1)`div`2 40 | 41 | day05a :: [String] :~> Int 42 | day05a = MkSol 43 | { sParse = Just . lines 44 | , sShow = show 45 | , sSolve = F.fold (F.premap seatId F.maximum) 46 | } 47 | 48 | day05b :: [String] :~> Int 49 | day05b = MkSol 50 | { sParse = Just . lines 51 | , sShow = show 52 | , sSolve = F.fold (F.premap seatId findHole) 53 | } 54 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day06.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day06 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 6. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day06 ( 11 | day06a 12 | , day06b 13 | ) where 14 | 15 | import AOC.Solver ((:~>)(..)) 16 | import Data.Bits (setBit, popCount, (.&.), (.|.)) 17 | import Data.Char (ord) 18 | import Data.List (foldl') 19 | import Data.List.NonEmpty (NonEmpty) 20 | import Data.List.Split (splitOn) 21 | import Data.Maybe (mapMaybe) 22 | import qualified Data.List.NonEmpty as NE 23 | 24 | type CharSet = Word 25 | 26 | toCharSet :: [Char] -> CharSet 27 | toCharSet = foldl' insertCharSet 0 28 | where 29 | insertCharSet cs c = cs `setBit` i 30 | where 31 | i = ord c - ord 'a' 32 | 33 | answers :: [String] -> Maybe (NonEmpty CharSet) 34 | answers = (fmap . fmap) toCharSet . NE.nonEmpty 35 | 36 | day06With 37 | :: (CharSet -> CharSet -> CharSet) 38 | -> [[String]] :~> Int 39 | day06With f = MkSol 40 | { sParse = Just . map lines . splitOn "\n\n" 41 | , sShow = show 42 | , sSolve = Just . sum . mapMaybe (fmap (popCount . foldr1 f) . answers) 43 | } 44 | 45 | day06a :: [[String]] :~> Int 46 | day06a = day06With (.|.) 47 | 48 | day06b :: [[String]] :~> Int 49 | day06b = day06With (.&.) 50 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day07.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | -- | 3 | -- Module : AOC.Challenge.Day07 4 | -- License : BSD3 5 | -- 6 | -- Stability : experimental 7 | -- Portability : non-portable 8 | -- 9 | -- Day 7. See "AOC.Solver" for the types used in this module! 10 | 11 | module AOC.Challenge.Day07 ( 12 | day07a 13 | , day07b 14 | , bagParser 15 | ) where 16 | 17 | import AOC.Common (pWord, parseLines, CharParser) 18 | import AOC.Solver ((:~>)(..)) 19 | import Control.Applicative (many) 20 | import Data.Map (Map) 21 | import Data.Semigroup (Sum(..)) 22 | import Data.Set (Set) 23 | import Data.Text (Text) 24 | import Text.Megaparsec (try) 25 | import Text.Megaparsec.Char (space) 26 | import Text.Megaparsec.Char.Lexer (decimal) 27 | import qualified Data.Map as M 28 | import qualified Data.Set as S 29 | import qualified Data.Text as T 30 | 31 | type Bag = (Text, Text) 32 | type Graph v e = Map v (Map v e) 33 | 34 | target :: Bag 35 | target = ("shiny", "gold") 36 | 37 | bagParser :: CharParser (Bag, Map Bag Int) 38 | bagParser = do 39 | nm <- bagName <* pWord 40 | bs <- fmap M.fromList . many . try $ do 41 | n <- decimal <* space 42 | b <- bagName 43 | pure (b, n) 44 | pure (nm, bs) 45 | where 46 | bagName :: CharParser Bag 47 | bagName = (,) <$> (T.pack <$> pWord) <*> (T.pack <$> pWord <* pWord) 48 | 49 | flipGraph :: Ord v => Graph v e -> Graph v e 50 | flipGraph mp = M.fromListWith M.union 51 | [ (m, M.singleton n e) 52 | | (n, ms) <- M.toList mp 53 | , (m, e ) <- M.toList ms 54 | ] 55 | 56 | -- | Recursively fold up a monoid value for each vertex and all of its 57 | -- children's monoid values. You can transform the value in-transit before 58 | -- it is accumulated if you want. 59 | foldMapGraph 60 | :: (Ord v, Monoid m) 61 | => (v -> m) -- ^ embed the vertex 62 | -> (e -> m -> m) -- ^ transform with edge before it is accumulated 63 | -> Graph v e 64 | -> Map v m 65 | foldMapGraph f g gr = res 66 | where 67 | res = M.foldMapWithKey (\s v -> f s <> foldMap (g v) (M.lookup s res)) 68 | <$> gr 69 | 70 | allDescendants :: Ord v => Graph v e -> Map v (Set v) 71 | allDescendants = foldMapGraph 72 | S.singleton -- the node is embedded as itself 73 | (\_ -> id) -- ignore the edge 74 | 75 | usageCounts :: Ord v => Graph v Int -> Map v (Sum Int) 76 | usageCounts = foldMapGraph 77 | (const 0) -- ignore the nodes 78 | (\n x -> Sum n * (x + 1)) -- the edge multiplies the accumulator plus one 79 | 80 | day07a :: Graph Bag Int :~> Int 81 | day07a = MkSol 82 | { sParse = fmap M.fromList . parseLines bagParser 83 | , sShow = show 84 | , sSolve = M.lookup target . fmap S.size . allDescendants . flipGraph 85 | } 86 | 87 | day07b :: Map Bag (Map Bag Int) :~> Int 88 | day07b = MkSol 89 | { sParse = fmap M.fromList . parseLines bagParser 90 | , sShow = show 91 | , sSolve = M.lookup target . fmap getSum . usageCounts 92 | } 93 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day08.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day08 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 8. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day08 ( 11 | day08a 12 | , day08b 13 | ) where 14 | 15 | import AOC.Common (perturbationsBy, CharParser, parseLines, pDecimal) 16 | import AOC.Solver ((:~>)(..)) 17 | import Control.DeepSeq (NFData) 18 | import Control.Lens (_1, Ixed(..), Index, IxValue, (^?)) 19 | import Data.IntSet (IntSet) 20 | import Data.Maybe (listToMaybe) 21 | import Data.Vector (Vector) 22 | import GHC.Generics (Generic) 23 | import qualified Data.Functor.Foldable as R 24 | import qualified Data.Functor.Foldable.TH as R 25 | import qualified Data.IntSet as IS 26 | import qualified Data.Vector as V 27 | import qualified Text.Megaparsec as P 28 | import qualified Text.Megaparsec.Char as P 29 | 30 | data Instr = NOP | ACC | JMP 31 | deriving (Generic, Eq, Ord, Show) 32 | instance NFData Instr 33 | 34 | type Command = (Instr, Int) 35 | 36 | instrParser :: CharParser Instr 37 | instrParser = P.choice 38 | [ NOP <$ P.string "nop" 39 | , ACC <$ P.string "acc" 40 | , JMP <$ P.string "jmp" 41 | ] 42 | 43 | commandParser :: CharParser Command 44 | commandParser = (,) <$> (instrParser <* P.space) <*> pDecimal 45 | 46 | -- RIP explicit state 47 | 48 | -- data CState = CS { csPtr :: !Int, csAcc :: !Int } 49 | -- deriving (Generic, Show) 50 | -- instance NFData CState 51 | 52 | -- initialCS :: CState 53 | -- initialCS = CS 0 0 54 | 55 | -- runCommand 56 | -- :: (Ixed t, Index t ~ Int, IxValue t ~ (Instr, Int)) 57 | -- => t 58 | -- -> CState 59 | -- -> Maybe CState 60 | -- runCommand cmds cs = (cmds ^? ix (csPtr cs)) <&> \case 61 | -- (NOP, _) -> cs & #csPtr +~ 1 62 | -- (ACC, i) -> cs & #csPtr +~ 1 63 | -- & #csAcc +~ i 64 | -- (JMP, i) -> cs & #csPtr +~ i 65 | 66 | data EndType = Halt | Loop 67 | deriving (Generic, Eq, Ord, Show) 68 | instance NFData EndType 69 | 70 | data AccStream = EndAcc EndType | Step AccStream | Acc Int AccStream 71 | R.makeBaseFunctor ''AccStream 72 | 73 | -- | Unfold an 'AccStream' over a program bank (@t@), given a seen-items 74 | -- list and the current instruction pointer. 75 | vmStreamCoalg 76 | :: (Ixed t, Index t ~ Int, IxValue t ~ (Instr, Int)) 77 | => t 78 | -> (IntSet, Int) 79 | -> AccStreamF (IntSet, Int) 80 | vmStreamCoalg cmds (!seen, !i) 81 | | i `IS.member` seen = EndAccF Loop 82 | | otherwise = case cmds ^? ix i of 83 | Nothing -> EndAccF Halt 84 | Just cmd -> case cmd of 85 | (NOP, _) -> StepF (seen', i+1) 86 | (ACC, n) -> AccF n (seen', i+1) 87 | (JMP, n) -> StepF (seen', i+n) 88 | where 89 | seen' = i `IS.insert` seen 90 | 91 | -- | Collapse an 'AccStream' to get the sum and the end state. 92 | sumStreamAlg 93 | :: AccStreamF (EndType, Int) 94 | -> (EndType, Int) 95 | sumStreamAlg = \case 96 | EndAccF es -> (es, 0) 97 | StepF a -> a 98 | AccF n (es, !x) -> (es, x + n) 99 | 100 | exhaustVM 101 | :: (Ixed t, Index t ~ Int, IxValue t ~ (Instr, Int)) 102 | => t 103 | -> (EndType, Int) 104 | exhaustVM cmds = R.hylo sumStreamAlg (vmStreamCoalg cmds) (IS.empty, 0) 105 | 106 | day08a :: Vector Command :~> Int 107 | day08a = MkSol 108 | { sParse = fmap V.fromList . parseLines commandParser 109 | , sShow = show 110 | , sSolve = Just . snd . exhaustVM 111 | } 112 | 113 | day08b :: Vector Command :~> Int 114 | day08b = MkSol 115 | { sParse = fmap V.fromList . parseLines commandParser 116 | , sShow = show 117 | , sSolve = \cmds0 -> listToMaybe [ 118 | i 119 | | cmds <- perturbationsBy (traverse . _1) perturbs cmds0 120 | , let (es, i) = exhaustVM cmds 121 | , es == Halt 122 | ] 123 | } 124 | where 125 | perturbs = \case 126 | NOP -> [JMP] 127 | ACC -> [] 128 | JMP -> [NOP] 129 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day09.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day09 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 9. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day09 ( 11 | day09a 12 | , day09b 13 | ) where 14 | 15 | import AOC.Common (slidingWindows, firstJust) 16 | import AOC.Solver ((:~>)(..), dyno_) 17 | import Control.Monad (guard) 18 | import Data.Foldable (toList) 19 | import Data.List (scanl', tails) 20 | import Data.Sequence (Seq(..)) 21 | import Text.Read (readMaybe) 22 | import qualified Data.Vector as V 23 | 24 | isBad :: Seq Int -> Maybe Int 25 | isBad xs0 = do 26 | (xs :|> x) <- pure xs0 27 | let badCheck = null do 28 | y:ys <- tails (toList xs) 29 | z <- ys 30 | guard $ (y + z) == x 31 | x <$ guard badCheck 32 | 33 | oddOneOut :: Int -> [Int] -> Maybe Int 34 | oddOneOut w = firstJust isBad . slidingWindows (w + 1) 35 | 36 | day09a :: [Int] :~> Int 37 | day09a = MkSol 38 | { sParse = traverse readMaybe . lines 39 | , sShow = show 40 | , sSolve = oddOneOut (dyno_ "window" 25) 41 | } 42 | 43 | findBounds :: V.Vector Int -> Int -> Maybe (Int, Int) 44 | findBounds ns goal = go 0 1 45 | where 46 | go !i !j = do 47 | x <- ns V.!? i 48 | y <- ns V.!? j 49 | case compare (y - x) goal of 50 | LT -> go i (j + 1) 51 | EQ -> pure (i, j) 52 | GT -> go (i + 1) j 53 | 54 | day09b :: [Int] :~> (Int, Int) 55 | day09b = MkSol 56 | { sParse = traverse readMaybe . lines 57 | , sShow = \(x,y) -> show (x + y) 58 | , sSolve = \ns -> do 59 | goal <- oddOneOut (dyno_ "window" 25) ns 60 | let cumsum = V.fromList (scanl' (+) 0 ns) 61 | (i, j) <- findBounds cumsum goal 62 | let xs = take (j - i) . drop i $ ns 63 | pure (minimum xs, maximum xs) 64 | } 65 | 66 | -- an implementation using a priority search queue, which should have 67 | -- efficient lookup and popping. but unfortunately it has too much overhead 68 | -- to offer any overall advantage 69 | 70 | -- isBad2 :: IntPSQ Int () -> Maybe Int 71 | -- isBad2 q = do 72 | -- (goal, _, _, xs) <- IntPSQ.minView q 73 | -- let badCheck = null do 74 | -- (x,_,_) <- IntPSQ.toList xs 75 | -- let y = goal - x 76 | -- guard $ y > x 77 | -- guard $ y `IntPSQ.member` xs 78 | -- goal <$ guard badCheck 79 | 80 | -- oddOneOut2 :: Int -> [Int] -> Maybe Int 81 | -- oddOneOut2 w = firstJust isBad2 82 | -- . reverse 83 | -- . sortedSlidingWindowsInt (w + 1) 84 | -- . reverse 85 | -- . map (,()) 86 | 87 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day10.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day10 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 10. See "AOC.Solver" for the types used in this module! 9 | -- 10 | -- After completing the challenge, it is recommended to: 11 | 12 | module AOC.Challenge.Day10 ( 13 | day10a 14 | , day10b 15 | ) where 16 | 17 | import AOC.Common (freqs, lookupFreq) 18 | import AOC.Solver ((:~>)(..)) 19 | import Data.IntMap (IntMap) 20 | import Data.IntSet (IntSet) 21 | import Text.Read (readMaybe) 22 | import qualified Data.IntMap as IM 23 | import qualified Data.IntSet as IS 24 | 25 | toChain :: [Int] -> IntSet 26 | toChain xs = xsset `IS.union` IS.fromList [0, top + 3] 27 | where 28 | xsset = IS.fromList xs 29 | top = IS.findMax xsset 30 | 31 | day10a :: [Int] :~> (Int, Int) 32 | day10a = MkSol 33 | { sParse = traverse readMaybe . lines 34 | , sShow = \(x,y) -> show (x * y) 35 | , sSolve = \(IS.toList . toChain->xs) -> Just 36 | let fs = freqs (zipWith (-) (drop 1 xs) xs) 37 | in (lookupFreq 1 fs, lookupFreq 3 fs) 38 | } 39 | 40 | findOrZero :: Num a => Int -> IntMap a -> a 41 | findOrZero = IM.findWithDefault 0 42 | 43 | -- | A map of numbers to the count of how many paths from that number to 44 | -- the goal 45 | pathsToGoal :: Num a => IntSet -> IntMap a 46 | pathsToGoal is = res 47 | where 48 | res = flip IM.fromSet is $ \i -> 49 | if i == goal 50 | then 1 51 | else sum [ findOrZero (i + j) res 52 | | j <- [1,2,3] 53 | ] 54 | goal = IS.findMax is 55 | 56 | -- -- it's about 1.5x slower 57 | -- gapMethod :: [Int] -> Int 58 | -- gapMethod xs = sfst 59 | -- . foldl' go (T2 1 0) 60 | -- $ zipWith (-) (tail xs) xs 61 | -- where 62 | -- go (T2 prod run) 1 = T2 prod (run + 1) 63 | -- go (T2 prod run) 3 64 | -- | run > 0 = T2 (prod * trib run) 0 65 | -- | otherwise = T2 prod 0 66 | 67 | -- trib :: Int -> Int 68 | -- trib i = tribs !!! (i + 2) 69 | -- where 70 | -- tribs = 0:0:1:zipWith3 (\x y z -> x + y + z) tribs (tail tribs) (tail (tail tribs)) 71 | 72 | day10b :: [Int] :~> Int 73 | day10b = MkSol 74 | { sParse = traverse readMaybe . lines 75 | , sShow = show 76 | , sSolve = Just . findOrZero 0 . pathsToGoal . toChain 77 | -- , sSolve = Just . gapMethod . IS.toList . toChain 78 | } 79 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day12.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day12 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 12. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day12 ( 11 | day12a 12 | , day12b 13 | ) where 14 | 15 | import AOC.Common.Point (Point, Dir(..), dirPoint, rotPoint, mannDist) 16 | import AOC.Solver ((:~>)(..)) 17 | import Control.DeepSeq (NFData) 18 | import Data.Group (pow) 19 | import Data.List (foldl') 20 | import Data.Map (Map) 21 | import GHC.Generics (Generic) 22 | import Linear (V2(..), (*^)) 23 | import Text.Read (readMaybe) 24 | import qualified Data.Map as M 25 | 26 | data Instr = Forward Int 27 | | Turn Dir 28 | | Move Point 29 | deriving (Show, Eq, Generic) 30 | instance NFData Instr 31 | 32 | mkInstr :: Map Char (Int -> Instr) 33 | mkInstr = M.fromList 34 | [ ('F', Forward) 35 | , ('L', Turn . pow West . (`div` 90)) 36 | , ('R', Turn . pow East . (`div` 90)) 37 | , ('N', Move . (*^ dirPoint North)) 38 | , ('S', Move . (*^ dirPoint South)) 39 | , ('E', Move . (*^ dirPoint East )) 40 | , ('W', Move . (*^ dirPoint West )) 41 | ] 42 | 43 | parseInstr :: String -> Maybe Instr 44 | parseInstr [] = Nothing 45 | parseInstr (c:n) = M.lookup c mkInstr <*> readMaybe n 46 | 47 | day12a :: [Instr] :~> Point 48 | day12a = MkSol 49 | { sParse = traverse parseInstr . lines 50 | , sShow = show . mannDist 0 51 | , sSolve = Just . snd . foldl' go (East, 0) 52 | } 53 | where 54 | go :: (Dir, Point) -> Instr -> (Dir, Point) 55 | go (!dir, !p) = \case 56 | Forward n -> (dir , p + n *^ dirPoint dir) 57 | Turn d -> (dir <> d, p ) 58 | Move r -> (dir , p + r ) 59 | 60 | day12b :: [Instr] :~> Point 61 | day12b = MkSol 62 | { sParse = sParse day12a 63 | , sShow = show . mannDist 0 64 | , sSolve = Just . fst . foldl' go (0, V2 10 1) 65 | } 66 | where 67 | go :: (Point, Point) -> Instr -> (Point, Point) 68 | go (!shp, !wp) = \case 69 | Forward n -> (shp + n *^ wp, wp ) 70 | Turn d -> (shp , rotPoint d wp) 71 | Move r -> (shp , wp + r ) 72 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day13.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day13 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 13. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day13 ( 11 | day13a 12 | , day13b 13 | ) where 14 | 15 | import AOC.Common (CharParser, parseMaybeLenient) 16 | import AOC.Solver ((:~>)(..)) 17 | import Control.Applicative ((<|>)) 18 | import Data.Foldable (minimumBy) 19 | import Data.List (foldl') 20 | import Data.Maybe (mapMaybe, catMaybes) 21 | import Data.Ord (comparing) 22 | import qualified Text.Megaparsec as P 23 | import qualified Text.Megaparsec.Char as P 24 | import qualified Text.Megaparsec.Char.Lexer as PL 25 | 26 | parseTrains :: Num a => CharParser [Maybe a] 27 | parseTrains = (Nothing <$ P.char 'x' <|> Just <$> PL.decimal) 28 | `P.sepBy` P.char ',' 29 | 30 | day13a :: (Int, [Int]) :~> (Int, Int) 31 | day13a = MkSol 32 | { sParse = parseMaybeLenient $ 33 | (,) <$> (PL.decimal <* P.newline) 34 | <*> (catMaybes <$> parseTrains) 35 | , sShow = \(x,y) -> show $ x * y 36 | , sSolve = \(t0, xs) -> Just $ minimumBy (comparing snd) 37 | [ (x, waitTime) 38 | | x <- xs 39 | , let waitTime = x - (t0 `mod` x) 40 | ] 41 | } 42 | 43 | day13b :: [(Int, Int)] :~> Int 44 | day13b = MkSol 45 | { sParse = parseMaybeLenient $ do 46 | _ <- P.manyTill P.anySingle P.newline 47 | mapMaybe sequenceA . zip [0,1..] <$> parseTrains 48 | , sShow = show 49 | , sSolve = Just . fst . foldl' go (0, 1) 50 | } 51 | where 52 | go (!base, !step) (offset, i) = (base', step * i) 53 | where 54 | base' = until (\n -> (n + offset) `mod` i == 0) 55 | (+ step) 56 | base 57 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day14.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | -- | 4 | -- Module : AOC.Challenge.Day14 5 | -- License : BSD3 6 | -- 7 | -- Stability : experimental 8 | -- Portability : non-portable 9 | -- 10 | -- Day 14. See "AOC.Solver" for the types used in this module! 11 | 12 | module AOC.Challenge.Day14 ( 13 | day14a 14 | , day14b 15 | ) where 16 | 17 | import AOC.Common (CharParser, parseLines) 18 | import AOC.Solver ((:~>)(..)) 19 | import Control.DeepSeq (NFData) 20 | import Control.Lens.Indexed (ifoldlM, ifoldl') 21 | import Data.Bits (setBit, clearBit) 22 | import Data.Functor (void) 23 | import Data.IntMap (IntMap) 24 | import Data.List (foldl') 25 | import GHC.Generics (Generic) 26 | import qualified Data.IntMap as IM 27 | import qualified Text.Megaparsec as P 28 | import qualified Text.Megaparsec.Char as P 29 | import qualified Text.Megaparsec.Char.Lexer as PP 30 | 31 | data Instr = 32 | Mask [Maybe Bool] 33 | | Write Int Int 34 | deriving (Show, Eq, Ord, Generic) 35 | instance NFData Instr 36 | 37 | applyMask1 :: Int -> [Maybe Bool] -> Int 38 | applyMask1 = ifoldl' $ \i x -> \case 39 | Nothing -> x 40 | Just False -> clearBit x i 41 | Just True -> setBit x i 42 | 43 | day14a :: [Instr] :~> Int 44 | day14a = MkSol 45 | { sParse = parseLines parseInstr 46 | , sShow = show 47 | , sSolve = Just . sum . fst . foldl' go mempty 48 | } 49 | where 50 | go :: (IntMap Int, [Maybe Bool]) -> Instr -> (IntMap Int, [Maybe Bool]) 51 | go (!mp, !msk) = \case 52 | Mask msk' -> (mp, msk') 53 | Write addr n -> (IM.insert addr (applyMask1 n msk) mp, msk) 54 | 55 | applyMask2 :: Int -> [Maybe Bool] -> [Int] 56 | applyMask2 = ifoldlM $ \i x -> \case -- we can save like 2ms with manual ifoldl' 57 | Nothing -> [clearBit x i, setBit x i] 58 | Just False -> [x] 59 | Just True -> [setBit x i] 60 | 61 | day14b :: [Instr] :~> Int 62 | day14b = MkSol 63 | { sParse = sParse day14a 64 | , sShow = show 65 | , sSolve = Just . sum . fst . foldl' go mempty 66 | } 67 | where 68 | go :: (IntMap Int, [Maybe Bool]) -> Instr -> (IntMap Int, [Maybe Bool]) 69 | go (!mp, !msk) = \case 70 | Mask msk' -> (mp, msk') 71 | Write addr n -> (IM.fromList ((,n) <$> applyMask2 addr msk) <> mp, msk) 72 | 73 | parseInstr :: CharParser Instr 74 | parseInstr = (Mask <$> P.try masker) P.<|> (uncurry Write <$> memer) 75 | where 76 | masker = do 77 | void "mask = " 78 | reverse <$> P.many bitter 79 | bitter = P.choice 80 | [ Just True <$ P.char '1' 81 | , Just False <$ P.char '0' 82 | , Nothing <$ P.char 'X' 83 | ] 84 | memer = (,) 85 | <$> ("mem[" *> PP.decimal <* "]") 86 | <*> (" = " *> PP.decimal) 87 | 88 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day15.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day15 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 15. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day15 ( 11 | day15a 12 | , day15b 13 | ) where 14 | 15 | import AOC.Solver ((:~>)(..)) 16 | import Control.Monad.Loops (whileM_) 17 | import Control.Monad.ST (runST) 18 | import Control.Monad.State.Strict (evalStateT, get, gets, put) 19 | import Data.Foldable (for_) 20 | import Data.List.Split (splitOn) 21 | import GHC.Int (Int32) 22 | import Text.Read (readMaybe) 23 | import qualified Data.Vector.Unboxed.Mutable as MV 24 | 25 | day15a :: [Int] :~> Int 26 | day15a = MkSol 27 | { sParse = traverse readMaybe . splitOn "," 28 | , sShow = show 29 | , sSolve = Just . looper 2020 30 | } 31 | 32 | day15b :: [Int] :~> Int 33 | day15b = MkSol 34 | { sParse = sParse day15a 35 | , sShow = show 36 | , sSolve = Just . looper 30000000 37 | } 38 | 39 | data LoopState = LS 40 | { lsLastSaid :: !Int 41 | , lsCurrTime :: !Int32 42 | } 43 | 44 | looper :: Int -> [Int] -> Int 45 | looper n xs0 = runST $ flip evalStateT (LS 0 0) $ do 46 | v <- MV.replicate n 0 47 | for_ xs0 $ \y -> do 48 | LS x i <- get 49 | MV.unsafeWrite v x i 50 | put (LS y (i + 1)) 51 | whileM_ (gets ((< n32) . lsCurrTime)) $ do 52 | LS x i <- get 53 | lst <- MV.unsafeRead v x 54 | MV.unsafeWrite v x i 55 | let j | lst == 0 = 0 56 | | otherwise = i - lst 57 | put (LS (fromIntegral j) (i + 1)) 58 | gets lsLastSaid 59 | where 60 | n32 :: Int32 61 | n32 = fromIntegral n 62 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day16.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | -- | 4 | -- Module : AOC.Challenge.Day16 5 | -- License : BSD3 6 | -- 7 | -- Stability : experimental 8 | -- Portability : non-portable 9 | -- 10 | -- Day 16. See "AOC.Solver" for the types used in this module! 11 | 12 | module AOC.Challenge.Day16 ( 13 | day16a 14 | , day16b 15 | ) where 16 | 17 | import AOC.Common (CharParser, withAllSized, pickUnique) 18 | import AOC.Solver ((:~>)(..), dyno_) 19 | import Control.DeepSeq (NFData) 20 | import Data.Char (isAlpha, isSpace) 21 | import Data.Distributive (distribute) 22 | import Data.IntervalMap.Strict (IntervalMap) 23 | import Data.List.NonEmpty (NonEmpty(..)) 24 | import Data.Maybe (listToMaybe, mapMaybe) 25 | import Data.Set (Set) 26 | import Data.Text (Text) 27 | import GHC.Generics (Generic) 28 | import qualified Data.ExtendedReal as ER 29 | import qualified Data.Interval as I 30 | import qualified Data.IntervalMap.Strict as IM 31 | import qualified Data.Map as M 32 | import qualified Data.Set as S 33 | import qualified Data.Text as T 34 | import qualified Data.Vector.Sized as V 35 | import qualified Text.Megaparsec as P 36 | import qualified Text.Megaparsec.Char as P 37 | import qualified Text.Megaparsec.Char.Lexer as PP 38 | 39 | type Passport = [Int] 40 | data Info = Info 41 | { iFields :: IntervalMap Int (Set Text) 42 | , iYours :: Passport 43 | , iTheirs :: [Passport] 44 | } 45 | deriving (Show, Eq, Generic) 46 | instance NFData Info 47 | 48 | day16a :: Info :~> Int 49 | day16a = MkSol 50 | { sParse = P.parseMaybe parseInfo 51 | , sShow = show 52 | , sSolve = \Info{..} -> Just . sum $ 53 | [ n 54 | | ns <- iTheirs 55 | , n <- ns 56 | , n `IM.notMember` iFields 57 | ] 58 | } 59 | 60 | day16b :: Info :~> [Int] 61 | day16b = MkSol 62 | { sParse = sParse day16a 63 | , sShow = show . product 64 | , sSolve = \Info{..} -> do 65 | th : ths <- pure $ mapMaybe (traverse (`IM.lookup` iFields)) iTheirs 66 | withAllSized (th :| ths) $ \vths -> do 67 | yours <- V.fromList iYours 68 | let candidates = V.toList . V.indexed 69 | . fmap (foldl1 S.intersection) 70 | $ distribute vths 71 | validMap <- listToMaybe $ pickUnique candidates 72 | pure 73 | [ yours `V.index` i 74 | | (i, k) <- M.toList validMap 75 | , dyno_ "prefix" "departure" `T.isPrefixOf` k 76 | ] 77 | } 78 | 79 | parseInfo :: CharParser Info 80 | parseInfo = do 81 | iFields <- IM.fromListWith (<>) . concat <$> P.many (tok (P.try fieldParser)) 82 | tok "your ticket:" 83 | iYours <- tok $ passportParser 84 | tok "nearby tickets:" 85 | iTheirs <- passportParser `P.sepBy` P.newline 86 | pure Info{..} 87 | where 88 | tok p = p <* P.some P.newline 89 | fieldParser = do 90 | k <- (P.satisfy (\c -> isAlpha c || isSpace c) `P.manyTill` ":") <* " " 91 | vs <- rangeParser `P.sepBy` P.string " or " 92 | pure $ (,S.singleton (T.pack k)) <$> vs 93 | rangeParser = (I.<=..<=) 94 | <$> (ER.Finite <$> PP.decimal <* "-") 95 | <*> (ER.Finite <$> PP.decimal) 96 | passportParser = PP.decimal `P.sepBy` "," 97 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day18.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | -- | 4 | -- Module : AOC.Challenge.Day18 5 | -- License : BSD3 6 | -- 7 | -- Stability : experimental 8 | -- Portability : non-portable 9 | -- 10 | -- Day 18. See "AOC.Solver" for the types used in this module! 11 | 12 | module AOC.Challenge.Day18 ( 13 | day18a 14 | , day18b 15 | ) where 16 | 17 | import AOC.Solver ((:~>)(..)) 18 | import Control.Monad (MonadPlus) 19 | import Data.Char (digitToInt) 20 | import Data.Void (Void) 21 | import qualified Text.Megaparsec as P 22 | import qualified Text.Megaparsec.Char as P 23 | 24 | type Parser = P.Parsec Void String 25 | 26 | -- | A right-associative syntax 27 | data Syntax f a = Syntax 28 | { sBinOps :: [f (a -> a -> a)] -- ^ Operations at each level; highest precedence is last. 29 | , sPrim :: f a -- ^ How to parse a primitive 30 | , sPar :: f a -> f a -- ^ parentheses 31 | } 32 | 33 | exprSyntax1 :: Syntax Parser Int 34 | exprSyntax1 = Syntax 35 | { sBinOps = [ P.choice [ (*) <$ " * ", (+) <$ " + " ] ] -- all same level 36 | , sPrim = digitToInt <$> P.digitChar 37 | , sPar = P.between "(" ")" 38 | } 39 | 40 | exprSyntax2 :: Syntax Parser Int 41 | exprSyntax2 = Syntax 42 | { sBinOps = [ (*) <$ " * " -- + higher than * 43 | , (+) <$ " + " 44 | ] 45 | , sPrim = digitToInt <$> P.digitChar 46 | , sPar = P.between "(" ")" 47 | } 48 | 49 | parseSyntax :: forall f a. MonadPlus f => Syntax f a -> f a 50 | parseSyntax Syntax{..} = parseTopLevel 51 | where 52 | parseTopLevel :: f a 53 | parseTopLevel = parseLevels sBinOps 54 | parseLevels :: [f (a -> a -> a)] -> f a 55 | parseLevels = \case 56 | [] -> sPrim P.<|> sPar parseTopLevel 57 | o:os -> 58 | let parseDown = parseLevels os 59 | parseThisLevelWith x = (P.<|> pure x) $ do 60 | f <- o 61 | y <- parseDown 62 | parseThisLevelWith (f x y) 63 | in parseDown >>= parseThisLevelWith 64 | 65 | day18 :: (Num a, Show a) => Syntax Parser a -> String :~> a 66 | day18 s = MkSol 67 | { sParse = Just 68 | , sShow = show 69 | , sSolve = P.parseMaybe $ sum <$> (parseSyntax s `P.sepBy` P.newline) 70 | } 71 | {-# INLINE day18 #-} 72 | 73 | day18a :: String :~> Int 74 | day18a = day18 exprSyntax1 75 | 76 | day18b :: String :~> Int 77 | day18b = day18 exprSyntax2 78 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day19.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | -- | 4 | -- Module : AOC.Challenge.Day19 5 | -- License : BSD3 6 | -- 7 | -- Stability : experimental 8 | -- Portability : non-portable 9 | -- 10 | -- Day 19. See "AOC.Solver" for the types used in this module! 11 | 12 | module AOC.Challenge.Day19 ( 13 | day19a 14 | , day19b 15 | ) where 16 | 17 | import AOC.Common (countTrue, pTok) 18 | import AOC.Solver ((:~>)(..)) 19 | import Control.Applicative (empty) 20 | import Control.DeepSeq (NFData) 21 | import Control.Monad ((>=>), guard) 22 | import Data.Bifunctor (first) 23 | import Data.Functor.Foldable (hylo) 24 | import Data.IntMap (IntMap) 25 | import Data.Void (Void) 26 | import GHC.Generics (Generic) 27 | import qualified Data.IntMap as IM 28 | import qualified Text.Megaparsec as P 29 | import qualified Text.Megaparsec.Char as P 30 | import qualified Text.Megaparsec.Char.Lexer as PP 31 | 32 | data Rule a = Simple Char 33 | | Compound [[a]] 34 | deriving (Show, Eq, Ord, Generic, Functor) 35 | 36 | instance NFData a => NFData (Rule a) 37 | 38 | matchRuleAlg :: Rule (String -> [String]) -> String -> [String] 39 | matchRuleAlg = \case 40 | Simple c -> \case 41 | [] -> empty 42 | d:ds -> ds <$ guard (c == d) 43 | Compound xs -> \str -> 44 | concatMap (\ys -> foldr (>=>) pure ys str) xs 45 | 46 | matcher :: IntMap (Rule Int) -> String -> [String] 47 | matcher rules = hylo matchRuleAlg (rules IM.!) 0 48 | 49 | solver :: IntMap (Rule Int) -> [String] -> Int 50 | solver rules = countTrue (any null . matcher rules) 51 | 52 | day19a :: (IntMap (Rule Int), [String]) :~> Int 53 | day19a = MkSol 54 | { sParse = P.parseMaybe inputParser 55 | , sShow = show 56 | , sSolve = Just . uncurry solver 57 | } 58 | 59 | day19b :: (IntMap (Rule Int), [String]) :~> Int 60 | day19b = MkSol 61 | { sParse = sParse day19a 62 | , sShow = show 63 | , sSolve = Just . uncurry solver . first (extraRules <>) 64 | } 65 | 66 | extraRules :: IntMap (Rule Int) 67 | extraRules = IM.fromList [ 68 | (8 , Compound [[42],[42,8]]) 69 | , (11, Compound [[42,31],[42,11,31]]) 70 | ] 71 | 72 | -- for fun: generate all matching strings 73 | _genAlg :: Rule [String] -> [String] 74 | _genAlg = \case 75 | Simple c -> [[c]] 76 | Compound xs -> concatMap (fmap concat . sequence) xs 77 | 78 | type Parser' = P.Parsec Void String 79 | 80 | ruleParser :: Parser' (Int, Rule Int) 81 | ruleParser = do 82 | i <- pTok $ PP.decimal <* ":" 83 | r <- P.choice 84 | [ P.try $ Simple <$> simpleParser 85 | , Compound <$> compoundParser 86 | ] 87 | pure (i, r) 88 | where 89 | simpleParser = P.between "\"" "\"" P.letterChar 90 | compoundParser = P.many (pTok PP.decimal) `P.sepBy` pTok "|" 91 | 92 | inputParser :: Parser' (IntMap (Rule Int), [String]) 93 | inputParser = do 94 | rs <- IM.fromList <$> P.many (ruleParser <* P.newline) 95 | P.newline 96 | ss <- P.many P.letterChar `P.sepBy` P.newline 97 | pure (rs, ss) 98 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day21.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | -- | 4 | -- Module : AOC.Challenge.Day21 5 | -- License : BSD3 6 | -- 7 | -- Stability : experimental 8 | -- Portability : non-portable 9 | -- 10 | -- Day 21. See "AOC.Solver" for the types used in this module! 11 | 12 | module AOC.Challenge.Day21 ( 13 | day21a 14 | , day21b 15 | ) where 16 | 17 | import AOC.Common (parseLines, pickUnique, countTrue) 18 | import AOC.Solver ((:~>)(..)) 19 | import Data.Foldable (toList) 20 | import Data.Functor ((<&>)) 21 | import Data.List (intercalate) 22 | import Data.Maybe (listToMaybe) 23 | import Data.Set (Set) 24 | import Data.Void (Void) 25 | import qualified Data.Map as M 26 | import qualified Data.Set as S 27 | import qualified Text.Megaparsec as P 28 | import qualified Text.Megaparsec.Char as P 29 | 30 | assembleOptions 31 | :: (Ord k, Ord a) 32 | => [(Set a, Set k)] 33 | -> [(k, Set a)] 34 | assembleOptions info = M.toList . M.unionsWith S.intersection $ 35 | info <&> \(igr, alg) -> M.fromSet (const igr) alg 36 | 37 | day21a :: [(Set String, Set String)] :~> Int 38 | day21a = MkSol 39 | { sParse = parseLines lineParser 40 | , sShow = show 41 | , sSolve = \igrsAlgs -> 42 | fmap (countNotIn (concatMap (toList . fst) igrsAlgs)) 43 | . listToMaybe 44 | . map (S.fromList . toList) 45 | . pickUnique 46 | $ assembleOptions igrsAlgs 47 | } 48 | where 49 | countNotIn xs bad = countTrue (`S.notMember` bad) xs 50 | 51 | day21b :: [(Set String, Set String)] :~> [String] 52 | day21b = MkSol 53 | { sParse = parseLines lineParser 54 | , sShow = intercalate "," 55 | , sSolve = fmap toList . listToMaybe . pickUnique . assembleOptions 56 | } 57 | 58 | type Parser = P.Parsec Void String 59 | 60 | lineParser :: Parser (Set String, Set String) 61 | lineParser = 62 | (,) <$> (S.fromList <$> P.many (P.some P.letterChar <* " ")) 63 | <*> (S.fromList <$> P.between "(" ")" 64 | ("contains " *> P.some P.letterChar `P.sepBy` ", ") 65 | ) 66 | 67 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day22.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | -- | 4 | -- Module : AOC.Challenge.Day22 5 | -- License : BSD3 6 | -- 7 | -- Stability : experimental 8 | -- Portability : non-portable 9 | -- 10 | -- Day 22. See "AOC.Solver" for the types used in this module! 11 | 12 | module AOC.Challenge.Day22 ( 13 | day22a 14 | , day22b 15 | ) where 16 | 17 | import AOC.Solver ((:~>)(..)) 18 | import Control.DeepSeq (NFData) 19 | import Control.Monad (guard) 20 | import Data.Foldable (toList) 21 | import Data.HashSet (HashSet) 22 | import Data.Hashable (Hashable(..)) 23 | import Data.Sequence (Seq(..)) 24 | import Data.Sequence.NonEmpty (NESeq(..)) 25 | import Data.Void (Void) 26 | import GHC.Generics (Generic) 27 | import qualified Data.HashSet as HS 28 | import qualified Data.Sequence as Seq 29 | import qualified Text.Megaparsec as P 30 | import qualified Text.Megaparsec.Char as P 31 | import qualified Text.Megaparsec.Char.Lexer as PP 32 | 33 | type Deck = Seq Int 34 | type NEDeck = NESeq Int 35 | 36 | data Player = P1 | P2 37 | deriving (Show, Eq, Ord, Generic) 38 | instance NFData Player 39 | 40 | data GameState = GS Deck Deck 41 | deriving Eq 42 | instance Hashable GameState where 43 | hashWithSalt s (GS xs ys) = 44 | hashWithSalt s 45 | ( take 2 (toList xs) 46 | , take 2 (toList ys) 47 | , Seq.length xs 48 | ) 49 | 50 | score :: Deck -> Int 51 | score = sum . zipWith (*) [1..] . reverse . toList 52 | 53 | playGameWith 54 | :: (NEDeck -> NEDeck -> Maybe Player) -- ^ recurse 55 | -> Deck 56 | -> Deck 57 | -> (Player, Deck) 58 | playGameWith f = go HS.empty 59 | where 60 | go :: HashSet GameState -> Deck -> Deck -> (Player, Deck) 61 | go !seen !xs0 !ys0 62 | | GS xs0 ys0 `HS.member` seen = (P1, xs0) 63 | | otherwise = case (xs0, ys0) of 64 | (x :<| xs, y :<| ys) -> 65 | let winner = case f (x :<|| xs) (y :<|| ys) of 66 | Nothing -> if x > y then P1 else P2 67 | Just p -> p 68 | in case winner of 69 | P1 -> go seen' (xs :|> x :|> y) ys 70 | P2 -> go seen' xs (ys :|> y :|> x) 71 | (Empty, _ ) -> (P2, ys0) 72 | (_ , Empty) -> (P1, xs0) 73 | where 74 | seen' = HS.insert (GS xs0 ys0) seen 75 | {-# INLINE playGameWith #-} 76 | 77 | game1 :: Deck -> Deck -> (Player, Deck) 78 | game1 = playGameWith $ \_ _ -> Nothing 79 | {-# INLINE game1 #-} 80 | 81 | game2 :: Deck -> Deck -> (Player, Deck) 82 | game2 = playGameWith $ \(x :<|| xs) (y :<|| ys) -> do 83 | xs' <- takeExactly x xs 84 | ys' <- takeExactly y ys 85 | let xMax = maximum xs' 86 | yMax = maximum ys' 87 | -- P1 has unbeatable card 88 | pure if xMax > yMax 89 | then P1 90 | else fst $ game2 xs' ys' 91 | {-# INLINE game2 #-} 92 | 93 | day22a :: (Deck, Deck) :~> Deck 94 | day22a = MkSol 95 | { sParse = P.parseMaybe gameParser 96 | , sShow = show . score 97 | , sSolve = Just . snd . uncurry game1 98 | } 99 | 100 | day22b :: (Deck, Deck) :~> Deck 101 | day22b = MkSol 102 | { sParse = P.parseMaybe gameParser 103 | , sShow = show . score 104 | , sSolve = Just . snd . uncurry game2 105 | } 106 | 107 | takeExactly :: Int -> Seq a -> Maybe (Seq a) 108 | takeExactly n xs = Seq.take n xs <$ guard (Seq.length xs >= n) 109 | {-# INLINE takeExactly #-} 110 | 111 | gameParser :: P.Parsec Void String (Deck, Deck) 112 | gameParser = (,) <$> deckParser <*> deckParser 113 | where 114 | deckParser = do 115 | _ :: Int <- "Player " *> PP.decimal <* ":" <* P.newline 116 | fmap Seq.fromList . P.many $ PP.decimal <* P.many P.newline 117 | 118 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day23.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day23 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 23. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day23 ( 11 | day23a 12 | , day23b 13 | ) where 14 | 15 | import AOC.Solver ((:~>)(..)) 16 | import Control.Monad (unless) 17 | import Control.Monad.Primitive (PrimMonad, PrimState) 18 | import Control.Monad.ST (runST) 19 | import Control.Monad.Trans.Class (lift) 20 | import Data.Char (digitToInt, intToDigit) 21 | import Data.Foldable (for_) 22 | import Data.Vector.Unboxed (Vector) 23 | import Data.Vector.Unboxed.Mutable (MVector) 24 | import qualified Data.Conduino as C 25 | import qualified Data.Conduino.Combinators as C 26 | import qualified Data.Vector.Unboxed as V 27 | import qualified Data.Vector.Unboxed.Mutable as MV 28 | 29 | newtype CrabState s = CrabState { csRight :: MVector s Int } 30 | 31 | sourceCrabState 32 | :: (PrimMonad m, PrimState m ~ s) 33 | => CrabState s 34 | -> Int -- ^ item to start from 35 | -> C.Pipe i Int u m () 36 | sourceCrabState CrabState{..} i0 = go i0 37 | where 38 | go i = do 39 | j <- lift $ MV.unsafeRead csRight i 40 | unless (j == i0) $ do 41 | C.yield j 42 | go j 43 | 44 | step 45 | :: forall m s. (PrimMonad m, PrimState m ~ s) 46 | => CrabState s 47 | -> Int 48 | -> m Int 49 | step CrabState{..} lab = do 50 | (gs@(g1,_,g3),lab') <- pull3 lab 51 | MV.unsafeWrite csRight lab lab' 52 | let target = until (notAny gs) subWrap (subWrap lab) 53 | aftertarg <- MV.unsafeRead csRight target 54 | MV.unsafeWrite csRight target g1 55 | MV.unsafeWrite csRight g3 aftertarg 56 | pure lab' 57 | where 58 | n = MV.length csRight 59 | subWrap x 60 | | x == 0 = n - 1 61 | | otherwise = x - 1 62 | notAny (g1,g2,g3) x = x /= g1 && x /= g2 && x /= g3 63 | {-# INLINE notAny #-} 64 | pull3 :: Int -> m ((Int, Int, Int), Int) 65 | pull3 i0 = do 66 | i1 <- MV.unsafeRead csRight i0 67 | i2 <- MV.unsafeRead csRight i1 68 | i3 <- MV.unsafeRead csRight i2 69 | i4 <- MV.unsafeRead csRight i3 70 | pure ((i1,i2,i3),i4) 71 | {-# INLINE pull3 #-} 72 | {-# INLINE step #-} 73 | 74 | initialize 75 | :: forall m s. (PrimMonad m, PrimState m ~ s) 76 | => Vector Int 77 | -> m (Int, CrabState s) -- ^ initial pointer 78 | initialize v0 = do 79 | csRight <- MV.unsafeNew n 80 | for_ [0 .. n-1] $ \i -> 81 | MV.unsafeWrite csRight (v0 V.! subWrap i) (v0 V.! i) 82 | let i0 = v0 V.! 0 83 | pure (i0, CrabState{..}) 84 | where 85 | n = V.length v0 86 | subWrap x 87 | | x == 0 = n - 1 88 | | otherwise = x - 1 89 | {-# INLINE initialize #-} 90 | 91 | run :: (PrimMonad m, PrimState m ~ s) 92 | => Int 93 | -> Int 94 | -> CrabState s 95 | -> m () 96 | run n i0 cs = go 0 i0 97 | where 98 | go !m !i 99 | | m == n = pure () 100 | | otherwise = go (m + 1) =<< step cs i 101 | {-# INLINE run #-} 102 | 103 | 104 | day23a :: Vector Int :~> [Int] 105 | day23a = MkSol 106 | { sParse = Just . V.fromList . map toIx 107 | , sShow = fmap intToDigit 108 | , sSolve = \v0 -> Just $ runST $ do 109 | (i0, cs) <- initialize v0 110 | run 100 i0 cs 111 | C.runPipe $ sourceCrabState cs 0 112 | C..| C.map fromIx 113 | C..| C.sinkList 114 | } 115 | 116 | day23b :: Vector Int :~> [Int] 117 | day23b = MkSol 118 | { sParse = Just . V.fromListN 1000000 . (++ [9 .. ]) . map toIx 119 | , sShow = show . product 120 | , sSolve = \v0 -> Just $ runST $ do 121 | (i0, cs) <- initialize v0 122 | run 10000000 i0 cs 123 | C.runPipe $ sourceCrabState cs 0 124 | C..| C.map fromIx 125 | C..| C.take 2 126 | C..| C.sinkList 127 | } 128 | 129 | toIx :: Char -> Int 130 | toIx = subtract 1 . digitToInt 131 | 132 | fromIx :: Int -> Int 133 | fromIx = (+ 1) 134 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day24.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day24 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 24. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day24 ( 11 | day24a 12 | , day24b 13 | ) where 14 | 15 | import AOC.Common ((!!!), foldMapParChunk) 16 | import AOC.Common.Point (Point) 17 | import AOC.Solver ((:~>)(..)) 18 | import Control.DeepSeq (NFData) 19 | import Data.Coerce (coerce) 20 | import Data.Map (Map) 21 | import Data.Semigroup (Sum(..)) 22 | import Data.Set (Set) 23 | import GHC.Generics (Generic) 24 | import Linear.V2 (V2(..)) 25 | import Math.Geometry.Grid.HexagonalInternal (HexDirection(..)) 26 | import qualified Data.Map.Monoidal.Strict as MM 27 | import qualified Data.Map.Strict as M 28 | import qualified Data.Set as S 29 | 30 | neighbors :: Point -> Set Point 31 | neighbors (V2 x y) = S.fromDistinctAscList 32 | [ V2 (x-1) y 33 | , V2 (x-1) (y+1) 34 | , V2 x (y-1) 35 | , V2 x (y+1) 36 | , V2 (x+1) (y-1) 37 | , V2 (x+1) y 38 | ] 39 | 40 | toDirs :: String -> Maybe [HexDirection] 41 | toDirs = \case 42 | [] -> Just [] 43 | 'w':ds -> (West:) <$> toDirs ds 44 | 'e':ds -> (East:) <$> toDirs ds 45 | 'n':'e':ds -> (Northeast:) <$> toDirs ds 46 | 'n':'w':ds -> (Northwest:) <$> toDirs ds 47 | 's':'e':ds -> (Southeast:) <$> toDirs ds 48 | 's':'w':ds -> (Southwest:) <$> toDirs ds 49 | _ -> Nothing 50 | 51 | hexOffset :: HexDirection -> Point 52 | hexOffset = \case 53 | West -> V2 (-1) 0 54 | Northwest -> V2 (-1) 1 55 | Northeast -> V2 0 1 56 | East -> V2 1 0 57 | Southeast -> V2 1 (-1) 58 | Southwest -> V2 0 (-1) 59 | 60 | newtype Xor = Xor { getXor :: Bool } 61 | deriving Generic 62 | instance NFData Xor 63 | instance Semigroup Xor where 64 | Xor x <> Xor y = Xor (x /= y) 65 | instance Monoid Xor where 66 | mempty = Xor False 67 | 68 | initialize :: [[HexDirection]] -> Set Point 69 | initialize = M.keysSet . M.filter getXor . coerce 70 | . foldMapParChunk 125 go 71 | where 72 | go = MM.MonoidalMap . (`M.singleton` Xor True) . sum . map hexOffset 73 | 74 | day24a :: [[HexDirection]] :~> Int 75 | day24a = MkSol 76 | { sParse = traverse toDirs . lines 77 | , sShow = show 78 | , sSolve = Just . S.size . initialize 79 | } 80 | 81 | day24b :: [[HexDirection]] :~> Int 82 | day24b = MkSol 83 | { sParse = traverse toDirs . lines 84 | , sShow = show 85 | , sSolve = Just . S.size . (!!! 100) . iterate step . initialize 86 | } 87 | 88 | step :: Set Point -> Set Point 89 | step ps = stayAlive <> comeAlive 90 | where 91 | neighborCounts :: Map Point Int 92 | neighborCounts = coerce $ foldMapParChunk 75 93 | (MM.MonoidalMap . M.fromSet (const (Sum (1 :: Int))) . neighbors) 94 | (S.toList ps) 95 | stayAlive = M.keysSet . M.filter (\n -> n == 1 || n == 2) $ 96 | neighborCounts `M.restrictKeys` ps 97 | comeAlive = M.keysSet . M.filter (== 2) $ 98 | neighborCounts `M.withoutKeys` ps 99 | -------------------------------------------------------------------------------- /src/AOC/Challenge/Day25.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Challenge.Day25 3 | -- License : BSD3 4 | -- 5 | -- Stability : experimental 6 | -- Portability : non-portable 7 | -- 8 | -- Day 25. See "AOC.Solver" for the types used in this module! 9 | 10 | module AOC.Challenge.Day25 ( 11 | day25a 12 | ) where 13 | 14 | import AOC.Common (_ListTup) 15 | import AOC.Solver ((:~>)(..)) 16 | import Control.Lens (preview) 17 | import Control.Monad ((<=<)) 18 | import Math.NumberTheory.Moduli (Mod, PrimitiveRoot, (^%), getVal, isMultElement, discreteLogarithm, isPrimitiveRoot) 19 | import Math.NumberTheory.Moduli.Singleton (CyclicGroup, cyclicGroup) 20 | import Numeric.Natural (Natural) 21 | import Text.Read (readMaybe) 22 | 23 | type Magic = 20201227 24 | 25 | magicGroup :: CyclicGroup Integer Magic 26 | Just magicGroup = cyclicGroup 27 | 28 | primBase :: PrimitiveRoot Magic 29 | Just primBase = isPrimitiveRoot magicGroup 7 30 | 31 | findSecret :: Mod Magic -> Maybe Natural 32 | findSecret = fmap (discreteLogarithm magicGroup primBase) 33 | . isMultElement 34 | 35 | day25a :: (Mod Magic, Mod Magic) :~> Integer 36 | day25a = MkSol 37 | { sParse = preview _ListTup 38 | <=< traverse (fmap fromInteger . readMaybe) 39 | . lines 40 | , sShow = show 41 | , sSolve = \(x, y) -> getVal . (y ^%) <$> findSecret x 42 | } 43 | -------------------------------------------------------------------------------- /src/AOC/Common/Numeric.hs: -------------------------------------------------------------------------------- 1 | 2 | module AOC.Common.Numeric ( 3 | PascalTable(..) 4 | , buildPascalTable 5 | , binom 6 | , pascals 7 | ) where 8 | 9 | import qualified Data.Vector.Unboxed as VU 10 | import Data.List (scanl') 11 | 12 | pascals :: [[Int]] 13 | pascals = repeat 1 : map (tail . scanl' (+) 0) pascals 14 | 15 | data PascalTable = PascalTable 16 | { ptWidth :: !Int 17 | , ptDepth :: !Int 18 | , ptTable :: !(VU.Vector Int) 19 | } 20 | deriving Show 21 | 22 | buildPascalTable 23 | :: Int -- ^ num diagonals 24 | -> Int -- ^ depth of diagonal 25 | -> PascalTable 26 | buildPascalTable n d = PascalTable 27 | { ptWidth = n 28 | , ptDepth = d 29 | , ptTable = VU.fromList . concat $ 30 | take d <$> take n pascals 31 | } 32 | 33 | binom 34 | :: PascalTable 35 | -> Int -- ^ diagonal 36 | -> Int -- ^ index 37 | -> Int 38 | binom PascalTable{..} i j = ptTable VU.! (i * ptDepth + j) 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/AOC/Prelude.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Prelude 3 | -- Copyright : (c) Justin Le 2018 4 | -- License : BSD3 5 | -- 6 | -- Maintainer : justin@jle.im 7 | -- Stability : experimental 8 | -- Portability : non-portable 9 | -- 10 | -- Custom Prelude while developing challenges. Ideally, once challenges 11 | -- are completed, an import to this module would be replaced with explicit 12 | -- ones for future readers. 13 | -- 14 | 15 | 16 | module AOC.Prelude ( 17 | module P 18 | ) where 19 | 20 | import AOC.Common as P 21 | import AOC.Common.Point as P 22 | import AOC.Solver as P 23 | import AOC.Util as P 24 | import Control.Applicative as P 25 | import Control.DeepSeq as P 26 | import Control.Lens as P hiding (uncons, noneOf) 27 | import Control.Monad as P 28 | import Control.Monad.Combinators as P hiding (endBy, option, many, some) 29 | import Control.Monad.Except as P 30 | import Control.Monad.State as P 31 | import Data.Bifunctor as P 32 | import Data.Char as P 33 | import Data.Coerce as P 34 | import Data.Containers.ListUtils as P 35 | import Data.Either as P 36 | import Data.Finite as P (Finite, packFinite, getFinite, modulo, finites) 37 | import Data.Foldable as P 38 | import Data.Function as P 39 | import Data.Functor as P 40 | import Data.IntMap as P (IntMap) 41 | import Data.IntMap.NonEmpty as P (NEIntMap) 42 | import Data.IntSet as P (IntSet) 43 | import Data.IntSet.NonEmpty as P (NEIntSet) 44 | import Data.Ix as P hiding (index) 45 | import Data.Kind as P 46 | import qualified Data.List as P 47 | import Data.List.NonEmpty as P (NonEmpty(..), nonEmpty) 48 | import Data.List.Split as P hiding (oneOf, sepBy) 49 | import Data.Map as P (Map) 50 | import Data.Map.NonEmpty as P (NEMap) 51 | import Data.Maybe as P 52 | import Data.Ord as P 53 | import Data.Semigroup as P 54 | import Data.Sequence as P (Seq) 55 | import Data.Set as P (Set) 56 | import Data.Set.NonEmpty as P (NESet) 57 | import Data.Text as P (Text) 58 | import Data.Text.Encoding as P (encodeUtf8, decodeUtf8) 59 | import Data.Time as P hiding (Day) 60 | import Data.Traversable as P 61 | import Data.Tuple as P 62 | import Data.Void as P 63 | import Debug.Trace as P 64 | import GHC.Generics as P (Generic) 65 | import Linear as P (V1(..), V2(..), V3(..), V4(..), R1(..), R2(..), R3(..), R4(..)) 66 | import Numeric.Natural as P 67 | import System.IO.Unsafe as P 68 | import Text.Megaparsec as P (try, eof, Parsec, oneOf, satisfy, anySingleBut, noneOf, anySingle) 69 | import Text.Megaparsec.Char as P (newline, space, space1, digitChar, binDigitChar, octDigitChar, hexDigitChar, asciiChar, char, string, string') 70 | import Text.Megaparsec.Char.Lexer as P (decimal, binary, octal, hexadecimal, scientific, float, signed) 71 | import Text.Printf as P 72 | import Text.Read as P (readMaybe) 73 | import Unsafe.Coerce as P 74 | 75 | import Data.Generics.Labels () 76 | -------------------------------------------------------------------------------- /src/AOC/Run/Config.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Run.Config 3 | -- Copyright : (c) Justin Le 2018 4 | -- License : BSD3 5 | -- 6 | -- Maintainer : justin@jle.im 7 | -- Stability : experimental 8 | -- Portability : non-portable 9 | -- 10 | -- Utilities for loading configuration file. 11 | -- 12 | 13 | 14 | module AOC.Run.Config ( 15 | Config(..), configFile, defConfPath 16 | , session 17 | ) where 18 | 19 | import Control.Exception 20 | import Control.Monad 21 | import Data.Default.Class 22 | import GHC.Generics (Generic) 23 | import System.IO.Error 24 | import Text.Printf 25 | import qualified Data.Aeson as A 26 | import qualified Data.ByteString as BS 27 | import qualified Data.Yaml as Y 28 | 29 | 30 | -- | Configuration for auto-runner. 31 | data Config = Cfg 32 | { _cfgSession :: Maybe String -- ^ Default: 'Nothing' 33 | , _cfgYear :: Integer -- ^ Default: 2015 34 | } 35 | deriving (Generic) 36 | 37 | -- | No session key, and 2015. 38 | instance Default Config where 39 | def = Cfg { _cfgSession = Nothing 40 | , _cfgYear = 2015 41 | } 42 | 43 | -- | Default math to find a configuration file. 44 | defConfPath :: FilePath 45 | defConfPath = "aoc-conf.yaml" 46 | 47 | -- | Load a 'Config' from a given filepath. 48 | configFile :: FilePath -> IO Config 49 | configFile fp = do 50 | cfgInp <- tryJust (guard . isDoesNotExistError) 51 | $ BS.readFile fp 52 | case cfgInp of 53 | Left () -> do 54 | Y.encodeFile @Config fp def 55 | return def 56 | Right b -> 57 | case Y.decodeEither' b of 58 | Left e -> do 59 | printf "Configuration file at %s could not be parsed:\n" fp 60 | print e 61 | return def 62 | Right cfg -> return cfg 63 | 64 | -- | Load a session token from the configuration file at a given filepath. 65 | session :: FilePath -> IO (Maybe String) 66 | session = fmap _cfgSession . configFile 67 | 68 | configJSON :: A.Options 69 | configJSON = A.defaultOptions 70 | { A.fieldLabelModifier = A.camelTo2 '-' . drop 4 } 71 | 72 | instance A.ToJSON Config where 73 | toJSON = A.genericToJSON configJSON 74 | toEncoding = A.genericToEncoding configJSON 75 | instance A.FromJSON Config where 76 | parseJSON = A.genericParseJSON configJSON 77 | -------------------------------------------------------------------------------- /src/AOC/Util.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : AOC.Util 3 | -- Copyright : (c) Justin Le 2018 4 | -- License : BSD3 5 | -- 6 | -- Maintainer : justin@jle.im 7 | -- Stability : experimental 8 | -- Portability : non-portable 9 | -- 10 | -- Assorted utility functions and orphans used for solutions. 11 | -- 12 | 13 | module AOC.Util ( 14 | strip 15 | , stripNewline 16 | , eitherToMaybe 17 | , firstJust 18 | , maybeToEither 19 | , maybeAlt 20 | , traceShowIdMsg 21 | , traceShowMsg 22 | ) where 23 | 24 | import Control.Applicative 25 | import Control.Monad.Except 26 | import Data.Foldable 27 | import Debug.Trace 28 | import qualified Data.Text as T 29 | 30 | -- | Strip trailing and leading whitespace. 31 | strip :: String -> String 32 | strip = T.unpack . T.strip . T.pack 33 | 34 | -- | Strip trailing newline 35 | stripNewline :: String -> String 36 | stripNewline = reverse . dropWhile (== '\n') . reverse 37 | 38 | -- | Convert an 'Either' into a 'Maybe', or any 'Alternative' instance, 39 | -- forgetting the error value. 40 | eitherToMaybe :: Alternative m => Either e a -> m a 41 | eitherToMaybe = either (const empty) pure 42 | 43 | -- | Convert a 'Maybe' into an 'Either', or any 'MonadError' instance, by 44 | -- providing an error value in case 'Nothing' was given. 45 | maybeToEither :: MonadError e m => e -> Maybe a -> m a 46 | maybeToEither e = maybe (throwError e) pure 47 | 48 | -- | Like 'find', but instead of taking an @a -> Bool@, takes an @a -> 49 | -- Maybe b@ and returns the first success. 50 | firstJust 51 | :: Foldable t 52 | => (a -> Maybe b) 53 | -> t a 54 | -> Maybe b 55 | firstJust p = asum . map p . toList 56 | 57 | -- | Generalize a 'Maybe' to any 'Alternative' 58 | maybeAlt :: Alternative m => Maybe a -> m a 59 | maybeAlt = maybe empty pure 60 | 61 | -- | Like 'traceShowId' but with an extra message 62 | traceShowIdMsg :: Show a => String -> a -> a 63 | traceShowIdMsg msg x = trace (msg ++ show x) x 64 | 65 | -- | Like 'traceShow' but with an extra message 66 | traceShowMsg :: Show a => String -> a -> b -> b 67 | traceShowMsg msg x = trace (msg ++ show x) 68 | -------------------------------------------------------------------------------- /src/AOC/Util/DynoMap.hs: -------------------------------------------------------------------------------- 1 | module AOC.Util.DynoMap ( 2 | DynoMap(..) 3 | , lookupDyno 4 | , lookupDynoWith 5 | ) where 6 | 7 | import Control.Monad 8 | import Data.Dynamic 9 | import Data.Map (Map) 10 | import Data.Maybe 11 | import qualified Data.Map as M 12 | 13 | newtype DynoMap = Dyno { runDyno :: Map String Dynamic } 14 | deriving newtype (Semigroup, Monoid) 15 | 16 | -- | Lookup the value at a given key in a 'Dyno'. 17 | -- 18 | -- > lookupDyno "hello" 19 | lookupDyno 20 | :: forall a. Typeable a 21 | => String 22 | -> DynoMap 23 | -> Maybe a 24 | lookupDyno sym = fromDynamic 25 | <=< M.lookup sym 26 | . runDyno 27 | 28 | -- | Like 'lookupDyno', but with a default value to be returned if the key 29 | -- is not found or has the wrong type. 30 | lookupDynoWith 31 | :: forall a. (Typeable a) 32 | => String 33 | -> a 34 | -> DynoMap 35 | -> a 36 | lookupDynoWith sym def = fromMaybe def . lookupDyno sym 37 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # https://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # 15 | # The location of a snapshot can be provided as a file or url. Stack assumes 16 | # a snapshot provided as a file might change, whereas a url resource does not. 17 | # 18 | # resolver: ./custom-snapshot.yaml 19 | # resolver: https://example.com/snapshots/2018-01-01.yaml 20 | resolver: nightly-2021-11-28 21 | 22 | # User packages to be built. 23 | # Various formats can be used as shown in the example below. 24 | # 25 | # packages: 26 | # - some-directory 27 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 28 | # - location: 29 | # git: https://github.com/commercialhaskell/stack.git 30 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 31 | # - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a 32 | # subdirs: 33 | # - auto-update 34 | # - wai 35 | packages: 36 | - . 37 | # Dependency packages to be pulled from upstream that are not in the resolver 38 | # using the same syntax as the packages field. 39 | # (e.g., acme-missiles-0.3) 40 | extra-deps: 41 | # - barbies-2.0.2.0 42 | # - conduino-0.2.2.0 43 | # - constraints-extras-0.3.0.2 44 | # - direct-sqlite-2.3.26 45 | # - list-transformer-1.0.6 46 | # - megaparsec-8.0.0 47 | # - parser-combinators-1.2.1 48 | # - refined-0.6.1 49 | # - sqlite-simple-0.4.18.0 50 | # - typelits-witnesses-0.4.0.0 51 | # - vinyl-0.13.0 52 | # - witherable-0.3.5 53 | - advent-of-code-api-0.2.8.1 54 | - dependent-sum-0.7.1.0 55 | - finitary-2.1.1.0 56 | - grid-7.8.15 57 | - haskell-names-0.9.9 58 | - monoidal-containers-0.6.0.1 59 | - mutable-0.2.2.0 60 | - newtype-0.2.2.0 61 | - nonempty-containers-0.3.4.4 62 | - one-liner-instances-0.1.3.0 63 | # - refined-0.6.2 64 | - strict-tuple-0.1.4 65 | - these-skinny-0.7.4 66 | - traverse-with-class-1.0.1.1 67 | - typelits-witnesses-0.4.0.0 68 | - github: nikita-volkov/refined 69 | commit: 240dd2b6fb5787cb6a079f4ab42674cb1ea7fd3b 70 | # - github: mstksg/vec 71 | # commit: d2dedab44fa49b4ed903fac6b9189c4d62835e1b 72 | # subdirs: 73 | # - vec 74 | # - github: mstksg/vector-sized 75 | # commit: f3757806e54afeaa243c87ffdcab881592470ddf 76 | 77 | allow-newer: true 78 | 79 | # Override default flag values for local packages and extra-deps 80 | # flags: {} 81 | 82 | # Extra package databases containing global packages 83 | # extra-package-dbs: [] 84 | 85 | # Control whether we use the GHC we find on the path 86 | # system-ghc: true 87 | # 88 | # Require a specific version of stack, using version ranges 89 | # require-stack-version: -any # Default 90 | # require-stack-version: ">=1.9" 91 | # 92 | # Override the architecture used by stack, especially useful on Windows 93 | # arch: i386 94 | # arch: x86_64 95 | # 96 | # Extra directories used by stack for building 97 | # extra-include-dirs: [/path/to/dir] 98 | # extra-lib-dirs: [/path/to/dir] 99 | # 100 | # Allow a newer minor version of GHC than the snapshot specifies 101 | # compiler-check: newer-minor 102 | -------------------------------------------------------------------------------- /template/DayXX.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-unused-imports #-} 2 | {-# OPTIONS_GHC -Wno-unused-top-binds #-} 3 | 4 | -- | 5 | -- Module : AOC.Challenge.Day${day} 6 | -- License : BSD3 7 | -- 8 | -- Stability : experimental 9 | -- Portability : non-portable 10 | -- 11 | -- Day ${day_short}. See "AOC.Solver" for the types used in this module! 12 | -- 13 | -- After completing the challenge, it is recommended to: 14 | -- 15 | -- * Replace "AOC.Prelude" imports to specific modules (with explicit 16 | -- imports) for readability. 17 | -- * Remove the @-Wno-unused-imports@ and @-Wno-unused-top-binds@ 18 | -- pragmas. 19 | -- * Replace the partial type signatures underscores in the solution 20 | -- types @_ :~> _@ with the actual types of inputs and outputs of the 21 | -- solution. You can delete the type signatures completely and GHC 22 | -- will recommend what should go in place of the underscores. 23 | 24 | module AOC.Challenge.Day${day} ( 25 | -- day${day}a 26 | -- , day${day}b 27 | ) where 28 | 29 | import AOC.Prelude 30 | 31 | import qualified Data.Graph.Inductive as G 32 | import qualified Data.IntMap as IM 33 | import qualified Data.IntSet as IS 34 | import qualified Data.List.NonEmpty as NE 35 | import qualified Data.List.PointedList as PL 36 | import qualified Data.List.PointedList.Circular as PLC 37 | import qualified Data.Map as M 38 | import qualified Data.OrdPSQ as PSQ 39 | import qualified Data.Sequence as Seq 40 | import qualified Data.Set as S 41 | import qualified Data.Text as T 42 | import qualified Data.Vector as V 43 | import qualified Linear as L 44 | import qualified Text.Megaparsec as P 45 | import qualified Text.Megaparsec.Char as P 46 | import qualified Text.Megaparsec.Char.Lexer as PP 47 | 48 | day${day}a :: _ :~> _ 49 | day${day}a = MkSol 50 | { sParse = Just . lines 51 | , sShow = show 52 | , sSolve = Just 53 | } 54 | 55 | day${day}b :: _ :~> _ 56 | day${day}b = MkSol 57 | { sParse = sParse day${day}a 58 | , sShow = show 59 | , sSolve = Just 60 | } 61 | -------------------------------------------------------------------------------- /template/feed-item.xml.template: -------------------------------------------------------------------------------- 1 | 2 | Day ${day} 3 | ${body} 4 | https://github.com/${github}/advent-of-code-${year}/blob/master/reflections.md#day-${day} 5 | ${time} 6 | 7 | -------------------------------------------------------------------------------- /template/feed.xml.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${name}'s Advent of Code ${year} Reflections 6 | Reflections for my Advent of Code solutions as I try to solve them all in fun ways using Haskell! 7 | https://github.com/${github}/advent-of-code-${year}/blob/master/reflections.md 8 | Copyright ${year} ${name} 9 | en-us 10 | ${time} 11 | ${email} 12 | ${time} 13 | ${email} 14 | Shake + Template 15 | ${body} 16 | 17 | 18 | -------------------------------------------------------------------------------- /template/reflection.md.template: -------------------------------------------------------------------------------- 1 | Day ${dayshort} 2 | ------ 3 | 4 | 9 | 10 | *[Prompt][d${daylong}p]* / *[Code][d${daylong}g]* / *[Rendered][d${daylong}h]* / *[Standalone Reflection Page][d${daylong}r]* 11 | 12 | [d${daylong}p]: https://adventofcode.com/${year}/day/${dayshort} 13 | [d${daylong}g]: https://github.com/${github}/advent-of-code-${year}/blob/master/src/AOC/Challenge/Day${daylong}.hs 14 | [d${daylong}h]: https://${github}.github.io/advent-of-code-${year}/src/AOC.Challenge.Day${daylong}.html 15 | [d${daylong}r]: https://github.com/${github}/advent-of-code-${year}/blob/master/reflections-out/day${daylong}.md 16 | 17 | ${body} 18 | 19 | ### Day ${dayshort} Benchmarks 20 | 21 | ``` 22 | ${benchmarks} 23 | ``` 24 | -------------------------------------------------------------------------------- /template/reflections.md.template: -------------------------------------------------------------------------------- 1 | Reflections 2 | =========== 3 | 4 | 8 | 9 | ${other_years} 10 | 11 | ${other_links} 12 | 13 | [Available as an RSS Feed][rss] 14 | 15 | [rss]: ${rss} 16 | 17 | Table of Contents 18 | ----------------- 19 | 20 | ${toc} 21 | 22 | ${body} 23 | -------------------------------------------------------------------------------- /template/standalone-reflection.md.template: -------------------------------------------------------------------------------- 1 | Day ${dayshort} 2 | === 3 | 4 | 9 | 10 | *[all][reflections]* / ${other_days} 11 | 12 | [reflections]: https://github.com/${github}/advent-of-code-${year}/blob/master/reflections.md 13 | ${other_links} 14 | 15 | [Available as an RSS Feed][rss] 16 | 17 | [rss]: ${rss} 18 | 19 | *[Prompt][d${daylong}p]* / *[Code][d${daylong}g]* / *[Rendered][d${daylong}h]* 20 | 21 | [d${daylong}p]: https://adventofcode.com/${year}/day/${dayshort} 22 | [d${daylong}g]: https://github.com/${github}/advent-of-code-${year}/blob/master/src/AOC/Challenge/Day${daylong}.hs 23 | [d${daylong}h]: https://${github}.github.io/advent-of-code-${year}/src/AOC.Challenge.Day${daylong}.html 24 | 25 | ${body} 26 | 27 | *[Back to all reflections for ${year}][reflections]* 28 | 29 | ## Day ${dayshort} Benchmarks 30 | 31 | ``` 32 | ${benchmarks} 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /test-data/2016/01a.txt: -------------------------------------------------------------------------------- 1 | R2, L3 2 | >>> 5 3 | R2, R2, R2 4 | >>> 2 5 | R5, L5, R5, R3 6 | >>> 12 7 | -------------------------------------------------------------------------------- /test-data/2016/01b.txt: -------------------------------------------------------------------------------- 1 | R8, R4, R4, R8 2 | >>> 4 3 | -------------------------------------------------------------------------------- /test-data/2016/02a.txt: -------------------------------------------------------------------------------- 1 | ULL 2 | RRDDD 3 | LURDL 4 | UUUUD 5 | >>> 1985 6 | -------------------------------------------------------------------------------- /test-data/2016/02b.txt: -------------------------------------------------------------------------------- 1 | ULL 2 | RRDDD 3 | LURDL 4 | UUUUD 5 | >>> 5DB3 6 | -------------------------------------------------------------------------------- /test-data/2016/03a.txt: -------------------------------------------------------------------------------- 1 | 5 10 25 2 | >>> 0 3 | -------------------------------------------------------------------------------- /test-data/2016/04a.txt: -------------------------------------------------------------------------------- 1 | aaaaa-bbb-z-y-x-123[abxyz] 2 | a-b-c-d-e-f-g-h-987[abcde] 3 | not-a-real-room-404[oarel] 4 | totally-real-room-200[decoy] 5 | >>> 1514 6 | -------------------------------------------------------------------------------- /test-data/2016/05a.txt: -------------------------------------------------------------------------------- 1 | abc 2 | >>> 18f47a30 3 | -------------------------------------------------------------------------------- /test-data/2016/05b.txt: -------------------------------------------------------------------------------- 1 | abc 2 | >>> 05ace8e3 3 | -------------------------------------------------------------------------------- /test-data/2016/06a.txt: -------------------------------------------------------------------------------- 1 | eedadn 2 | drvtee 3 | eandsr 4 | raavrd 5 | atevrs 6 | tsrnev 7 | sdttsa 8 | rasrtv 9 | nssdts 10 | ntnada 11 | svetve 12 | tesnvt 13 | vntsnd 14 | vrdear 15 | dvrsen 16 | enarar 17 | >>> easter 18 | -------------------------------------------------------------------------------- /test-data/2016/06b.txt: -------------------------------------------------------------------------------- 1 | eedadn 2 | drvtee 3 | eandsr 4 | raavrd 5 | atevrs 6 | tsrnev 7 | sdttsa 8 | rasrtv 9 | nssdts 10 | ntnada 11 | svetve 12 | tesnvt 13 | vntsnd 14 | vrdear 15 | dvrsen 16 | enarar 17 | >>> advent 18 | -------------------------------------------------------------------------------- /test-data/2016/07a.txt: -------------------------------------------------------------------------------- 1 | abba[mnop]qrst 2 | >>> 1 3 | abcd[bddb]xyyx 4 | >>> 0 5 | aaaa[qwer]tyui 6 | >>> 0 7 | ioxxoj[asdfgh]zxcvbn 8 | >>> 1 9 | abba[mnop]qrst 10 | abcd[bddb]xyyx 11 | aaaa[qwer]tyui 12 | ioxxoj[asdfgh]zxcvbn 13 | >>> 2 14 | -------------------------------------------------------------------------------- /test-data/2016/07b.txt: -------------------------------------------------------------------------------- 1 | aba[bab]xyz 2 | >>> 1 3 | xyx[xyx]xyx 4 | >>> 0 5 | aaa[kek]eke 6 | >>> 1 7 | zazbz[bzb]cdb 8 | >>> 1 9 | aba[bab]xyz 10 | xyx[xyx]xyx 11 | aaa[kek]eke 12 | zazbz[bzb]cdb 13 | >>> 3 14 | -------------------------------------------------------------------------------- /test-data/2016/08a.txt: -------------------------------------------------------------------------------- 1 | rect 3x2 2 | rotate column x=1 by 1 3 | rotate row y=0 by 4 4 | rotate column x=1 by 1 5 | >>> 6 6 | -------------------------------------------------------------------------------- /test-data/2016/09a.txt: -------------------------------------------------------------------------------- 1 | ADVENT 2 | >>> 6 3 | A(1x5)BC 4 | >>> 7 5 | (3x3)XYZ 6 | >>> 9 7 | A(2x2)BCD(2x2)EFG 8 | >>> 11 9 | (6x1)(1x3)A 10 | >>> 6 11 | X(8x2)(3x3)ABCY 12 | >>> 18 13 | -------------------------------------------------------------------------------- /test-data/2016/09b.txt: -------------------------------------------------------------------------------- 1 | (3x3)XYZ 2 | >>> 9 3 | X(8x2)(3x3)ABCY 4 | XABCABCABCABCABCABCY 5 | >>> 20 6 | (27x12)(20x12)(13x14)(7x10)(1x12)A 7 | >>> 241920 8 | (25x3)(3x3)ABC(2x3)XY(5x2)PQRSTX(18x9)(3x2)TWO(5x7)SEVEN 9 | >>> 445 10 | -------------------------------------------------------------------------------- /test-data/2017/01a.txt: -------------------------------------------------------------------------------- 1 | 1122 2 | >>> 3 3 | 1111 4 | >>> 4 5 | 1234 6 | >>> 0 7 | 91212129 8 | >>> 9 9 | -------------------------------------------------------------------------------- /test-data/2017/01b.txt: -------------------------------------------------------------------------------- 1 | 1212 2 | >>> 6 3 | 1221 4 | >>> 0 5 | 123425 6 | >>> 4 7 | 123123 8 | >>> 12 9 | 12131415 10 | >>> 4 11 | -------------------------------------------------------------------------------- /test-data/2017/02a.txt: -------------------------------------------------------------------------------- 1 | 5 1 9 5 2 | 7 5 3 3 | 2 4 6 8 4 | >>> 18 5 | -------------------------------------------------------------------------------- /test-data/2017/02b.txt: -------------------------------------------------------------------------------- 1 | 5 9 2 8 2 | 9 4 7 3 3 | 3 8 6 5 4 | >>> 9 5 | -------------------------------------------------------------------------------- /test-data/2017/03a.txt: -------------------------------------------------------------------------------- 1 | 1 2 | >>> 0 3 | 12 4 | >>> 3 5 | 23 6 | >>> 2 7 | 1024 8 | >>> 31 9 | -------------------------------------------------------------------------------- /test-data/2017/03b.txt: -------------------------------------------------------------------------------- 1 | 12 2 | >>> 23 3 | 60 4 | >>> 122 5 | 748 6 | >>> 806 7 | -------------------------------------------------------------------------------- /test-data/2017/04a.txt: -------------------------------------------------------------------------------- 1 | aa bb cc dd ee 2 | >>> 1 3 | aa bb cc dd aa 4 | >>> 0 5 | aa bb cc dd aaa 6 | >>> 1 7 | aa bb cc dd ee 8 | aa bb cc dd aa 9 | aa bb cc dd aaa 10 | >>> 2 11 | -------------------------------------------------------------------------------- /test-data/2017/04b.txt: -------------------------------------------------------------------------------- 1 | abcde fghij 2 | >>> 1 3 | abcde xyz ecdab 4 | >>> 0 5 | a ab abc abd abf abj 6 | >>> 1 7 | iiii oiii ooii oooi oooo 8 | >>> 1 9 | oiii ioii iioi iiio 10 | >>> 0 11 | abcde fghij 12 | abcde xyz ecdab 13 | a ab abc abd abf abj 14 | iiii oiii ooii oooi oooo 15 | oiii ioii iioi iiio 16 | >>> 3 17 | -------------------------------------------------------------------------------- /test-data/2017/05a.txt: -------------------------------------------------------------------------------- 1 | 0 2 | 3 3 | 0 4 | 1 5 | -3 6 | >>> 5 7 | -------------------------------------------------------------------------------- /test-data/2017/05b.txt: -------------------------------------------------------------------------------- 1 | 0 2 | 3 3 | 0 4 | 1 5 | -3 6 | >>> 10 7 | -------------------------------------------------------------------------------- /test-data/2017/06a.txt: -------------------------------------------------------------------------------- 1 | 0 2 7 0 2 | >>> 5 3 | -------------------------------------------------------------------------------- /test-data/2017/06b.txt: -------------------------------------------------------------------------------- 1 | 0 2 7 0 2 | >>> 4 3 | -------------------------------------------------------------------------------- /test-data/2017/07a.txt: -------------------------------------------------------------------------------- 1 | pbga (66) 2 | xhth (57) 3 | ebii (61) 4 | havc (66) 5 | ktlj (57) 6 | fwft (72) -> ktlj, cntj, xhth 7 | qoyq (66) 8 | padx (45) -> pbga, havc, qoyq 9 | tknk (41) -> ugml, padx, fwft 10 | jptl (61) 11 | ugml (68) -> gyxo, ebii, jptl 12 | gyxo (61) 13 | cntj (57) 14 | >>> tknk 15 | -------------------------------------------------------------------------------- /test-data/2017/07b.txt: -------------------------------------------------------------------------------- 1 | pbga (66) 2 | xhth (57) 3 | ebii (61) 4 | havc (66) 5 | ktlj (57) 6 | fwft (72) -> ktlj, cntj, xhth 7 | qoyq (66) 8 | padx (45) -> pbga, havc, qoyq 9 | tknk (41) -> ugml, padx, fwft 10 | jptl (61) 11 | ugml (68) -> gyxo, ebii, jptl 12 | gyxo (61) 13 | cntj (57) 14 | >>> 60 15 | -------------------------------------------------------------------------------- /test-data/2017/08a.txt: -------------------------------------------------------------------------------- 1 | b inc 5 if a > 1 2 | a inc 1 if b < 5 3 | c dec -10 if a >= 1 4 | c inc -20 if c == 10 5 | >>> 1 6 | -------------------------------------------------------------------------------- /test-data/2017/08b.txt: -------------------------------------------------------------------------------- 1 | b inc 5 if a > 1 2 | a inc 1 if b < 5 3 | c dec -10 if a >= 1 4 | c inc -20 if c == 10 5 | >>> 10 6 | -------------------------------------------------------------------------------- /test-data/2017/09a.txt: -------------------------------------------------------------------------------- 1 | {} 2 | >>> 1 3 | {{{}}} 4 | >>> 6 5 | {{},{}} 6 | >>> 5 7 | {{{},{},{{}}}} 8 | >>> 16 9 | {,,,} 10 | >>> 1 11 | {{},{},{},{}} 12 | >>> 9 13 | {{},{},{},{}} 14 | >>> 9 15 | {{},{},{},{}} 16 | >>> 3 17 | -------------------------------------------------------------------------------- /test-data/2017/09b.txt: -------------------------------------------------------------------------------- 1 | <> 2 | >>> 0 3 | 4 | >>> 17 5 | <<<<> 6 | >>> 3 7 | <{!>}> 8 | >>> 2 9 | 10 | >>> 0 11 | > 12 | >>> 0 13 | <{o"i!a,<{i 14 | >>> 10 15 | -------------------------------------------------------------------------------- /test-data/2017/10b.txt: -------------------------------------------------------------------------------- 1 | 2 | >>> a2582a3a0e66e6e86e3812dcb672a272 3 | AoC 2017 4 | >>> 33efeb34ea91902bb2f59c9920caa6cd 5 | 1,2,3 6 | >>> 3efbe78a8d82f29979031a4aa0b16a9d 7 | 1,2,4 8 | >>> 63960835bcdc130f0b66d7ff4f6a5a8e 9 | -------------------------------------------------------------------------------- /test-data/2017/11a.txt: -------------------------------------------------------------------------------- 1 | ne,ne,ne 2 | >>> 3 3 | ne,ne,sw,sw 4 | >>> 0 5 | ne,ne,s,s 6 | >>> 2 7 | se,sw,se,sw,sw 8 | >>> 3 9 | -------------------------------------------------------------------------------- /test-data/2017/11b.txt: -------------------------------------------------------------------------------- 1 | ne,ne,ne 2 | >>> 3 3 | ne,ne,sw,sw 4 | >>> 2 5 | ne,ne,s,s 6 | >>> 2 7 | se,sw,se,sw,sw 8 | >>> 3 9 | -------------------------------------------------------------------------------- /test-data/2017/13a.txt: -------------------------------------------------------------------------------- 1 | 0: 3 2 | 1: 2 3 | 4: 4 4 | 6: 4 5 | >>> 24 6 | -------------------------------------------------------------------------------- /test-data/2017/13b.txt: -------------------------------------------------------------------------------- 1 | 0: 3 2 | 1: 2 3 | 4: 4 4 | 6: 4 5 | >>> 10 6 | -------------------------------------------------------------------------------- /test-data/2017/14a.txt: -------------------------------------------------------------------------------- 1 | flqrgnkx 2 | >>> 8108 3 | -------------------------------------------------------------------------------- /test-data/2017/14b.txt: -------------------------------------------------------------------------------- 1 | flqrgnkx 2 | >>> 1242 3 | -------------------------------------------------------------------------------- /test-data/2017/17a.txt: -------------------------------------------------------------------------------- 1 | 3 2 | >>> 638 3 | -------------------------------------------------------------------------------- /test-data/2017/18b.txt: -------------------------------------------------------------------------------- 1 | snd 1 2 | snd 2 3 | snd p 4 | rcv a 5 | rcv b 6 | rcv c 7 | rcv d 8 | >>> 3 9 | -------------------------------------------------------------------------------- /test-data/2017/19a.txt: -------------------------------------------------------------------------------- 1 | | 2 | | +--+ 3 | A | C 4 | F---|----E|--+ 5 | | | | D 6 | +B-+ +--+ 7 | >>> ABCDEF 8 | -------------------------------------------------------------------------------- /test-data/2017/19b.txt: -------------------------------------------------------------------------------- 1 | | 2 | | +--+ 3 | A | C 4 | F---|----E|--+ 5 | | | | D 6 | +B-+ +--+ 7 | >>> 38 8 | -------------------------------------------------------------------------------- /test-data/2017/20a.txt: -------------------------------------------------------------------------------- 1 | p=< 3,0,0>, v=< 2,0,0>, a=<-1,0,0> 2 | p=< 4,0,0>, v=< 0,0,0>, a=<-2,0,0> 3 | >>> 0 4 | -------------------------------------------------------------------------------- /test-data/2017/20b.txt: -------------------------------------------------------------------------------- 1 | p=<-6,0,0>, v=< 3,0,0>, a=< 0,0,0> 2 | p=<-4,0,0>, v=< 2,0,0>, a=< 0,0,0> 3 | p=<-2,0,0>, v=< 1,0,0>, a=< 0,0,0> 4 | p=< 3,0,0>, v=<-1,0,0>, a=< 0,0,0> 5 | >>> 1 6 | -------------------------------------------------------------------------------- /test-data/2018/01a.txt: -------------------------------------------------------------------------------- 1 | +1 2 | -2 3 | +3 4 | +1 5 | >>> 3 6 | +1 7 | +1 8 | +1 9 | >>> 3 10 | +1 11 | +1 12 | -2 13 | >>> 0 14 | -1 15 | -2 16 | -3 17 | >>> -6 18 | -------------------------------------------------------------------------------- /test-data/2018/01b.txt: -------------------------------------------------------------------------------- 1 | +1 2 | -2 3 | +3 4 | +1 5 | >>> 2 6 | +1 7 | -1 8 | >>> 0 9 | +3 10 | +3 11 | +4 12 | -2 13 | -4 14 | >>> 10 15 | -6 16 | +3 17 | +8 18 | +5 19 | -6 20 | >>> 5 21 | +7 22 | +7 23 | -2 24 | -7 25 | -4 26 | >>> 14 27 | -------------------------------------------------------------------------------- /test-data/2018/02a.txt: -------------------------------------------------------------------------------- 1 | abcdef 2 | bababc 3 | abbcde 4 | abcccd 5 | aabcdd 6 | abcdee 7 | ababab 8 | >>> 12 9 | -------------------------------------------------------------------------------- /test-data/2018/02b.txt: -------------------------------------------------------------------------------- 1 | abcde 2 | fghij 3 | klmno 4 | pqrst 5 | fguij 6 | axcye 7 | wvxyz 8 | >>> fgij 9 | -------------------------------------------------------------------------------- /test-data/2018/03a.txt: -------------------------------------------------------------------------------- 1 | #1 @ 1,3: 4x4 2 | #2 @ 3,1: 4x4 3 | #3 @ 5,5: 2x2 4 | >>> 4 5 | -------------------------------------------------------------------------------- /test-data/2018/03b.txt: -------------------------------------------------------------------------------- 1 | #1 @ 1,3: 4x4 2 | #2 @ 3,1: 4x4 3 | #3 @ 5,5: 2x2 4 | >>> 3 5 | -------------------------------------------------------------------------------- /test-data/2018/04a.txt: -------------------------------------------------------------------------------- 1 | [1518-11-01 00:00] Guard #10 begins shift 2 | [1518-11-01 00:05] falls asleep 3 | [1518-11-01 00:25] wakes up 4 | [1518-11-01 00:30] falls asleep 5 | [1518-11-01 00:55] wakes up 6 | [1518-11-01 23:58] Guard #99 begins shift 7 | [1518-11-02 00:40] falls asleep 8 | [1518-11-02 00:50] wakes up 9 | [1518-11-03 00:05] Guard #10 begins shift 10 | [1518-11-03 00:24] falls asleep 11 | [1518-11-03 00:29] wakes up 12 | [1518-11-04 00:02] Guard #99 begins shift 13 | [1518-11-04 00:36] falls asleep 14 | [1518-11-04 00:46] wakes up 15 | [1518-11-05 00:03] Guard #99 begins shift 16 | [1518-11-05 00:45] falls asleep 17 | [1518-11-05 00:55] wakes up 18 | >>> 240 19 | -------------------------------------------------------------------------------- /test-data/2018/04b.txt: -------------------------------------------------------------------------------- 1 | [1518-11-01 00:00] Guard #10 begins shift 2 | [1518-11-01 00:05] falls asleep 3 | [1518-11-01 00:25] wakes up 4 | [1518-11-01 00:30] falls asleep 5 | [1518-11-01 00:55] wakes up 6 | [1518-11-01 23:58] Guard #99 begins shift 7 | [1518-11-02 00:40] falls asleep 8 | [1518-11-02 00:50] wakes up 9 | [1518-11-03 00:05] Guard #10 begins shift 10 | [1518-11-03 00:24] falls asleep 11 | [1518-11-03 00:29] wakes up 12 | [1518-11-04 00:02] Guard #99 begins shift 13 | [1518-11-04 00:36] falls asleep 14 | [1518-11-04 00:46] wakes up 15 | [1518-11-05 00:03] Guard #99 begins shift 16 | [1518-11-05 00:45] falls asleep 17 | [1518-11-05 00:55] wakes up 18 | >>> 4455 19 | -------------------------------------------------------------------------------- /test-data/2018/05a.txt: -------------------------------------------------------------------------------- 1 | aA 2 | >>> 0 3 | abBA 4 | >>> 0 5 | abAB 6 | >>> 4 7 | aabAAB 8 | >>> 6 9 | dabAcCaCBAcCcaDA 10 | >>> 10 11 | -------------------------------------------------------------------------------- /test-data/2018/05b.txt: -------------------------------------------------------------------------------- 1 | dabAcCaCBAcCcaDA 2 | >>> 4 3 | -------------------------------------------------------------------------------- /test-data/2018/06a.txt: -------------------------------------------------------------------------------- 1 | 1, 1 2 | 1, 6 3 | 8, 3 4 | 3, 4 5 | 5, 5 6 | 8, 9 7 | >>> 17 8 | -------------------------------------------------------------------------------- /test-data/2018/06b.txt: -------------------------------------------------------------------------------- 1 | 1, 1 2 | 1, 6 3 | 8, 3 4 | 3, 4 5 | 5, 5 6 | 8, 9 7 | >>>lim:32:int 8 | >>> 16 9 | -------------------------------------------------------------------------------- /test-data/2018/07a.txt: -------------------------------------------------------------------------------- 1 | Step C must be finished before step A can begin. 2 | Step C must be finished before step F can begin. 3 | Step A must be finished before step B can begin. 4 | Step A must be finished before step D can begin. 5 | Step B must be finished before step E can begin. 6 | Step D must be finished before step E can begin. 7 | Step F must be finished before step E can begin. 8 | >>> CABDFE 9 | -------------------------------------------------------------------------------- /test-data/2018/07b.txt: -------------------------------------------------------------------------------- 1 | Step C must be finished before step A can begin. 2 | Step C must be finished before step F can begin. 3 | Step A must be finished before step B can begin. 4 | Step A must be finished before step D can begin. 5 | Step B must be finished before step E can begin. 6 | Step D must be finished before step E can begin. 7 | Step F must be finished before step E can begin. 8 | >>>cap:2:int 9 | >>>wait:0:int 10 | >>> 15 11 | -------------------------------------------------------------------------------- /test-data/2018/08a.txt: -------------------------------------------------------------------------------- 1 | 2 3 0 3 10 11 12 1 1 0 1 99 2 1 1 2 2 | >>> 138 3 | -------------------------------------------------------------------------------- /test-data/2018/08b.txt: -------------------------------------------------------------------------------- 1 | 2 3 0 3 10 11 12 1 1 0 1 99 2 1 1 2 2 | >>> 66 3 | -------------------------------------------------------------------------------- /test-data/2018/09a.txt: -------------------------------------------------------------------------------- 1 | 9 25 2 | >>> 32 3 | 10 1618 4 | >>> 8317 5 | 13 players; last marble is worth 7999 points 6 | >>> 146373 7 | 17 1104 8 | >>> 2764 9 | 21 6111 10 | >>> 54718 11 | 30 5807 12 | >>> 37305 13 | -------------------------------------------------------------------------------- /test-data/2018/10b.txt: -------------------------------------------------------------------------------- 1 | position=< 9, 1> velocity=< 0, 2> 2 | position=< 7, 0> velocity=<-1, 0> 3 | position=< 3, -2> velocity=<-1, 1> 4 | position=< 6, 10> velocity=<-2, -1> 5 | position=< 2, -4> velocity=< 2, 2> 6 | position=<-6, 10> velocity=< 2, -2> 7 | position=< 1, 8> velocity=< 1, -1> 8 | position=< 1, 7> velocity=< 1, 0> 9 | position=<-3, 11> velocity=< 1, -2> 10 | position=< 7, 6> velocity=<-1, -1> 11 | position=<-2, 3> velocity=< 1, 0> 12 | position=<-4, 3> velocity=< 2, 0> 13 | position=<10, -3> velocity=<-1, 1> 14 | position=< 5, 11> velocity=< 1, -2> 15 | position=< 4, 7> velocity=< 0, -1> 16 | position=< 8, -2> velocity=< 0, 1> 17 | position=<15, 0> velocity=<-2, 0> 18 | position=< 1, 6> velocity=< 1, 0> 19 | position=< 8, 9> velocity=< 0, -1> 20 | position=< 3, 3> velocity=<-1, 1> 21 | position=< 0, 5> velocity=< 0, -1> 22 | position=<-2, 2> velocity=< 2, 0> 23 | position=< 5, -2> velocity=< 1, 2> 24 | position=< 1, 4> velocity=< 2, 1> 25 | position=<-2, 7> velocity=< 2, -2> 26 | position=< 3, 6> velocity=<-1, -1> 27 | position=< 5, 0> velocity=< 1, 0> 28 | position=<-6, 0> velocity=< 2, 0> 29 | position=< 5, 9> velocity=< 1, -2> 30 | position=<14, 7> velocity=<-2, 0> 31 | position=<-3, 6> velocity=< 2, -1> 32 | >>> 3 33 | -------------------------------------------------------------------------------- /test-data/2018/11a.txt: -------------------------------------------------------------------------------- 1 | 18 2 | >>> 33,45 3 | 42 4 | >>> 21,61 5 | -------------------------------------------------------------------------------- /test-data/2018/11b.txt: -------------------------------------------------------------------------------- 1 | 18 2 | >>> 90,269,16 3 | 42 4 | >>> 232,251,12 5 | -------------------------------------------------------------------------------- /test-data/2018/14a.txt: -------------------------------------------------------------------------------- 1 | 9 2 | >>> 5158916779 3 | 5 4 | >>> 0124515891 5 | 18 6 | >>> 9251071085 7 | 2018 8 | >>> 5941429882 9 | -------------------------------------------------------------------------------- /test-data/2018/14b.txt: -------------------------------------------------------------------------------- 1 | 51589 2 | >>> 9 3 | 01245 4 | >>> 5 5 | 92510 6 | >>> 18 7 | 59414 8 | >>> 2018 9 | 5891 10 | >>> 11 11 | -------------------------------------------------------------------------------- /test-data/2018/15a.txt: -------------------------------------------------------------------------------- 1 | ######### 2 | #G..G..G# 3 | #.......# 4 | #.......# 5 | #G..E..G# 6 | #.......# 7 | #.......# 8 | #G..G..G# 9 | ######### 10 | >>> 27828 11 | ####### 12 | #.G...# 13 | #...EG# 14 | #.#.#G# 15 | #..G#E# 16 | #.....# 17 | ####### 18 | >>> 27730 19 | ####### 20 | #G..#E# 21 | #E#E.E# 22 | #G.##.# 23 | #...#E# 24 | #...E.# 25 | ####### 26 | >>> 36334 27 | ####### 28 | #E..EG# 29 | #.#G.E# 30 | #E.##E# 31 | #G..#.# 32 | #..E#.# 33 | ####### 34 | >>> 39514 35 | ####### 36 | #E.G#.# 37 | #.#G..# 38 | #G.#.G# 39 | #G..#.# 40 | #...E.# 41 | ####### 42 | >>> 27755 43 | ####### 44 | #.E...# 45 | #.#..G# 46 | #.###.# 47 | #E#G#G# 48 | #...#G# 49 | ####### 50 | >>> 28944 51 | ######### 52 | #G......# 53 | #.E.#...# 54 | #..##..G# 55 | #...##..# 56 | #...#...# 57 | #.G...G.# 58 | #.....G.# 59 | ######### 60 | >>> 18740 61 | -------------------------------------------------------------------------------- /test-data/2018/15b.txt: -------------------------------------------------------------------------------- 1 | ####### 2 | #.G...# 3 | #...EG# 4 | #.#.#G# 5 | #..G#E# 6 | #.....# 7 | ####### 8 | >>> 4988 9 | ####### 10 | #E..EG# 11 | #.#G.E# 12 | #E.##E# 13 | #G..#.# 14 | #..E#.# 15 | ####### 16 | >>> 31284 17 | ####### 18 | #E.G#.# 19 | #.#G..# 20 | #G.#.G# 21 | #G..#.# 22 | #...E.# 23 | ####### 24 | >>> 3478 25 | ####### 26 | #.E...# 27 | #.#..G# 28 | #.###.# 29 | #E#G#G# 30 | #...#G# 31 | ####### 32 | >>> 6474 33 | ######### 34 | #G......# 35 | #.E.#...# 36 | #..##..G# 37 | #...##..# 38 | #...#...# 39 | #.G...G.# 40 | #.....G.# 41 | ######### 42 | >>> 1140 43 | -------------------------------------------------------------------------------- /test-data/2018/17a.txt: -------------------------------------------------------------------------------- 1 | x=495, y=2..7 2 | y=7, x=495..501 3 | x=501, y=3..7 4 | x=498, y=2..4 5 | x=506, y=1..2 6 | x=498, y=10..13 7 | x=504, y=10..13 8 | y=13, x=498..504 9 | >>> 57 10 | x=493, y=1..2 11 | x=497, y=2..4 12 | x=503, y=2..4 13 | y=4, x=497..503 14 | x=494, y=8..9 15 | x=506, y=7..9 16 | y=9, x=495..505 17 | >>> 55 18 | -------------------------------------------------------------------------------- /test-data/2018/18a.txt: -------------------------------------------------------------------------------- 1 | .#.#...|#. 2 | .....#|##| 3 | .|..|...#. 4 | ..|#.....# 5 | #.#|||#|#| 6 | ...#.||... 7 | .|....|... 8 | ||...#|.#| 9 | |.||||..|. 10 | ...#.|..|. 11 | >>> 1147 12 | -------------------------------------------------------------------------------- /test-data/2018/20a.txt: -------------------------------------------------------------------------------- 1 | ^WNE$ 2 | >>> 3 3 | ^ENWWW(NEEE|SSE(EE|N))$ 4 | >>> 10 5 | ^ENNWSWW(NEWS|)SSSEEN(WNSE|)EE(SWEN|)NNN$ 6 | >>> 18 7 | -------------------------------------------------------------------------------- /test-data/2018/22a.txt: -------------------------------------------------------------------------------- 1 | depth: 510 2 | target: 10,10 3 | >>> 114 4 | -------------------------------------------------------------------------------- /test-data/2018/22b.txt: -------------------------------------------------------------------------------- 1 | depth: 510 2 | target: 10,10 3 | >>> 45 4 | -------------------------------------------------------------------------------- /test-data/2018/23a.txt: -------------------------------------------------------------------------------- 1 | pos=<0,0,0>, r=4 2 | pos=<1,0,0>, r=1 3 | pos=<4,0,0>, r=3 4 | pos=<0,2,0>, r=1 5 | pos=<0,5,0>, r=3 6 | pos=<0,0,3>, r=1 7 | pos=<1,1,1>, r=1 8 | pos=<1,1,2>, r=1 9 | pos=<1,3,1>, r=1 10 | >>> 7 11 | -------------------------------------------------------------------------------- /test-data/2018/23b.txt: -------------------------------------------------------------------------------- 1 | pos=<10,12,12>, r=2 2 | pos=<12,14,12>, r=2 3 | pos=<16,12,12>, r=4 4 | pos=<14,14,14>, r=6 5 | pos=<50,50,50>, r=200 6 | pos=<10,10,10>, r=5 7 | >>> 36 8 | -------------------------------------------------------------------------------- /test-data/2018/24a.txt: -------------------------------------------------------------------------------- 1 | Immune System: 2 | 17 units each with 5390 hit points (weak to radiation, bludgeoning) with an attack that does 4507 fire damage at initiative 2 3 | 989 units each with 1274 hit points (immune to fire; weak to bludgeoning, slashing) with an attack that does 25 slashing damage at initiative 3 4 | 5 | Infection: 6 | 801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1 7 | 4485 units each with 2961 hit points (immune to radiation; weak to fire, cold) with an attack that does 12 slashing damage at initiative 4 8 | >>> 5216 9 | -------------------------------------------------------------------------------- /test-data/2018/24b.txt: -------------------------------------------------------------------------------- 1 | Immune System: 2 | 17 units each with 5390 hit points (weak to radiation, bludgeoning) with an attack that does 4507 fire damage at initiative 2 3 | 989 units each with 1274 hit points (immune to fire; weak to bludgeoning, slashing) with an attack that does 25 slashing damage at initiative 3 4 | 5 | Infection: 6 | 801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1 7 | 4485 units each with 2961 hit points (immune to radiation; weak to fire, cold) with an attack that does 12 slashing damage at initiative 4 8 | >>> 51 9 | -------------------------------------------------------------------------------- /test-data/2019/01a.txt: -------------------------------------------------------------------------------- 1 | 12 2 | >>> 2 3 | 14 4 | >>> 2 5 | 1969 6 | >>> 654 7 | 100756 8 | >>> 33583 9 | -------------------------------------------------------------------------------- /test-data/2019/01b.txt: -------------------------------------------------------------------------------- 1 | 14 2 | >>> 2 3 | 1969 4 | >>> 966 5 | 100756 6 | >>> 50346 7 | -------------------------------------------------------------------------------- /test-data/2019/02a.txt: -------------------------------------------------------------------------------- 1 | 1,9,10,3,2,3,11,0,99,30,40,50 2 | >>>noun:9:int 3 | >>>verb:10:int 4 | >>> 3500 5 | 1,0,0,0,99 6 | >>>noun:0:int 7 | >>>verb:0:int 8 | >>> 2 9 | 2,3,0,3,99 10 | >>>noun:3:int 11 | >>>verb:0:int 12 | >>> 2 13 | 2,4,4,5,99,0 14 | >>>noun:4:int 15 | >>>verb:4:int 16 | >>> 2 17 | 1,1,1,4,99,5,6,0,99 18 | >>>noun:1:int 19 | >>>verb:1:int 20 | >>> 30 21 | -------------------------------------------------------------------------------- /test-data/2019/03a.txt: -------------------------------------------------------------------------------- 1 | R8,U5,L5,D3 2 | U7,R6,D4,L4 3 | >>> 6 4 | R75,D30,R83,U83,L12,D49,R71,U7,L72 5 | U62,R66,U55,R34,D71,R55,D58,R83 6 | >>> 159 7 | R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51 8 | U98,R91,D20,R16,D67,R40,U7,R15,U6,R7 9 | >>> 135 10 | -------------------------------------------------------------------------------- /test-data/2019/03b.txt: -------------------------------------------------------------------------------- 1 | R8,U5,L5,D3 2 | U7,R6,D4,L4 3 | >>> 30 4 | R75,D30,R83,U83,L12,D49,R71,U7,L72 5 | U62,R66,U55,R34,D71,R55,D58,R83 6 | >>> 610 7 | R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51 8 | U98,R91,D20,R16,D67,R40,U7,R15,U6,R7 9 | >>> 410 10 | -------------------------------------------------------------------------------- /test-data/2019/04a.txt: -------------------------------------------------------------------------------- 1 | 111111-111111 2 | >>> 1 3 | 223450-223450 4 | >>> 0 5 | 123789-123789 6 | >>> 0 7 | -------------------------------------------------------------------------------- /test-data/2019/04b.txt: -------------------------------------------------------------------------------- 1 | 112233-112233 2 | >>> 1 3 | 123444-123444 4 | >>> 0 5 | 111122-111122 6 | >>> 1 7 | -------------------------------------------------------------------------------- /test-data/2019/05a.txt: -------------------------------------------------------------------------------- 1 | 3,0,4,0,99 2 | >>> 1 3 | -------------------------------------------------------------------------------- /test-data/2019/06a.txt: -------------------------------------------------------------------------------- 1 | COM)B 2 | B)C 3 | C)D 4 | D)E 5 | E)F 6 | B)G 7 | G)H 8 | D)I 9 | E)J 10 | J)K 11 | K)L 12 | >>> 42 13 | -------------------------------------------------------------------------------- /test-data/2019/06b.txt: -------------------------------------------------------------------------------- 1 | COM)B 2 | B)C 3 | C)D 4 | D)E 5 | E)F 6 | B)G 7 | G)H 8 | D)I 9 | E)J 10 | J)K 11 | K)L 12 | K)YOU 13 | I)SAN 14 | >>> 4 15 | -------------------------------------------------------------------------------- /test-data/2019/07a.txt: -------------------------------------------------------------------------------- 1 | 3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0 2 | >>> 43210 3 | 3,23,3,24,1002,24,10,24,1002,23,-1,23, 101,5,23,23,1,24,23,23,4,23,99,0,0 4 | >>> 54321 5 | 3,31,3,32,1002,32,10,32,1001,31,-2,31,1007,31,0,33, 1002,33,7,33,1,33,31,31,1,32,31,31,4,31,99,0,0,0 6 | >>> 65210 7 | -------------------------------------------------------------------------------- /test-data/2019/07b.txt: -------------------------------------------------------------------------------- 1 | 3,26,1001,26,-4,26,3,27,1002,27,2,27,1,27,26,27,4,27,1001,28,-1,28,1005,28,6,99,0,0,5 2 | >>> 139629729 3 | 3,52,1001,52,-5,52,3,53,1,52,56,54,1007,54,5,55,1005,55,26,1001,54,-5,54,1105,1,12,1,53,54,53,1008,54,0,55,1001,55,1,55,2,53,55,53,4,53,1001,56,-1,56,1005,56,6,99,0,0,0,0,10 4 | >>> 18216 5 | -------------------------------------------------------------------------------- /test-data/2019/08a.txt: -------------------------------------------------------------------------------- 1 | 123456789012 2 | >>>w:3:int 3 | >>>h:2:int 4 | >>> 1 5 | -------------------------------------------------------------------------------- /test-data/2019/09a.txt: -------------------------------------------------------------------------------- 1 | 109,1,204,-1,1001,100,1,100,1008,100,16,101,1006,101,0,99 2 | >>> 109 3 | 1102,34915192,34915192,7,4,7,99,0 4 | >>> 1219070632396864 5 | 104,1125899906842624,99 6 | >>> 1125899906842624 7 | -------------------------------------------------------------------------------- /test-data/2019/10a.txt: -------------------------------------------------------------------------------- 1 | .#..# 2 | ..... 3 | ##### 4 | ....# 5 | ...## 6 | >>> 8 7 | ......#.#. 8 | #..#.#.... 9 | ..#######. 10 | .#.#.###.. 11 | .#..#..... 12 | ..#....#.# 13 | #..#....#. 14 | .##.#..### 15 | ##...#..#. 16 | .#....#### 17 | >>> 33 18 | #.#...#.#. 19 | .###....#. 20 | .#....#... 21 | ##.#.#.#.# 22 | ....#.#.#. 23 | .##..###.# 24 | ..#...##.. 25 | ..##....## 26 | ......#... 27 | .####.###. 28 | >>> 35 29 | .#..#..### 30 | ####.###.# 31 | ....###.#. 32 | ..###.##.# 33 | ##.##.#.#. 34 | ....###..# 35 | ..#.#..#.# 36 | #..#.#.### 37 | .##...##.# 38 | .....#.#.. 39 | >>> 41 40 | .#..##.###...####### 41 | ##.############..##. 42 | .#.######.########.# 43 | .###.#######.####.#. 44 | #####.##.#.##.###.## 45 | ..#####..#.######### 46 | #################### 47 | #.####....###.#.#.## 48 | ##.################# 49 | #####.##.###..####.. 50 | ..######..##.####### 51 | ####.##.####...##..# 52 | .#####..#.######.### 53 | ##...#.##########... 54 | #.##########.####### 55 | .####.#.###.###.#.## 56 | ....##.##.###..##### 57 | .#.#.###########.### 58 | #.#.#.#####.####.### 59 | ###.##.####.##.#..## 60 | >>> 210 61 | -------------------------------------------------------------------------------- /test-data/2019/10b.txt: -------------------------------------------------------------------------------- 1 | .#..##.###...####### 2 | ##.############..##. 3 | .#.######.########.# 4 | .###.#######.####.#. 5 | #####.##.#.##.###.## 6 | ..#####..#.######### 7 | #################### 8 | #.####....###.#.#.## 9 | ##.################# 10 | #####.##.###..####.. 11 | ..######..##.####### 12 | ####.##.####...##..# 13 | .#####..#.######.### 14 | ##...#.##########... 15 | #.##########.####### 16 | .####.#.###.###.#.## 17 | ....##.##.###..##### 18 | .#.#.###########.### 19 | #.#.#.#####.####.### 20 | ###.##.####.##.#..## 21 | >>> 802 22 | -------------------------------------------------------------------------------- /test-data/2019/12a.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | >>>steps:10:int 6 | >>> 179 7 | 8 | 9 | 10 | 11 | >>>steps:100:int 12 | >>> 1940 13 | -------------------------------------------------------------------------------- /test-data/2019/12b.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | >>> 2772 6 | 7 | 8 | 9 | 10 | >>> 4686774924 11 | -------------------------------------------------------------------------------- /test-data/2019/14a.txt: -------------------------------------------------------------------------------- 1 | 10 ORE => 10 A 2 | 1 ORE => 1 B 3 | 7 A, 1 B => 1 C 4 | 7 A, 1 C => 1 D 5 | 7 A, 1 D => 1 E 6 | 7 A, 1 E => 1 FUEL 7 | >>> 31 8 | 9 ORE => 2 A 9 | 8 ORE => 3 B 10 | 7 ORE => 5 C 11 | 3 A, 4 B => 1 AB 12 | 5 B, 7 C => 1 BC 13 | 4 C, 1 A => 1 CA 14 | 2 AB, 3 BC, 4 CA => 1 FUEL 15 | >>> 165 16 | 157 ORE => 5 NZVS 17 | 165 ORE => 6 DCFZ 18 | 44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL 19 | 12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ 20 | 179 ORE => 7 PSHF 21 | 177 ORE => 5 HKGWZ 22 | 7 DCFZ, 7 PSHF => 2 XJWVT 23 | 165 ORE => 2 GPVTF 24 | 3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT 25 | >>> 13312 26 | 2 VPVL, 7 FWMGM, 2 CXFTF, 11 MNCFX => 1 STKFG 27 | 17 NVRVD, 3 JNWZP => 8 VPVL 28 | 53 STKFG, 6 MNCFX, 46 VJHF, 81 HVMC, 68 CXFTF, 25 GNMV => 1 FUEL 29 | 22 VJHF, 37 MNCFX => 5 FWMGM 30 | 139 ORE => 4 NVRVD 31 | 144 ORE => 7 JNWZP 32 | 5 MNCFX, 7 RFSQX, 2 FWMGM, 2 VPVL, 19 CXFTF => 3 HVMC 33 | 5 VJHF, 7 MNCFX, 9 VPVL, 37 CXFTF => 6 GNMV 34 | 145 ORE => 6 MNCFX 35 | 1 NVRVD => 8 CXFTF 36 | 1 VJHF, 6 MNCFX => 4 RFSQX 37 | 176 ORE => 6 VJHF 38 | >>> 180697 39 | 171 ORE => 8 CNZTR 40 | 7 ZLQW, 3 BMBT, 9 XCVML, 26 XMNCP, 1 WPTQ, 2 MZWV, 1 RJRHP => 4 PLWSL 41 | 114 ORE => 4 BHXH 42 | 14 VRPVC => 6 BMBT 43 | 6 BHXH, 18 KTJDG, 12 WPTQ, 7 PLWSL, 31 FHTLT, 37 ZDVW => 1 FUEL 44 | 6 WPTQ, 2 BMBT, 8 ZLQW, 18 KTJDG, 1 XMNCP, 6 MZWV, 1 RJRHP => 6 FHTLT 45 | 15 XDBXC, 2 LTCX, 1 VRPVC => 6 ZLQW 46 | 13 WPTQ, 10 LTCX, 3 RJRHP, 14 XMNCP, 2 MZWV, 1 ZLQW => 1 ZDVW 47 | 5 BMBT => 4 WPTQ 48 | 189 ORE => 9 KTJDG 49 | 1 MZWV, 17 XDBXC, 3 XCVML => 2 XMNCP 50 | 12 VRPVC, 27 CNZTR => 2 XDBXC 51 | 15 KTJDG, 12 BHXH => 5 XCVML 52 | 3 BHXH, 2 VRPVC => 7 MZWV 53 | 121 ORE => 7 VRPVC 54 | 7 XCVML => 6 RJRHP 55 | 5 BHXH, 4 VRPVC => 5 LTCX 56 | >>> 2210736 57 | -------------------------------------------------------------------------------- /test-data/2019/14b.txt: -------------------------------------------------------------------------------- 1 | 157 ORE => 5 NZVS 2 | 165 ORE => 6 DCFZ 3 | 44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL 4 | 12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ 5 | 179 ORE => 7 PSHF 6 | 177 ORE => 5 HKGWZ 7 | 7 DCFZ, 7 PSHF => 2 XJWVT 8 | 165 ORE => 2 GPVTF 9 | 3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT 10 | >>> 82892753 11 | 2 VPVL, 7 FWMGM, 2 CXFTF, 11 MNCFX => 1 STKFG 12 | 17 NVRVD, 3 JNWZP => 8 VPVL 13 | 53 STKFG, 6 MNCFX, 46 VJHF, 81 HVMC, 68 CXFTF, 25 GNMV => 1 FUEL 14 | 22 VJHF, 37 MNCFX => 5 FWMGM 15 | 139 ORE => 4 NVRVD 16 | 144 ORE => 7 JNWZP 17 | 5 MNCFX, 7 RFSQX, 2 FWMGM, 2 VPVL, 19 CXFTF => 3 HVMC 18 | 5 VJHF, 7 MNCFX, 9 VPVL, 37 CXFTF => 6 GNMV 19 | 145 ORE => 6 MNCFX 20 | 1 NVRVD => 8 CXFTF 21 | 1 VJHF, 6 MNCFX => 4 RFSQX 22 | 176 ORE => 6 VJHF 23 | >>> 5586022 24 | 171 ORE => 8 CNZTR 25 | 7 ZLQW, 3 BMBT, 9 XCVML, 26 XMNCP, 1 WPTQ, 2 MZWV, 1 RJRHP => 4 PLWSL 26 | 114 ORE => 4 BHXH 27 | 14 VRPVC => 6 BMBT 28 | 6 BHXH, 18 KTJDG, 12 WPTQ, 7 PLWSL, 31 FHTLT, 37 ZDVW => 1 FUEL 29 | 6 WPTQ, 2 BMBT, 8 ZLQW, 18 KTJDG, 1 XMNCP, 6 MZWV, 1 RJRHP => 6 FHTLT 30 | 15 XDBXC, 2 LTCX, 1 VRPVC => 6 ZLQW 31 | 13 WPTQ, 10 LTCX, 3 RJRHP, 14 XMNCP, 2 MZWV, 1 ZLQW => 1 ZDVW 32 | 5 BMBT => 4 WPTQ 33 | 189 ORE => 9 KTJDG 34 | 1 MZWV, 17 XDBXC, 3 XCVML => 2 XMNCP 35 | 12 VRPVC, 27 CNZTR => 2 XDBXC 36 | 15 KTJDG, 12 BHXH => 5 XCVML 37 | 3 BHXH, 2 VRPVC => 7 MZWV 38 | 121 ORE => 7 VRPVC 39 | 7 XCVML => 6 RJRHP 40 | 5 BHXH, 4 VRPVC => 5 LTCX 41 | >>> 460664 42 | -------------------------------------------------------------------------------- /test-data/2019/16a.txt: -------------------------------------------------------------------------------- 1 | 80871224585914546619083218645595 2 | >>> 24176176 3 | 19617804207202209144916044189917 4 | >>> 73745418 5 | 69317163492948606335995924319873 6 | >>> 52432133 7 | -------------------------------------------------------------------------------- /test-data/2019/16b.txt: -------------------------------------------------------------------------------- 1 | 03036732577212944063491565474664 2 | >>> 84462026 3 | 02935109699940807407585447034323 4 | >>> 78725270 5 | 03081770884921959731165446850517 6 | >>> 53553731 7 | -------------------------------------------------------------------------------- /test-data/2019/18a.txt: -------------------------------------------------------------------------------- 1 | ######### 2 | #b.A.@.a# 3 | ######### 4 | >>> 8 5 | ######################## 6 | #f.D.E.e.C.b.A.@.a.B.c.# 7 | ######################.# 8 | #d.....................# 9 | ######################## 10 | >>> 86 11 | ######################## 12 | #...............b.C.D.f# 13 | #.###################### 14 | #.....@.a.B.c.d.A.e.F.g# 15 | ######################## 16 | >>> 132 17 | ################# 18 | #i.G..c...e..H.p# 19 | ########.######## 20 | #j.A..b...f..D.o# 21 | ########@######## 22 | #k.E..a...g..B.n# 23 | ########.######## 24 | #l.F..d...h..C.m# 25 | ################# 26 | >>> 136 27 | ######################## 28 | #@..............ac.GI.b# 29 | ###d#e#f################ 30 | ###A#B#C################ 31 | ###g#h#i################ 32 | ######################## 33 | >>> 81 34 | -------------------------------------------------------------------------------- /test-data/2019/18b.txt: -------------------------------------------------------------------------------- 1 | ############### 2 | #d.ABC.#.....a# 3 | ######...###### 4 | ######.@.###### 5 | ######...###### 6 | #b.....#.....c# 7 | ############### 8 | >>> 24 9 | ############# 10 | #DcBa.#.GhKl# 11 | #.###...#I### 12 | #e#d#.@.#j#k# 13 | ###C#...###J# 14 | #fEbA.#.FgHi# 15 | ############# 16 | >>> 32 17 | ############# 18 | #g#f.D#..h#l# 19 | #F###e#E###.# 20 | #dCba...BcIJ# 21 | #####.@.##### 22 | #nK.L...G...# 23 | #M###N#H###.# 24 | #o#m..#i#jk.# 25 | ############# 26 | >>> 72 27 | -------------------------------------------------------------------------------- /test-data/2019/20a.txt: -------------------------------------------------------------------------------- 1 | A 2 | A 3 | #######.######### 4 | #######.........# 5 | #######.#######.# 6 | #######.#######.# 7 | #######.#######.# 8 | ##### B ###.# 9 | BC...## C ###.# 10 | ##.## ###.# 11 | ##...DE F ###.# 12 | ##### G ###.# 13 | #########.#####.# 14 | DE..#######...###.# 15 | #.#########.###.# 16 | FG..#########.....# 17 | ###########.##### 18 | Z 19 | Z 20 | >>> 23 21 | A 22 | A 23 | #################.############# 24 | #.#...#...................#.#.# 25 | #.#.#.###.###.###.#########.#.# 26 | #.#.#.......#...#.....#.#.#...# 27 | #.#########.###.#####.#.#.###.# 28 | #.............#.#.....#.......# 29 | ###.###########.###.#####.#.#.# 30 | #.....# A C #.#.#.# 31 | ####### S P #####.# 32 | #.#...# #......VT 33 | #.#.#.# #.##### 34 | #...#.# YN....#.# 35 | #.###.# #####.# 36 | DI....#.# #.....# 37 | #####.# #.###.# 38 | ZZ......# QG....#..AS 39 | ###.### ####### 40 | JO..#.#.# #.....# 41 | #.#.#.# ###.#.# 42 | #...#..DI BU....#..LF 43 | #####.# #.##### 44 | YN......# VT..#....QG 45 | #.###.# #.###.# 46 | #.#...# #.....# 47 | ###.### J L J #.#.### 48 | #.....# O F P #.#...# 49 | #.###.#####.#.#####.#####.###.# 50 | #...#.#.#...#.....#.....#.#...# 51 | #.#####.###.###.#.#.#########.# 52 | #...#.#.....#...#.#.#.#.....#.# 53 | #.###.#####.###.###.#.#.####### 54 | #.#.........#...#.............# 55 | #########.###.###.############# 56 | B J C 57 | U P P 58 | >>> 58 59 | -------------------------------------------------------------------------------- /test-data/2019/20b.txt: -------------------------------------------------------------------------------- 1 | A 2 | A 3 | #######.######### 4 | #######.........# 5 | #######.#######.# 6 | #######.#######.# 7 | #######.#######.# 8 | ##### B ###.# 9 | BC...## C ###.# 10 | ##.## ###.# 11 | ##...DE F ###.# 12 | ##### G ###.# 13 | #########.#####.# 14 | DE..#######...###.# 15 | #.#########.###.# 16 | FG..#########.....# 17 | ###########.##### 18 | Z 19 | Z 20 | >>> 26 21 | Z L X W C 22 | Z P Q B K 23 | ###########.#.#.#.#######.############### 24 | #...#.......#.#.......#.#.......#.#.#...# 25 | ###.#.#.#.#.#.#.#.###.#.#.#######.#.#.### 26 | #.#...#.#.#...#.#.#...#...#...#.#.......# 27 | #.###.#######.###.###.#.###.###.#.####### 28 | #...#.......#.#...#...#.............#...# 29 | #.#########.#######.#.#######.#######.### 30 | #...#.# F R I Z #.#.#.# 31 | #.###.# D E C H #.#.#.# 32 | #.#...# #...#.# 33 | #.###.# #.###.# 34 | #.#....OA WB..#.#..ZH 35 | #.###.# #.#.#.# 36 | CJ......# #.....# 37 | ####### ####### 38 | #.#....CK #......IC 39 | #.###.# #.###.# 40 | #.....# #...#.# 41 | ###.### #.#.#.# 42 | XF....#.# RF..#.#.# 43 | #####.# ####### 44 | #......CJ NM..#...# 45 | ###.#.# #.###.# 46 | RE....#.# #......RF 47 | ###.### X X L #.#.#.# 48 | #.....# F Q P #.#.#.# 49 | ###.###########.###.#######.#########.### 50 | #.....#...#.....#.......#...#.....#.#...# 51 | #####.#.###.#######.#######.###.###.#.#.# 52 | #.......#.......#.#.#.#.#...#...#...#.#.# 53 | #####.###.#####.#.#.#.#.###.###.#.###.### 54 | #.......#.....#.#...#...............#...# 55 | #############.#.#.###.################### 56 | A O F N 57 | A A D M 58 | >>> 396 59 | -------------------------------------------------------------------------------- /test-data/2019/24b.txt: -------------------------------------------------------------------------------- 1 | ....# 2 | #..#. 3 | #.?## 4 | ..#.. 5 | #.... 6 | >>>steps:10:int 7 | >>> 99 8 | -------------------------------------------------------------------------------- /test-data/2020/01a.txt: -------------------------------------------------------------------------------- 1 | 1721 2 | 979 3 | 366 4 | 299 5 | 675 6 | 1456 7 | >>> 514579 8 | -------------------------------------------------------------------------------- /test-data/2020/01b.txt: -------------------------------------------------------------------------------- 1 | 1721 2 | 979 3 | 366 4 | 299 5 | 675 6 | 1456 7 | >>> 241861950 8 | -------------------------------------------------------------------------------- /test-data/2020/02a.txt: -------------------------------------------------------------------------------- 1 | 1-3 a: abcde 2 | >>> 1 3 | 1-3 b: cdefg 4 | >>> 0 5 | 2-9 c: ccccccccc 6 | >>> 1 7 | 1-3 a: abcde 8 | 1-3 b: cdefg 9 | 2-9 c: ccccccccc 10 | >>> 2 11 | -------------------------------------------------------------------------------- /test-data/2020/02b.txt: -------------------------------------------------------------------------------- 1 | 1-3 a: abcde 2 | >>> 1 3 | 1-3 b: cdefg 4 | >>> 0 5 | 2-9 c: ccccccccc 6 | >>> 0 7 | 1-3 a: abcde 8 | 1-3 b: cdefg 9 | 2-9 c: ccccccccc 10 | >>> 1 11 | -------------------------------------------------------------------------------- /test-data/2020/04a.txt: -------------------------------------------------------------------------------- 1 | ecl:gry pid:860033327 eyr:2020 hcl:#fffffd 2 | byr:1937 iyr:2017 cid:147 hgt:183cm 3 | >>> 1 4 | iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884 5 | hcl:#cfa07d byr:1929 6 | >>> 0 7 | hcl:#ae17e1 iyr:2013 8 | eyr:2024 9 | ecl:brn pid:760753108 byr:1931 10 | hgt:179cm 11 | >>> 1 12 | hcl:#cfa07d eyr:2025 pid:166559648 13 | iyr:2011 ecl:brn hgt:59in 14 | >>> 0 15 | ecl:gry pid:860033327 eyr:2020 hcl:#fffffd 16 | byr:1937 iyr:2017 cid:147 hgt:183cm 17 | 18 | iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884 19 | hcl:#cfa07d byr:1929 20 | 21 | hcl:#ae17e1 iyr:2013 22 | eyr:2024 23 | ecl:brn pid:760753108 byr:1931 24 | hgt:179cm 25 | 26 | hcl:#cfa07d eyr:2025 pid:166559648 27 | iyr:2011 ecl:brn hgt:59in 28 | >>> 2 29 | -------------------------------------------------------------------------------- /test-data/2020/04b.txt: -------------------------------------------------------------------------------- 1 | eyr:1972 cid:100 2 | hcl:#18171d ecl:amb hgt:170 pid:186cm iyr:2018 byr:1926 3 | 4 | iyr:2019 5 | hcl:#602927 eyr:1967 hgt:170cm 6 | ecl:grn pid:012533040 byr:1946 7 | 8 | hcl:dab227 iyr:2012 9 | ecl:brn hgt:182cm pid:021572410 eyr:2020 byr:1992 cid:277 10 | 11 | hgt:59cm ecl:zzz 12 | eyr:2038 hcl:74454a iyr:2023 13 | pid:3556412378 byr:2007 14 | >>> 0 15 | pid:087499704 hgt:74in ecl:grn iyr:2012 eyr:2030 byr:1980 16 | hcl:#623a2f 17 | 18 | eyr:2029 ecl:blu cid:129 byr:1989 19 | iyr:2014 pid:896056539 hcl:#a97842 hgt:165cm 20 | 21 | hcl:#888785 22 | hgt:164cm byr:2001 iyr:2015 cid:88 23 | pid:545766238 ecl:hzl 24 | eyr:2022 25 | 26 | iyr:2010 hgt:158cm hcl:#b6652a ecl:blu byr:1944 eyr:2021 pid:093154719 27 | >>> 4 28 | -------------------------------------------------------------------------------- /test-data/2020/05a.txt: -------------------------------------------------------------------------------- 1 | FBFBBFFRLR 2 | >>> 357 3 | BFFFBBFRRR 4 | >>> 567 5 | FFFBBBFRRR 6 | >>> 119 7 | BBFFBBFRLL 8 | >>> 820 9 | FBFBBFFRLR 10 | BFFFBBFRRR 11 | FFFBBBFRRR 12 | BBFFBBFRLL 13 | >>> 820 14 | -------------------------------------------------------------------------------- /test-data/2020/07a.txt: -------------------------------------------------------------------------------- 1 | light red bags contain 1 bright white bag, 2 muted yellow bags. 2 | dark orange bags contain 3 bright white bags, 4 muted yellow bags. 3 | bright white bags contain 1 shiny gold bag. 4 | muted yellow bags contain 2 shiny gold bags, 9 faded blue bags. 5 | shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags. 6 | dark olive bags contain 3 faded blue bags, 4 dotted black bags. 7 | vibrant plum bags contain 5 faded blue bags, 6 dotted black bags. 8 | faded blue bags contain no other bags. 9 | dotted black bags contain no other bags. 10 | >>> 4 11 | -------------------------------------------------------------------------------- /test-data/2020/07b.txt: -------------------------------------------------------------------------------- 1 | light red bags contain 1 bright white bag, 2 muted yellow bags. 2 | dark orange bags contain 3 bright white bags, 4 muted yellow bags. 3 | bright white bags contain 1 shiny gold bag. 4 | muted yellow bags contain 2 shiny gold bags, 9 faded blue bags. 5 | shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags. 6 | dark olive bags contain 3 faded blue bags, 4 dotted black bags. 7 | vibrant plum bags contain 5 faded blue bags, 6 dotted black bags. 8 | faded blue bags contain no other bags. 9 | dotted black bags contain no other bags. 10 | >>> 32 11 | shiny gold bags contain 2 dark red bags. 12 | dark red bags contain 2 dark orange bags. 13 | dark orange bags contain 2 dark yellow bags. 14 | dark yellow bags contain 2 dark green bags. 15 | dark green bags contain 2 dark blue bags. 16 | dark blue bags contain 2 dark violet bags. 17 | dark violet bags contain no other bags. 18 | >>> 126 19 | -------------------------------------------------------------------------------- /test-data/2020/09a.txt: -------------------------------------------------------------------------------- 1 | 35 2 | 20 3 | 15 4 | 25 5 | 47 6 | 40 7 | 62 8 | 55 9 | 65 10 | 95 11 | 102 12 | 117 13 | 150 14 | 182 15 | 127 16 | 219 17 | 299 18 | 277 19 | 309 20 | 576 21 | >>>window:5:int 22 | >>> 127 23 | -------------------------------------------------------------------------------- /test-data/2020/09b.txt: -------------------------------------------------------------------------------- 1 | 35 2 | 20 3 | 15 4 | 25 5 | 47 6 | 40 7 | 62 8 | 55 9 | 65 10 | 95 11 | 102 12 | 117 13 | 150 14 | 182 15 | 127 16 | 219 17 | 299 18 | 277 19 | 309 20 | 576 21 | >>>window:5:int 22 | >>> 62 23 | -------------------------------------------------------------------------------- /test-data/2020/10a.txt: -------------------------------------------------------------------------------- 1 | 16 2 | 10 3 | 15 4 | 5 5 | 1 6 | 11 7 | 7 8 | 19 9 | 6 10 | 12 11 | 4 12 | >>> 35 13 | 28 14 | 33 15 | 18 16 | 42 17 | 31 18 | 14 19 | 46 20 | 20 21 | 48 22 | 47 23 | 24 24 | 23 25 | 49 26 | 45 27 | 19 28 | 38 29 | 39 30 | 11 31 | 1 32 | 32 33 | 25 34 | 35 35 | 8 36 | 17 37 | 7 38 | 9 39 | 4 40 | 2 41 | 34 42 | 10 43 | 3 44 | >>> 220 45 | -------------------------------------------------------------------------------- /test-data/2020/10b.txt: -------------------------------------------------------------------------------- 1 | 16 2 | 10 3 | 15 4 | 5 5 | 1 6 | 11 7 | 7 8 | 19 9 | 6 10 | 12 11 | 4 12 | >>> 8 13 | 28 14 | 33 15 | 18 16 | 42 17 | 31 18 | 14 19 | 46 20 | 20 21 | 48 22 | 47 23 | 24 24 | 23 25 | 49 26 | 45 27 | 19 28 | 38 29 | 39 30 | 11 31 | 1 32 | 32 33 | 25 34 | 35 35 | 8 36 | 17 37 | 7 38 | 9 39 | 4 40 | 2 41 | 34 42 | 10 43 | 3 44 | >>> 19208 45 | 3 46 | 4 47 | 5 48 | 6 49 | >>> 4 50 | 1 51 | 2 52 | 3 53 | 4 54 | 5 55 | 6 56 | 7 57 | 8 58 | 9 59 | 10 60 | >>> 274 61 | 1 62 | 2 63 | 3 64 | 4 65 | 5 66 | 6 67 | 7 68 | 8 69 | 9 70 | 10 71 | 11 72 | 12 73 | 13 74 | 14 75 | 15 76 | >>> 5768 77 | 1 78 | 2 79 | 3 80 | 4 81 | 5 82 | 6 83 | 7 84 | 8 85 | 9 86 | 10 87 | 11 88 | 12 89 | 13 90 | 14 91 | 15 92 | 16 93 | 17 94 | 18 95 | 19 96 | 20 97 | >>> 121415 98 | -------------------------------------------------------------------------------- /test-data/2020/11a.txt: -------------------------------------------------------------------------------- 1 | L.LL.LL.LL 2 | LLLLLLL.LL 3 | L.L.L..L.. 4 | LLLL.LL.LL 5 | L.LL.LL.LL 6 | L.LLLLL.LL 7 | ..L.L..... 8 | LLLLLLLLLL 9 | L.LLLLLL.L 10 | L.LLLLL.LL 11 | >>> 37 12 | -------------------------------------------------------------------------------- /test-data/2020/11b.txt: -------------------------------------------------------------------------------- 1 | L.LL.LL.LL 2 | LLLLLLL.LL 3 | L.L.L..L.. 4 | LLLL.LL.LL 5 | L.LL.LL.LL 6 | L.LLLLL.LL 7 | ..L.L..... 8 | LLLLLLLLLL 9 | L.LLLLLL.L 10 | L.LLLLL.LL 11 | >>> 26 12 | -------------------------------------------------------------------------------- /test-data/2020/12a.txt: -------------------------------------------------------------------------------- 1 | F10 2 | N3 3 | F7 4 | R90 5 | F11 6 | >>> 25 7 | -------------------------------------------------------------------------------- /test-data/2020/12b.txt: -------------------------------------------------------------------------------- 1 | F10 2 | N3 3 | F7 4 | R90 5 | F11 6 | >>> 286 7 | -------------------------------------------------------------------------------- /test-data/2020/13a.txt: -------------------------------------------------------------------------------- 1 | 939 2 | 7,13,x,x,59,x,31,19 3 | >>> 295 4 | -------------------------------------------------------------------------------- /test-data/2020/13b.txt: -------------------------------------------------------------------------------- 1 | 939 2 | 7,13,x,x,59,x,31,19 3 | >>> 1068781 4 | 1 5 | 17,x,13,19 6 | >>> 3417 7 | 1 8 | 67,7,59,61 9 | >>> 754018 10 | 1 11 | 67,x,7,59,61 12 | >>> 779210 13 | 1 14 | 67,7,x,59,61 15 | >>> 1261476 16 | 1 17 | 1789,37,47,1889 18 | >>> 1202161486 19 | -------------------------------------------------------------------------------- /test-data/2020/14a.txt: -------------------------------------------------------------------------------- 1 | mask = XXXXXXXXXXXXXXXXXXXXXXXXXXXXX1XXXX0X 2 | mem[8] = 11 3 | mem[7] = 101 4 | mem[8] = 0 5 | >>> 165 6 | -------------------------------------------------------------------------------- /test-data/2020/14b.txt: -------------------------------------------------------------------------------- 1 | mask = 000000000000000000000000000000X1001X 2 | mem[42] = 100 3 | mask = 00000000000000000000000000000000X0XX 4 | mem[26] = 1 5 | >>> 208 6 | -------------------------------------------------------------------------------- /test-data/2020/15a.txt: -------------------------------------------------------------------------------- 1 | 0,3,6 2 | >>> 436 3 | 1,3,2 4 | >>> 1 5 | 2,1,3 6 | >>> 10 7 | 1,2,3 8 | >>> 27 9 | 2,3,1 10 | >>> 78 11 | 3,2,1 12 | >>> 438 13 | 3,1,2 14 | >>> 1836 15 | -------------------------------------------------------------------------------- /test-data/2020/15b.txt: -------------------------------------------------------------------------------- 1 | 0,3,6 2 | >>> 175594 3 | 1,3,2 4 | >>> 2578 5 | 2,1,3 6 | >>> 3544142 7 | 1,2,3 8 | >>> 261214 9 | 2,3,1 10 | >>> 6895259 11 | 3,2,1 12 | >>> 18 13 | 3,1,2 14 | >>> 362 15 | -------------------------------------------------------------------------------- /test-data/2020/16a.txt: -------------------------------------------------------------------------------- 1 | class: 1-3 or 5-7 2 | row: 6-11 or 33-44 3 | seat: 13-40 or 45-50 4 | 5 | your ticket: 6 | 7,1,14 7 | 8 | nearby tickets: 9 | 7,3,47 10 | 40,4,50 11 | 55,2,20 12 | 38,6,12 13 | >>> 71 14 | -------------------------------------------------------------------------------- /test-data/2020/16b.txt: -------------------------------------------------------------------------------- 1 | class: 0-1 or 4-19 2 | row: 0-5 or 8-19 3 | seat: 0-13 or 16-19 4 | 5 | your ticket: 6 | 11,12,13 7 | 8 | nearby tickets: 9 | 3,9,18 10 | 15,1,5 11 | 5,14,9 12 | >>>prefix:seat:text 13 | >>> 13 14 | -------------------------------------------------------------------------------- /test-data/2020/17a.txt: -------------------------------------------------------------------------------- 1 | .#. 2 | ..# 3 | ### 4 | >>> 112 5 | -------------------------------------------------------------------------------- /test-data/2020/17b.txt: -------------------------------------------------------------------------------- 1 | .#. 2 | ..# 3 | ### 4 | >>> 848 5 | -------------------------------------------------------------------------------- /test-data/2020/18a.txt: -------------------------------------------------------------------------------- 1 | 1 + 2 * 3 + 4 * 5 + 6 2 | >>> 71 3 | 1 + (2 * 3) + (4 * (5 + 6)) 4 | >>> 51 5 | 2 * 3 + (4 * 5) 6 | >>> 26 7 | 5 + (8 * 3 + 9 + 3 * 4 * 3) 8 | >>> 437 9 | 5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4)) 10 | >>> 12240 11 | ((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2 12 | >>> 13632 13 | -------------------------------------------------------------------------------- /test-data/2020/18b.txt: -------------------------------------------------------------------------------- 1 | 1 + 2 * 3 + 4 * 5 + 6 2 | >>> 231 3 | 1 + (2 * 3) + (4 * (5 + 6)) 4 | >>> 51 5 | 2 * 3 + (4 * 5) 6 | >>> 46 7 | 5 + (8 * 3 + 9 + 3 * 4 * 3) 8 | >>> 1445 9 | 5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4)) 10 | >>> 669060 11 | ((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2 12 | >>> 23340 13 | -------------------------------------------------------------------------------- /test-data/2020/19a.txt: -------------------------------------------------------------------------------- 1 | 0: 4 1 5 2 | 1: 2 3 | 3 2 3 | 2: 4 4 | 5 5 4 | 3: 4 5 | 5 4 5 | 4: "a" 6 | 5: "b" 7 | 8 | ababbb 9 | bababa 10 | abbbab 11 | aaabbb 12 | aaaabbb 13 | >>> 2 14 | -------------------------------------------------------------------------------- /test-data/2020/19b.txt: -------------------------------------------------------------------------------- 1 | 42: 9 14 | 10 1 2 | 9: 14 27 | 1 26 3 | 10: 23 14 | 28 1 4 | 1: "a" 5 | 11: 42 31 6 | 5: 1 14 | 15 1 7 | 19: 14 1 | 14 14 8 | 12: 24 14 | 19 1 9 | 16: 15 1 | 14 14 10 | 31: 14 17 | 1 13 11 | 6: 14 14 | 1 14 12 | 2: 1 24 | 14 4 13 | 0: 8 11 14 | 13: 14 3 | 1 12 15 | 15: 1 | 14 16 | 17: 14 2 | 1 7 17 | 23: 25 1 | 22 14 18 | 28: 16 1 19 | 4: 1 1 20 | 20: 14 14 | 1 15 21 | 3: 5 14 | 16 1 22 | 27: 1 6 | 14 18 23 | 14: "b" 24 | 21: 14 1 | 1 14 25 | 25: 1 1 | 1 14 26 | 22: 14 14 27 | 8: 42 28 | 26: 14 22 | 1 20 29 | 18: 15 15 30 | 7: 14 5 | 1 21 31 | 24: 14 1 32 | 33 | abbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa 34 | bbabbbbaabaabba 35 | babbbbaabbbbbabbbbbbaabaaabaaa 36 | aaabbbbbbaaaabaababaabababbabaaabbababababaaa 37 | bbbbbbbaaaabbbbaaabbabaaa 38 | bbbababbbbaaaaaaaabbababaaababaabab 39 | ababaaaaaabaaab 40 | ababaaaaabbbaba 41 | baabbaaaabbaaaababbaababb 42 | abbbbabbbbaaaababbbbbbaaaababb 43 | aaaaabbaabaaaaababaa 44 | aaaabbaaaabbaaa 45 | aaaabbaabbaaaaaaabbbabbbaaabbaabaaa 46 | babaaabbbaaabaababbaabababaaab 47 | aabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba 48 | >>> 12 49 | -------------------------------------------------------------------------------- /test-data/2020/20a.txt: -------------------------------------------------------------------------------- 1 | Tile 2311: 2 | ..##.#..#. 3 | ##..#..... 4 | #...##..#. 5 | ####.#...# 6 | ##.##.###. 7 | ##...#.### 8 | .#.#.#..## 9 | ..#....#.. 10 | ###...#.#. 11 | ..###..### 12 | 13 | Tile 1951: 14 | #.##...##. 15 | #.####...# 16 | .....#..## 17 | #...###### 18 | .##.#....# 19 | .###.##### 20 | ###.##.##. 21 | .###....#. 22 | ..#.#..#.# 23 | #...##.#.. 24 | 25 | Tile 1171: 26 | ####...##. 27 | #..##.#..# 28 | ##.#..#.#. 29 | .###.####. 30 | ..###.#### 31 | .##....##. 32 | .#...####. 33 | #.##.####. 34 | ####..#... 35 | .....##... 36 | 37 | Tile 1427: 38 | ###.##.#.. 39 | .#..#.##.. 40 | .#.##.#..# 41 | #.#.#.##.# 42 | ....#...## 43 | ...##..##. 44 | ...#.##### 45 | .#.####.#. 46 | ..#..###.# 47 | ..##.#..#. 48 | 49 | Tile 1489: 50 | ##.#.#.... 51 | ..##...#.. 52 | .##..##... 53 | ..#...#... 54 | #####...#. 55 | #..#.#.#.# 56 | ...#.#.#.. 57 | ##.#...##. 58 | ..##.##.## 59 | ###.##.#.. 60 | 61 | Tile 2473: 62 | #....####. 63 | #..#.##... 64 | #.##..#... 65 | ######.#.# 66 | .#...#.#.# 67 | .######### 68 | .###.#..#. 69 | ########.# 70 | ##...##.#. 71 | ..###.#.#. 72 | 73 | Tile 2971: 74 | ..#.#....# 75 | #...###... 76 | #.#.###... 77 | ##.##..#.. 78 | .#####..## 79 | .#..####.# 80 | #..#.#..#. 81 | ..####.### 82 | ..#.#.###. 83 | ...#.#.#.# 84 | 85 | Tile 2729: 86 | ...#.#.#.# 87 | ####.#.... 88 | ..#.#..... 89 | ....#..#.# 90 | .##..##.#. 91 | .#.####... 92 | ####.#.#.. 93 | ##.####... 94 | ##..#.##.. 95 | #.##...##. 96 | 97 | Tile 3079: 98 | #.#.#####. 99 | .#..###### 100 | ..#....... 101 | ######.... 102 | ####.#..#. 103 | .#...#.##. 104 | #.#####.## 105 | ..#.###... 106 | ..#....... 107 | ..#.###... 108 | >>> 20899048083289 109 | -------------------------------------------------------------------------------- /test-data/2020/20b.txt: -------------------------------------------------------------------------------- 1 | Tile 2311: 2 | ..##.#..#. 3 | ##..#..... 4 | #...##..#. 5 | ####.#...# 6 | ##.##.###. 7 | ##...#.### 8 | .#.#.#..## 9 | ..#....#.. 10 | ###...#.#. 11 | ..###..### 12 | 13 | Tile 1951: 14 | #.##...##. 15 | #.####...# 16 | .....#..## 17 | #...###### 18 | .##.#....# 19 | .###.##### 20 | ###.##.##. 21 | .###....#. 22 | ..#.#..#.# 23 | #...##.#.. 24 | 25 | Tile 1171: 26 | ####...##. 27 | #..##.#..# 28 | ##.#..#.#. 29 | .###.####. 30 | ..###.#### 31 | .##....##. 32 | .#...####. 33 | #.##.####. 34 | ####..#... 35 | .....##... 36 | 37 | Tile 1427: 38 | ###.##.#.. 39 | .#..#.##.. 40 | .#.##.#..# 41 | #.#.#.##.# 42 | ....#...## 43 | ...##..##. 44 | ...#.##### 45 | .#.####.#. 46 | ..#..###.# 47 | ..##.#..#. 48 | 49 | Tile 1489: 50 | ##.#.#.... 51 | ..##...#.. 52 | .##..##... 53 | ..#...#... 54 | #####...#. 55 | #..#.#.#.# 56 | ...#.#.#.. 57 | ##.#...##. 58 | ..##.##.## 59 | ###.##.#.. 60 | 61 | Tile 2473: 62 | #....####. 63 | #..#.##... 64 | #.##..#... 65 | ######.#.# 66 | .#...#.#.# 67 | .######### 68 | .###.#..#. 69 | ########.# 70 | ##...##.#. 71 | ..###.#.#. 72 | 73 | Tile 2971: 74 | ..#.#....# 75 | #...###... 76 | #.#.###... 77 | ##.##..#.. 78 | .#####..## 79 | .#..####.# 80 | #..#.#..#. 81 | ..####.### 82 | ..#.#.###. 83 | ...#.#.#.# 84 | 85 | Tile 2729: 86 | ...#.#.#.# 87 | ####.#.... 88 | ..#.#..... 89 | ....#..#.# 90 | .##..##.#. 91 | .#.####... 92 | ####.#.#.. 93 | ##.####... 94 | ##..#.##.. 95 | #.##...##. 96 | 97 | Tile 3079: 98 | #.#.#####. 99 | .#..###### 100 | ..#....... 101 | ######.... 102 | ####.#..#. 103 | .#...#.##. 104 | #.#####.## 105 | ..#.###... 106 | ..#....... 107 | ..#.###... 108 | >>> 273 109 | -------------------------------------------------------------------------------- /test-data/2020/21a.txt: -------------------------------------------------------------------------------- 1 | mxmxvkd kfcds sqjhc nhms (contains dairy, fish) 2 | trh fvjkl sbzzf mxmxvkd (contains dairy) 3 | sqjhc fvjkl (contains soy) 4 | sqjhc mxmxvkd sbzzf (contains fish) 5 | >>> 5 6 | -------------------------------------------------------------------------------- /test-data/2020/21b.txt: -------------------------------------------------------------------------------- 1 | mxmxvkd kfcds sqjhc nhms (contains dairy, fish) 2 | trh fvjkl sbzzf mxmxvkd (contains dairy) 3 | sqjhc fvjkl (contains soy) 4 | sqjhc mxmxvkd sbzzf (contains fish) 5 | >>> mxmxvkd,sqjhc,fvjkl 6 | -------------------------------------------------------------------------------- /test-data/2020/22a.txt: -------------------------------------------------------------------------------- 1 | Player 1: 2 | 9 3 | 2 4 | 6 5 | 3 6 | 1 7 | 8 | Player 2: 9 | 5 10 | 8 11 | 4 12 | 7 13 | 10 14 | >>> 306 15 | -------------------------------------------------------------------------------- /test-data/2020/22b.txt: -------------------------------------------------------------------------------- 1 | Player 1: 2 | 9 3 | 2 4 | 6 5 | 3 6 | 1 7 | 8 | Player 2: 9 | 5 10 | 8 11 | 4 12 | 7 13 | 10 14 | >>> 291 15 | -------------------------------------------------------------------------------- /test-data/2020/23a.txt: -------------------------------------------------------------------------------- 1 | 389125467 2 | >>> 67384529 3 | -------------------------------------------------------------------------------- /test-data/2020/23b.txt: -------------------------------------------------------------------------------- 1 | 389125467 2 | >>> 149245887792 3 | -------------------------------------------------------------------------------- /test-data/2020/24a.txt: -------------------------------------------------------------------------------- 1 | sesenwnenenewseeswwswswwnenewsewsw 2 | neeenesenwnwwswnenewnwwsewnenwseswesw 3 | seswneswswsenwwnwse 4 | nwnwneseeswswnenewneswwnewseswneseene 5 | swweswneswnenwsewnwneneseenw 6 | eesenwseswswnenwswnwnwsewwnwsene 7 | sewnenenenesenwsewnenwwwse 8 | wenwwweseeeweswwwnwwe 9 | wsweesenenewnwwnwsenewsenwwsesesenwne 10 | neeswseenwwswnwswswnw 11 | nenwswwsewswnenenewsenwsenwnesesenew 12 | enewnwewneswsewnwswenweswnenwsenwsw 13 | sweneswneswneneenwnewenewwneswswnese 14 | swwesenesewenwneswnwwneseswwne 15 | enesenwswwswneneswsenwnewswseenwsese 16 | wnwnesenesenenwwnenwsewesewsesesew 17 | nenewswnwewswnenesenwnesewesw 18 | eneswnwswnwsenenwnwnwwseeswneewsenese 19 | neswnwewnwnwseenwseesewsenwsweewe 20 | wseweeenwnesenwwwswnew 21 | >>> 10 22 | -------------------------------------------------------------------------------- /test-data/2020/24b.txt: -------------------------------------------------------------------------------- 1 | sesenwnenenewseeswwswswwnenewsewsw 2 | neeenesenwnwwswnenewnwwsewnenwseswesw 3 | seswneswswsenwwnwse 4 | nwnwneseeswswnenewneswwnewseswneseene 5 | swweswneswnenwsewnwneneseenw 6 | eesenwseswswnenwswnwnwsewwnwsene 7 | sewnenenenesenwsewnenwwwse 8 | wenwwweseeeweswwwnwwe 9 | wsweesenenewnwwnwsenewsenwwsesesenwne 10 | neeswseenwwswnwswswnw 11 | nenwswwsewswnenenewsenwsenwnesesenew 12 | enewnwewneswsewnwswenweswnenwsenwsw 13 | sweneswneswneneenwnewenewwneswswnese 14 | swwesenesewenwneswnwwneseswwne 15 | enesenwswwswneneswsenwnewswseenwsese 16 | wnwnesenesenenwwnenwsewesewsesesew 17 | nenewswnwewswnenesenwnesewesw 18 | eneswnwswnwsenenwnwnwwseeswneewsenese 19 | neswnwewnwnwseenwseesewsenwsweewe 20 | wseweeenwnesenwwwswnew 21 | >>> 2208 22 | -------------------------------------------------------------------------------- /test/Spec.hs: -------------------------------------------------------------------------------- 1 | 2 | import AOC 3 | import Control.Monad.Except 4 | import Data.Semigroup 5 | import System.Exit 6 | import Text.Printf 7 | import qualified System.Console.ANSI as ANSI 8 | 9 | main :: IO () 10 | main = do 11 | putStrLn "" 12 | cfg <- configFile defConfPath 13 | out <- runExceptT $ do 14 | runOut <- mainRun cfg $ (defaultMRO TSAll) 15 | { _mroTest = True 16 | } 17 | let Sum totErrors = (foldMap . foldMap) (uncurry numErrors) runOut 18 | when (totErrors > 0) $ throwError [printf "Failed %d test(s)." totErrors] 19 | case out of 20 | Left e -> do 21 | withColor ANSI.Vivid ANSI.Red $ 22 | putStrLn "[ERROR]" 23 | mapM_ putStrLn e 24 | exitFailure 25 | Right () -> pure () 26 | 27 | numErrors 28 | :: Maybe Bool 29 | -> Either [String] String 30 | -> Sum Int 31 | numErrors m e = contM m <> contE e 32 | where 33 | contM Nothing = 0 34 | contM (Just r) 35 | | r = Sum 0 36 | | otherwise = Sum 1 37 | contE (Left es) 38 | | null es = Sum 0 39 | | otherwise = Sum 1 40 | contE (Right _) = Sum 0 41 | --------------------------------------------------------------------------------