diff --git a/README.md b/README.md index c6dcf50..3dcd115 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Advent of Code 2023 This file generated by the build script at ./Build.hs --> -*[2016][]* / *[2017][]* / *[2018][]* / *[2019][]* / *[2020][]* / *[2021][]* / *[2022][]* / *2023* +*[2016][]* / *[2017][]* / *[2018][]* / *[2019][]* / *[2020][]* / *[2021][]* / *[2022][]* / *[2023][]* [2016]: https://github.com/mstksg/advent-of-code-2016 [2017]: https://github.com/mstksg/advent-of-code-2017 @@ -14,304 +14,8 @@ This file generated by the build script at ./Build.hs [2020]: https://github.com/mstksg/advent-of-code-2020 [2021]: https://github.com/mstksg/advent-of-code-2021 [2022]: https://github.com/mstksg/advent-of-code-2022 +[2023]: https://github.com/mstksg/advent-of-code-2023 -It's the most wonderful time of the year! - -My [Advent of Code 2023][aoc] Haskell solutions here, along with an automated -fetching, testing, running environment (powered by the -*[advent-of-code-api][]* library). The interactive development environment and -runner/bench marker/viewer/tester has been pulled out [here][dev], so this is -implemented as "fork" of it with my own solutions and reflections. - -Check out the [reflections][] (with [rss feed][rss]) and [package -haddocks][haddock] --- more info below! - -[aoc]: https://adventofcode.com/2023 -[haddock]: https://mstksg.github.io/advent-of-code-2023/ -[advent-of-code-api]: https://hackage.haskell.org/package/advent-of-code-api -[dev]: https://github.com/mstksg/advent-of-code-dev - -[Reflections and Benchmarks][reflections] ------------------------------------------ - -[Available as RSS Feed][rss] - -[rss]: http://feeds.feedburner.com/jle-advent-of-code-2023 - -| Challenge | Reflections | Code | Rendered | Benchmarks | -| --------- | ----------- | --------- | ---------- | ---------- | -| Day 1 | [x][d01r] | [x][d01g] | [x][d01h] | [x][d01b] | -| Day 2 | [x][d02r] | [x][d02g] | [x][d02h] | [x][d02b] | -| Day 3 | | [x][d03g] | [x][d03h] | [x][d03b] | -| Day 4 | | [x][d04g] | [x][d04h] | [x][d04b] | -| Day 5 | [x][d05r] | [x][d05g] | [x][d05h] | [x][d05b] | -| Day 6 | | | | | -| Day 7 | | | | | -| Day 8 | | | | | -| Day 9 | | | | | -| Day 10 | | | | | -| Day 11 | | | | | -| Day 12 | | | | | -| Day 13 | | | | | -| Day 14 | | | | | -| Day 15 | | | | | -| Day 16 | | | | | -| Day 17 | | | | | -| Day 18 | | | | | -| Day 19 | | | | | -| Day 20 | | | | | -| Day 21 | | | | | -| Day 22 | | | | | -| Day 23 | | | | | -| Day 24 | | | | | -| Day 25 | | | | | - -"Rendered" links go to haddock source renders for code, with reflections in the -documentation. Haddock source renders have hyperlinked identifiers, -so you can follow any unrecognized identifiers to see where I have defined them -in the library. - -[reflections]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md - -### `:~>` type - -If you're looking at my actual github solutions, you'll notice that this year -I'm implementing my solutions in terms of a `:~>` record type: - -```haskell -data a :~> b = MkSol - { sParse :: String -> Maybe a -- ^ parse input into an `a` - , sSolve :: a -> Maybe b -- ^ solve an `a` input to a `b` solution - , sShow :: b -> String -- ^ print out the `b` solution for submission - } -``` - -An `a :~> b` is a solution to a challenge expecting input of type `a` and -producing answers of type `b`. It also packs in functions to parse a `String` -into an `a`, and functions to show a `b` as a `String` to submit as an answer. - -This helps me mentally separate out parsing, solving, and showing, allowing for -some cleaner code and an easier time planning my solution. - -Such a challenge can be "run" on string inputs by feeding the string into -`sParse`, then `sSolve`, then `sShow`: - -```haskell --- | Run a ':~>' on some input, retuning 'Maybe' -runSolution :: Challenge -> String -> Maybe String -runSolution MkSol{..} s = do - x <- sParse s - y <- sSolve x - pure (sShow y) -``` - -In the actual library, I have `runSolution` return an `Either` so I can debug -which stage the error happened in. - -You might also notice the function `dyno_`, used like `dyno_ "limit" 10000`. This -is how I implement parameters in problems that vary between test data and -actual input. For example, Day 6 involved finding points that had a total -distance of less than 10000, but for the test input, we found the points that -had a total distance of less than 32. So, I have a system that lets me write -`dyno_ "limit" 10000` in my code instead of hard-coding in `10000`. This -`10000` would be replaced by `32` when running with test data (which is parsed -from [this file][7btest]) - -[7btest]: https://github.com/mstksg/advent-of-code-2018/blob/master/test-data/06b.txt - -Interactive ------------ - -The *[AOC.Run.Interactive][interactive]* module has code (powered by -*[advent-of-code-api][]*) for testing your solutions and submitting within -GHCI, so you don't have to re-compile. If you edit your solution programs, they -are automatically updated when you hit `:r` in ghci. - -[interactive]: https://mstksg.github.io/advent-of-code-2023/AOC2023-Run-Interactive.html - -```haskell -ghci> execSolution_ $ solSpec 'day02a -- get answer for challenge based on solution -ghci> testSolution_ $ solSpec 'day02a -- run solution against test suite -ghci> viewPrompt_ $ solSpec 'day02a -- view the prompt for a part -ghci> waitForPrompt_ $ solSpec 'day02a -- count down to the prompt for a part -ghci> submitSolution_ $ solSpec 'day02a -- submit a solution, and retry after cooldown automatically -``` - -These are loaded with session key stored in the configuration file (see next -section). - -Executable ----------- - -Comes with test examples given in problems. - -You can install using `stack`: - -```bash -$ git clone https://github.com/mstksg/advent-of-code-2023 -$ cd advent-of-code-2023 -$ stack setup -$ stack install -``` - -The executable `aoc2023` includes a testing and benchmark suite, as well as a -way to view prompts within the command line: - -``` -$ aoc2023 --help -aoc2023 - Advent of Code 2023 challenge runner - -Usage: aoc2023 [-c|--config PATH] COMMAND - Run challenges from Advent of Code 2023. Available days: 1, 2, 3 (..) - -Available options: - -c,--config PATH Path to configuration file (default: aoc-conf.yaml) - -h,--help Show this help text - -Available commands: - run Run, test, and benchmark challenges - view View a prompt for a given challenge - submit Test and submit answers for challenges - test Alias for run --test - bench Alias for run --bench - countdown Alias for view --countdown - -$ aoc2023 run 3 b ->> Day 03b ->> [✓] 243 -``` - -You can supply input via stdin with `--stdin`: - -``` -$ aoc2023 run 1 --stdin ->> Day 01a -+1 -+2 -+1 --3 - -[?] 1 ->> Day 01b -[?] 1 -``` - -Benchmarking is implemented using *criterion* - -``` -$ aoc2023 bench 2 ->> Day 02a -benchmarking... -time 1.317 ms (1.271 ms .. 1.392 ms) - 0.982 R² (0.966 R² .. 0.999 R²) -mean 1.324 ms (1.298 ms .. 1.373 ms) -std dev 115.5 μs (77.34 μs .. 189.0 μs) -variance introduced by outliers: 65% (severely inflated) - ->> Day 02b -benchmarking... -time 69.61 ms (68.29 ms .. 72.09 ms) - 0.998 R² (0.996 R² .. 1.000 R²) -mean 69.08 ms (68.47 ms .. 69.99 ms) -std dev 1.327 ms (840.8 μs .. 1.835 ms) -``` - -Test suites run the example problems given in the puzzle description, and -outputs are colorized in ANSI terminals. - -``` -$ aoc2023 test 1 ->> Day 01a -[✓] (3) -[✓] (3) -[✓] (0) -[✓] (-6) -[✓] Passed 4 out of 4 test(s) -[✓] 416 ->> Day 01b -[✓] (2) -[✓] (0) -[✓] (10) -[✓] (5) -[✓] (14) -[✓] Passed 5 out of 5 test(s) -[✓] 56752 -``` - -This should only work if you're running `aoc2023` in the project directory. - -**To run on actual inputs**, the executable expects inputs to be found in the -folder `data/XX.txt` in the directory you are running in. That is, the input -for Day 7 will be expected at `data/07.txt`. - -*aoc2023 will download missing input files*, but requires a session token. -This can be provided in `aoc-conf.yaml`: - -```yaml -session: [[ session token goes here ]] -``` - -Session keys are also required to download "Part 2" prompts for each challenge. - -You can "lock in" your current answers (telling the executable that those are -the correct answers) by passing in `--lock`. This will lock in any final -puzzle solutions encountered as the verified official answers. Later, if you -edit or modify your solutions, they will be checked on the locked-in answers. - -These are stored in `data/ans/XXpart.txt`. That is, the target output for Day 7 -(Part 2, `b`) will be expected at `data/ans/07b.txt`. You can also manually -edit these files. - -You can view prompts: (use `--countdown` to count down until a prompt is -released, and display immediately) - -``` -$ aoc2023 view 3 b ->> Day 03b ---- Part Two --- ----------------- - -Amidst the chaos, you notice that exactly one claim doesn't overlap by -even a single square inch of fabric with any other claim. If you can -somehow draw attention to it, maybe the Elves will be able to make -Santa's suit after all! - -For example, in the claims above, only claim `3` is intact after all -claims are made. - -*What is the ID of the only claim that doesn't overlap?* -``` - -You can also submit answers: - -``` -$ aoc2023 submit 1 a -``` - -Submissions will automatically run the test suite. If any tests fail, you will -be asked to confirm submission or else abort. The submit command will output -the result of your submission: The message from the AoC website, and whether or -not your answer was correct (or invalid or ignored). Answers that are -confirmed correct will be locked in and saved for future testing against, in -case you change your solution. - -All networking features are powered by *[advent-of-code-api][]*. - -[d01g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day01.hs -[d01h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day01.html -[d01r]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md#day-1 -[d01b]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md#day-1-benchmarks -[d02g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day02.hs -[d02h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day02.html -[d02r]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md#day-2 -[d02b]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md#day-2-benchmarks -[d03g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day03.hs -[d03h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day03.html -[d03b]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md#day-3-benchmarks -[d04g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day04.hs -[d04h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day04.html -[d04b]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md#day-4-benchmarks -[d05g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day05.hs -[d05h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day05.html -[d05r]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md#day-5 -[d05b]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md#day-5-benchmarks +In-progress master repo that's eventually going to subsume my individual year +repos, and maybe even include non-haskell. In the meanwhile, check out the +yearly repositories above. diff --git a/Setup.hs b/Setup.hs deleted file mode 100644 index 9a994af..0000000 --- a/Setup.hs +++ /dev/null @@ -1,2 +0,0 @@ -import Distribution.Simple -main = defaultMain diff --git a/app/aoc-runner.hs b/app/aoc-runner.hs deleted file mode 100644 index aca1c7f..0000000 --- a/app/aoc-runner.hs +++ /dev/null @@ -1,203 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} -{-# OPTIONS_GHC -Werror=incomplete-patterns #-} - -import AOC -import Control.Applicative -import Control.DeepSeq -import Control.Exception -import Control.Lens hiding (argument) -import Control.Monad -import Control.Monad.Except -import Data.Char -import Data.IORef -import Data.Maybe -import Options.Applicative -import System.IO.Error -import Text.Printf -import Text.Read -import qualified Data.List as L -import qualified Data.Map as M -import qualified System.Console.ANSI as ANSI - -data Mode = MRun MainRunOpts - | MView MainViewOpts - | MSubmit MainSubmitOpts - -data Opts = O { _oMode :: !Mode - , _oConfig :: !(Maybe FilePath) - } - -main :: IO () -main = do - inputCache <- newIORef Nothing - O{..} <- execParser $ info (parseOpts inputCache <**> helper) - ( fullDesc - <> header "aoc-dev - Advent of Code interactive development environment" - <> progDesc ("Run, test, bench, challenges from Advent of Code, and view prompts. Available days: " ++ availableDays) - ) - cfg <- configFile $ fromMaybe defConfPath _oConfig - out <- runExceptT $ case _oMode of - MRun mro -> void $ mainRun cfg mro - MView mvo -> void $ mainView cfg mvo - MSubmit mso -> void $ mainSubmit cfg mso - forOf_ _Left out $ \e -> do - withColor ANSI.Vivid ANSI.Red $ - putStrLn "[ERROR]" - mapM_ putStrLn e - where - availableDays = L.intercalate ", " - . map (show . dayInt) - . M.keys - $ challengeMap - - --- --------- --- | Parsers --- --------- - -readDay :: ReadM Day -readDay = eitherReader $ \s -> do - n <- maybe (Left "Invalid day") Right $ readMaybe s - maybe (Left "Day out of range") Right $ mkDay n - -readPart :: ReadM Part -readPart = eitherReader $ \case - "" -> Left "No part" - "a" -> Right Part1 - "b" -> Right Part2 - _ -> Left "Invalid part (not 'a' or 'b')" - -parseChallengeSpec :: Parser ChallengeSpec -parseChallengeSpec = do - d <- argument pDay ( metavar "DAY" - <> help "Day of challenge (1 - 25)" - ) - p <- argument pPart ( metavar "PART" - <> help "Challenge part (a, b, c, etc.)" - ) - pure $ CS d p - where - pDay = readDay - pPart = readPart - -parseTestSpec :: Parser TestSpec -parseTestSpec = do - d <- argument pDay ( metavar "DAY" - <> help "Day of challenge (1 - 25), or \"all\"" - ) - p <- optional $ argument pPart ( metavar "PART" - <> help "Challenge part (a, b, c, etc.)" - ) - pure $ case d of - Just d' -> case p of - Just p' -> TSDayPart (CS d' p') - Nothing -> TSDayAll d' - Nothing -> TSAll - where - pDay = asum [ Nothing <$ maybeReader (guard . (== "all") . map toLower) - , Just <$> readDay - ] - pPart = readPart - -parseOpts - :: IORef (Maybe (Maybe (Day, String))) - -> Parser Opts -parseOpts inputCache = do - _oConfig <- optional . strOption . mconcat $ - [ long "config" - , metavar "PATH" - , help $ printf "Path to configuration file (default: %s)" defConfPath - ] - _oMode <- subparser . mconcat $ - [ command "run" $ - info (MRun <$> parseRun <**> helper) (progDesc "Run, test, and benchmark challenges" ) - , command "view" $ - info (MView <$> parseView <**> helper) (progDesc "View a prompt for a given challenge" ) - , command "submit" $ - info (MSubmit <$> parseSubmit <**> helper) (progDesc "Test and submit answers for challenges") - , command "test" $ - info (MRun <$> parseTest <**> helper) (progDesc "Alias for run --test" ) - , command "bench" $ - info (MRun <$> parseBench <**> helper) (progDesc "Alias for run --bench" ) - , command "countdown" $ - info (MView <$> parseCountdown <**> helper) (progDesc "Alias for view --countdown" ) - ] - pure O{..} - where - parseRun :: Parser MainRunOpts - parseRun = do - _mroSpec <- parseTestSpec - _mroActual <- fmap not . switch . mconcat $ - [ long "skip" - , short 's' - , help "Do not run the actual input, but still run tests or benchmarks" - ] - _mroTest <- switch . mconcat $ - [ long "test" - , short 't' - , help "Run sample tests" - ] - _mroBench <- switch . mconcat $ - [ long "bench" - , short 'b' - , help "Run benchmarks" - ] - _mroLock <- switch . mconcat $ - [ long "lock" - , short 'l' - , help "Lock in results as \"correct\" answers" - ] - takeStdin <- switch . mconcat $ - [ long "stdin" - , help "Take first input from stdin instead of input directory" - ] - pure $ let _mroInput - | takeStdin = \d _ -> pullStdin inputCache d - | otherwise = \_ _ -> pure Nothing - in MRO{..} - parseView :: Parser MainViewOpts - parseView = do - _mvoSpec <- parseTestSpec - _mvoWait <- switch . mconcat $ - [ long "countdown" - , short 'c' - , help "Countdown until release if not yet available" - ] - pure MVO{..} - parseSubmit :: Parser MainSubmitOpts - parseSubmit = do - _msoSpec <- parseChallengeSpec - _msoTest <- fmap not . switch . mconcat $ - [ long "skip-tests" - , short 's' - , help "Skip running tests before submission" - ] - _msoForce <- switch . mconcat $ - [ long "force" - , short 'f' - , help "Always submit, even if tests fail" - ] - _msoLock <- fmap not . switch . mconcat $ - [ long "no-lock" - , short 'n' - , help "Do not lock in answer, even if correct submission was received" - ] - pure MSO{..} - parseTest :: Parser MainRunOpts - parseTest = parseRun & mapped . mroTest .~ True - parseBench :: Parser MainRunOpts - parseBench = parseRun & mapped . mroBench .~ True - parseCountdown :: Parser MainViewOpts - parseCountdown = parseView & mapped . mvoWait .~ True - -pullStdin - :: IORef (Maybe (Maybe (Day, String))) -- ^ Nothing: first time; Just Nothing: failed forever. - -> Day - -> IO (Maybe String) -pullStdin inputCache d = readIORef inputCache >>= \case - Nothing -> do - out <- fmap eitherToMaybe . tryJust (guard . isEOFError) $ - evaluate . force =<< getContents - writeIORef inputCache . Just $ (d,) <$> out - pure out - Just old -> pure . firstJust (\(d',o) -> o <$ guard (d == d')) $ old diff --git a/reflections-out/day01.md b/reflections-out/day01.md deleted file mode 100644 index e92401e..0000000 --- a/reflections-out/day01.md +++ /dev/null @@ -1,103 +0,0 @@ -Day 1 -=== - - - -*[all][reflections]* / *1* / *[2][day02]* / *[5][day05]* - -[reflections]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md -[day02]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections-out/day02.md -[day05]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections-out/day05.md - -[Available as an RSS Feed][rss] - -[rss]: http://feeds.feedburner.com/jle-advent-of-code-2023 - -*[Prompt][d01p]* / *[Code][d01g]* / *[Rendered][d01h]* - -[d01p]: https://adventofcode.com/2023/day/1 -[d01g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day01.hs -[d01h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day01.html - -This year's day 1 was definitely a little spicier than most! But Haskell's -stream processing works well for us, again. - -First let's get a nice utility function to make a number the first and last -seen digit: - -```haskell -firstAndLast :: [Int] -> Int -firstAndLast xs = head xs * 10 + last xs -``` - -And now we have to figure out how to extract the digits from each part. For -part 1, we need only to extract all of the `isDigit` characters: - -```haskell -extract1 :: String -> [Int] -extract1 = map digitToInt . filter isDigit -``` - -For part 2, it's a little bit trickier: we can look at each position and see if -it is a new number string: - -```haskell -extract2 :: String -> [Int] -extract2 = mapMaybe isNumberString . tails - where - isNumberString :: String -> Maybe Int - isNumberString str = listToMaybe - [ d - | (numStr, d) <- - [ ("one",1), ("two",2), ("three",3) - , ("four",4), ("five",5), ("six",6) - , ("seven",7), ("eight",8), ("nine",9) - ] - | numStr `isPrefixOf` str - ] -``` - -Note that `tails` is a function that takes `"abc"` and returns -`["abc","bc","c"]`, allowing us to look at each position in order. - -And that gives us the solutions: - -```haskell -day01 :: (String -> [Int]) -> String -> Int -day01 extractor = sum . map (sum . extractor) . lines - -day01a = day01 extract1 -day01b = day01 extract2 -``` - - - -*[Back to all reflections for 2023][reflections]* - -## Day 1 Benchmarks - -``` ->> Day 01a -benchmarking... -time 644.4 μs (634.7 μs .. 654.0 μs) - 0.992 R² (0.985 R² .. 0.997 R²) -mean 636.6 μs (615.0 μs .. 658.1 μs) -std dev 72.70 μs (60.51 μs .. 86.91 μs) -variance introduced by outliers: 80% (severely inflated) - -* parsing and formatting times excluded - ->> Day 01b -benchmarking... -time 3.106 ms (3.086 ms .. 3.145 ms) - 1.000 R² (0.999 R² .. 1.000 R²) -mean 3.094 ms (3.090 ms .. 3.105 ms) -std dev 22.59 μs (8.206 μs .. 41.38 μs) - -* parsing and formatting times excluded -``` - diff --git a/reflections-out/day02.md b/reflections-out/day02.md deleted file mode 100644 index 11e8cc8..0000000 --- a/reflections-out/day02.md +++ /dev/null @@ -1,103 +0,0 @@ -Day 2 -=== - - - -*[all][reflections]* / *[1][day01]* / *2* / *[5][day05]* - -[reflections]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md -[day01]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections-out/day01.md -[day05]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections-out/day05.md - -[Available as an RSS Feed][rss] - -[rss]: http://feeds.feedburner.com/jle-advent-of-code-2023 - -*[Prompt][d02p]* / *[Code][d02g]* / *[Rendered][d02h]* - -[d02p]: https://adventofcode.com/2023/day/2 -[d02g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day02.hs -[d02h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day02.html - -This one was definitely a bit too heavy on the parsing side heh. But maybe the -more interesting Haskell insight would be that we can use a nice data type -- -`V3` from the *linear* library (`data V3 a = V3 a a a`) to make the actual -solving logic very simple. I've always held that `V2` and `V3` are pretty much -some of the most useful non-standard data types for advent of code, and they -were pretty helpful today. They have the most convenient `Num`, `Functor`, -`Applicative`, `Foldable`, and `Traversable` instances! - -If we encode each "set" as `V3 `, then the filters and -checksums become very simple. - -For part 1, we need to see if all `V3 Int`s in a line are legal. We can -implement that as `all isLegal`: - -```hasell -isLegal :: V3 Int -> Bool -isLegal colorVec = and do - allowed <- V3 12 13 14 - amount <- colorVec - pure (amount <= allowed) -``` - -It reads a little silly, but you just have to remember that `and` just checks -if every item is true, and here the `do` notation semantics are to compare each -`allowed` and `amount` point-wise, first the red-by-red, then the -green-by-green, and then the blue-by-blue. - -Part 2 is a little simpler, we just need to aggregate the max per-color, and -then find the product: - -```haskell -calcPower = product . foldr (liftA2 max) 0 -``` - -where `liftA2 max (V3 x y z) (V3 a b c) = V3 (max x a) (max y b) (max z c)`, -and `0` for `V3` is `V3 0 0 0`. And `product` works like how you'd think. - -Going back to parsing, one neat way we can leverage `V3` is with its `Functor` -instance: - -``` --- [("red", 1), ("blue", 2), ("green", 6)] => V3 1 6 2 -pairUp :: [(String, Int)] -> V3 Int -pairUp pairs = do - color <- V3 "red" "green" "blue" - pure $ fromMaybe 0 (lookup color pairs) -``` - -This performs an action per-color and fills in the spot with the result of -`lookup`, with `0` if the lookup fails. - - -*[Back to all reflections for 2023][reflections]* - -## Day 2 Benchmarks - -``` ->> Day 02a -benchmarking... -time 1.376 μs (1.373 μs .. 1.380 μs) - 0.999 R² (0.998 R² .. 1.000 R²) -mean 1.388 μs (1.376 μs .. 1.435 μs) -std dev 75.81 ns (6.071 ns .. 160.5 ns) -variance introduced by outliers: 69% (severely inflated) - -* parsing and formatting times excluded - ->> Day 02b -benchmarking... -time 3.878 μs (3.678 μs .. 4.054 μs) - 0.992 R² (0.988 R² .. 0.997 R²) -mean 3.808 μs (3.761 μs .. 3.861 μs) -std dev 173.2 ns (112.0 ns .. 256.3 ns) -variance introduced by outliers: 58% (severely inflated) - -* parsing and formatting times excluded -``` - diff --git a/reflections-out/day05.md b/reflections-out/day05.md deleted file mode 100644 index d4b910d..0000000 --- a/reflections-out/day05.md +++ /dev/null @@ -1,135 +0,0 @@ -Day 5 -=== - - - -*[all][reflections]* / *[1][day01]* / *[2][day02]* / *5* - -[reflections]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections.md -[day01]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections-out/day01.md -[day02]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections-out/day02.md - -[Available as an RSS Feed][rss] - -[rss]: http://feeds.feedburner.com/jle-advent-of-code-2023 - -*[Prompt][d05p]* / *[Code][d05g]* / *[Rendered][d05h]* - -[d05p]: https://adventofcode.com/2023/day/5 -[d05g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day05.hs -[d05h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day05.html - -Another year and another puzzle where I feel like using Haskell's -[data-interval][] is cheating :) It lets us work with ranged intervals and -gives us types like `IntervalSet` (a set of intervals) and `IntervalMap` -(mapping intervals to values), but with the correct implementations of map -intersection, set intersection, etc. that is aware of how intervals work. We -can generate a single `Interval`: - -[data-interval]: https://hackage.haskell.org/package/data-interval - -```haskell -import Data.Interval (Interval) -import qualified Data.Interval as IV - -fromRange :: Int -> Int -> Interval Int -fromRange x len = IV.Finite x IV.<=..< IV.Finite (x + len) -``` - -The main type we will be using here is the `IntervalMap` type. We will use it -to map intervals to deltas: if you fall inside the interval, your value will -update by `+ delta`. - -```haskell -import Data.IntervalMap.Strict (IntervalMap) -import qualified Data.IntervalMap.Strict as IVM - --- | Takes in the (a, b, c) triples that the problem gives map chunks -buildMap :: [(Int, Int, Int)] -> IntervalMap Int Int -buildMap = IVM.fromList - . map (\(dest, src, len) -> fromRange src len, dest - src) -``` - -Now we can run it on a single point: - -```haskell -convertSingle :: Int -> IntervalMap Int Int -> Int -convertSingle x mp = case IVM.lookup x mp of - Nothing -> x -- the value is not in any known interval - Just delta -> x + delta -- that interval has the given delta -``` - -So part 1 is simply finding the minimum after iterating `convertSingle` across -every range: - -```haskell -day05a :: [IntervalMap Int Int] -> [Int] -> Int -day05a imaps = minimum . map (\s0 -> foldl' convertSingle s0 imaps) -``` - -Part 2 is where it gets interesting. We actually want to keep track of an -`IntervalSet` -- the set of all intervals that we have seeds at. We need to -write a function `convertMany` that takes an `IntervalSet` and runs that -interval set through an `IntervalMap` appropriately. - -To do this, we find all of the "misses" (the intervals in the `IntervalSet` -that aren't mapped) and the "hits" (the intervals in the `IntervalSet` that do -exist in the map, shifted by the delta) and then recombine it: - -```haskell -import Data.IntervalSet (IntervalSet) -import qualified Data.IntervalSet as IVS - -convertMany :: IntervalMap Int Int -> IntervalSet Int -> IntervalSet Int -convertMany mp xs = misses <> hits - where - -- dummy map corresponding to putting `()` at every interval in our - -- `IntervalSet`, needed because interval map functions require maps - tempMap :: IntervalMap Int () - tempMap = IVM.fromList . map (,()) . IVS.toList $ xs - - misses = IVM.keysSet $ tempMap `IVM.difference` mp - hits = - IVS.fromList - . map (\(iv, delta) -> IV.mapMonotonic (+ delta) iv) - . IVM.toList - $ IVM.intersectionWith const mp tempMap -``` - -And run it all together with: - -```haskell -day05b :: IntervalSet Int -> [IntervalMap Int Int] -> IntervalSet Int -day05b = foldl' convertMany -``` - - -*[Back to all reflections for 2023][reflections]* - -## Day 5 Benchmarks - -``` ->> Day 05a -benchmarking... -time 17.13 μs (16.83 μs .. 17.86 μs) - 0.997 R² (0.994 R² .. 1.000 R²) -mean 16.87 μs (16.80 μs .. 17.13 μs) -std dev 421.7 ns (78.33 ns .. 880.9 ns) -variance introduced by outliers: 26% (moderately inflated) - -* parsing and formatting times excluded - ->> Day 05b -benchmarking... -time 450.8 μs (450.1 μs .. 451.6 μs) - 1.000 R² (1.000 R² .. 1.000 R²) -mean 450.3 μs (450.0 μs .. 450.6 μs) -std dev 946.6 ns (694.4 ns .. 1.270 μs) - -* parsing and formatting times excluded -``` - diff --git a/reflections.md b/reflections.md deleted file mode 100644 index fecfa58..0000000 --- a/reflections.md +++ /dev/null @@ -1,427 +0,0 @@ -Reflections -=========== - - - -*[2016][]* / *[2017][]* / *[2018][]* / *[2019][]* / *[2020][]* / *[2021][]* / *[2022][]* / *2023* - -[2016]: https://github.com/mstksg/advent-of-code-2016/blob/master/reflections.md -[2017]: https://github.com/mstksg/advent-of-code-2017/blob/master/reflections.md -[2018]: https://github.com/mstksg/advent-of-code-2018/blob/master/reflections.md -[2019]: https://github.com/mstksg/advent-of-code-2019/blob/master/reflections.md -[2020]: https://github.com/mstksg/advent-of-code-2020/blob/master/reflections.md -[2021]: https://github.com/mstksg/advent-of-code-2021/blob/master/reflections.md -[2022]: https://github.com/mstksg/advent-of-code-2022/blob/master/reflections.md - -[Available as an RSS Feed][rss] - -[rss]: http://feeds.feedburner.com/jle-advent-of-code-2023 - -Table of Contents ------------------ - -* [Day 1](#day-1) -* [Day 2](#day-2) -* [Day 3](#day-3) *(no reflection yet)* -* [Day 4](#day-4) *(no reflection yet)* -* [Day 5](#day-5) - -Day 1 ------- - - - -*[Prompt][d01p]* / *[Code][d01g]* / *[Rendered][d01h]* / *[Standalone Reflection Page][d01r]* - -[d01p]: https://adventofcode.com/2023/day/1 -[d01g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day01.hs -[d01h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day01.html -[d01r]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections-out/day01.md - -This year's day 1 was definitely a little spicier than most! But Haskell's -stream processing works well for us, again. - -First let's get a nice utility function to make a number the first and last -seen digit: - -```haskell -firstAndLast :: [Int] -> Int -firstAndLast xs = head xs * 10 + last xs -``` - -And now we have to figure out how to extract the digits from each part. For -part 1, we need only to extract all of the `isDigit` characters: - -```haskell -extract1 :: String -> [Int] -extract1 = map digitToInt . filter isDigit -``` - -For part 2, it's a little bit trickier: we can look at each position and see if -it is a new number string: - -```haskell -extract2 :: String -> [Int] -extract2 = mapMaybe isNumberString . tails - where - isNumberString :: String -> Maybe Int - isNumberString str = listToMaybe - [ d - | (numStr, d) <- - [ ("one",1), ("two",2), ("three",3) - , ("four",4), ("five",5), ("six",6) - , ("seven",7), ("eight",8), ("nine",9) - ] - | numStr `isPrefixOf` str - ] -``` - -Note that `tails` is a function that takes `"abc"` and returns -`["abc","bc","c"]`, allowing us to look at each position in order. - -And that gives us the solutions: - -```haskell -day01 :: (String -> [Int]) -> String -> Int -day01 extractor = sum . map (sum . extractor) . lines - -day01a = day01 extract1 -day01b = day01 extract2 -``` - - - -### Day 1 Benchmarks - -``` ->> Day 01a -benchmarking... -time 644.4 μs (634.7 μs .. 654.0 μs) - 0.992 R² (0.985 R² .. 0.997 R²) -mean 636.6 μs (615.0 μs .. 658.1 μs) -std dev 72.70 μs (60.51 μs .. 86.91 μs) -variance introduced by outliers: 80% (severely inflated) - -* parsing and formatting times excluded - ->> Day 01b -benchmarking... -time 3.106 ms (3.086 ms .. 3.145 ms) - 1.000 R² (0.999 R² .. 1.000 R²) -mean 3.094 ms (3.090 ms .. 3.105 ms) -std dev 22.59 μs (8.206 μs .. 41.38 μs) - -* parsing and formatting times excluded -``` - - - -Day 2 ------- - - - -*[Prompt][d02p]* / *[Code][d02g]* / *[Rendered][d02h]* / *[Standalone Reflection Page][d02r]* - -[d02p]: https://adventofcode.com/2023/day/2 -[d02g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day02.hs -[d02h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day02.html -[d02r]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections-out/day02.md - -This one was definitely a bit too heavy on the parsing side heh. But maybe the -more interesting Haskell insight would be that we can use a nice data type -- -`V3` from the *linear* library (`data V3 a = V3 a a a`) to make the actual -solving logic very simple. I've always held that `V2` and `V3` are pretty much -some of the most useful non-standard data types for advent of code, and they -were pretty helpful today. They have the most convenient `Num`, `Functor`, -`Applicative`, `Foldable`, and `Traversable` instances! - -If we encode each "set" as `V3 `, then the filters and -checksums become very simple. - -For part 1, we need to see if all `V3 Int`s in a line are legal. We can -implement that as `all isLegal`: - -```hasell -isLegal :: V3 Int -> Bool -isLegal colorVec = and do - allowed <- V3 12 13 14 - amount <- colorVec - pure (amount <= allowed) -``` - -It reads a little silly, but you just have to remember that `and` just checks -if every item is true, and here the `do` notation semantics are to compare each -`allowed` and `amount` point-wise, first the red-by-red, then the -green-by-green, and then the blue-by-blue. - -Part 2 is a little simpler, we just need to aggregate the max per-color, and -then find the product: - -```haskell -calcPower = product . foldr (liftA2 max) 0 -``` - -where `liftA2 max (V3 x y z) (V3 a b c) = V3 (max x a) (max y b) (max z c)`, -and `0` for `V3` is `V3 0 0 0`. And `product` works like how you'd think. - -Going back to parsing, one neat way we can leverage `V3` is with its `Functor` -instance: - -``` --- [("red", 1), ("blue", 2), ("green", 6)] => V3 1 6 2 -pairUp :: [(String, Int)] -> V3 Int -pairUp pairs = do - color <- V3 "red" "green" "blue" - pure $ fromMaybe 0 (lookup color pairs) -``` - -This performs an action per-color and fills in the spot with the result of -`lookup`, with `0` if the lookup fails. - - -### Day 2 Benchmarks - -``` ->> Day 02a -benchmarking... -time 1.376 μs (1.373 μs .. 1.380 μs) - 0.999 R² (0.998 R² .. 1.000 R²) -mean 1.388 μs (1.376 μs .. 1.435 μs) -std dev 75.81 ns (6.071 ns .. 160.5 ns) -variance introduced by outliers: 69% (severely inflated) - -* parsing and formatting times excluded - ->> Day 02b -benchmarking... -time 3.878 μs (3.678 μs .. 4.054 μs) - 0.992 R² (0.988 R² .. 0.997 R²) -mean 3.808 μs (3.761 μs .. 3.861 μs) -std dev 173.2 ns (112.0 ns .. 256.3 ns) -variance introduced by outliers: 58% (severely inflated) - -* parsing and formatting times excluded -``` - - - -Day 3 ------- - - - -*[Prompt][d03p]* / *[Code][d03g]* / *[Rendered][d03h]* / *[Standalone Reflection Page][d03r]* - -[d03p]: https://adventofcode.com/2023/day/3 -[d03g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day03.hs -[d03h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day03.html -[d03r]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections-out/day03.md - -*Reflection not yet written -- please check back later!* - -### Day 3 Benchmarks - -``` ->> Day 03a -benchmarking... -time 10.59 ms (10.40 ms .. 10.83 ms) - 0.996 R² (0.991 R² .. 1.000 R²) -mean 10.49 ms (10.43 ms .. 10.73 ms) -std dev 291.4 μs (112.4 μs .. 559.0 μs) - -* parsing and formatting times excluded - ->> Day 03b -benchmarking... -time 7.405 ms (7.385 ms .. 7.423 ms) - 1.000 R² (1.000 R² .. 1.000 R²) -mean 7.420 ms (7.412 ms .. 7.432 ms) -std dev 26.68 μs (18.95 μs .. 40.49 μs) - -* parsing and formatting times excluded -``` - - - -Day 4 ------- - - - -*[Prompt][d04p]* / *[Code][d04g]* / *[Rendered][d04h]* / *[Standalone Reflection Page][d04r]* - -[d04p]: https://adventofcode.com/2023/day/4 -[d04g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day04.hs -[d04h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day04.html -[d04r]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections-out/day04.md - -*Reflection not yet written -- please check back later!* - -### Day 4 Benchmarks - -``` ->> Day 04a -benchmarking... -time 170.3 μs (169.9 μs .. 170.6 μs) - 1.000 R² (1.000 R² .. 1.000 R²) -mean 169.8 μs (169.7 μs .. 170.0 μs) -std dev 485.7 ns (334.9 ns .. 807.0 ns) - -* parsing and formatting times excluded - ->> Day 04b -benchmarking... -time 315.3 μs (314.7 μs .. 316.1 μs) - 1.000 R² (1.000 R² .. 1.000 R²) -mean 314.8 μs (314.4 μs .. 315.2 μs) -std dev 1.252 μs (781.1 ns .. 1.957 μs) - -* parsing and formatting times excluded -``` - - - -Day 5 ------- - - - -*[Prompt][d05p]* / *[Code][d05g]* / *[Rendered][d05h]* / *[Standalone Reflection Page][d05r]* - -[d05p]: https://adventofcode.com/2023/day/5 -[d05g]: https://github.com/mstksg/advent-of-code-2023/blob/master/src/AOC/Challenge/Day05.hs -[d05h]: https://mstksg.github.io/advent-of-code-2023/src/AOC.Challenge.Day05.html -[d05r]: https://github.com/mstksg/advent-of-code-2023/blob/master/reflections-out/day05.md - -Another year and another puzzle where I feel like using Haskell's -[data-interval][] is cheating :) It lets us work with ranged intervals and -gives us types like `IntervalSet` (a set of intervals) and `IntervalMap` -(mapping intervals to values), but with the correct implementations of map -intersection, set intersection, etc. that is aware of how intervals work. We -can generate a single `Interval`: - -[data-interval]: https://hackage.haskell.org/package/data-interval - -```haskell -import Data.Interval (Interval) -import qualified Data.Interval as IV - -fromRange :: Int -> Int -> Interval Int -fromRange x len = IV.Finite x IV.<=..< IV.Finite (x + len) -``` - -The main type we will be using here is the `IntervalMap` type. We will use it -to map intervals to deltas: if you fall inside the interval, your value will -update by `+ delta`. - -```haskell -import Data.IntervalMap.Strict (IntervalMap) -import qualified Data.IntervalMap.Strict as IVM - --- | Takes in the (a, b, c) triples that the problem gives map chunks -buildMap :: [(Int, Int, Int)] -> IntervalMap Int Int -buildMap = IVM.fromList - . map (\(dest, src, len) -> fromRange src len, dest - src) -``` - -Now we can run it on a single point: - -```haskell -convertSingle :: Int -> IntervalMap Int Int -> Int -convertSingle x mp = case IVM.lookup x mp of - Nothing -> x -- the value is not in any known interval - Just delta -> x + delta -- that interval has the given delta -``` - -So part 1 is simply finding the minimum after iterating `convertSingle` across -every range: - -```haskell -day05a :: [IntervalMap Int Int] -> [Int] -> Int -day05a imaps = minimum . map (\s0 -> foldl' convertSingle s0 imaps) -``` - -Part 2 is where it gets interesting. We actually want to keep track of an -`IntervalSet` -- the set of all intervals that we have seeds at. We need to -write a function `convertMany` that takes an `IntervalSet` and runs that -interval set through an `IntervalMap` appropriately. - -To do this, we find all of the "misses" (the intervals in the `IntervalSet` -that aren't mapped) and the "hits" (the intervals in the `IntervalSet` that do -exist in the map, shifted by the delta) and then recombine it: - -```haskell -import Data.IntervalSet (IntervalSet) -import qualified Data.IntervalSet as IVS - -convertMany :: IntervalMap Int Int -> IntervalSet Int -> IntervalSet Int -convertMany mp xs = misses <> hits - where - -- dummy map corresponding to putting `()` at every interval in our - -- `IntervalSet`, needed because interval map functions require maps - tempMap :: IntervalMap Int () - tempMap = IVM.fromList . map (,()) . IVS.toList $ xs - - misses = IVM.keysSet $ tempMap `IVM.difference` mp - hits = - IVS.fromList - . map (\(iv, delta) -> IV.mapMonotonic (+ delta) iv) - . IVM.toList - $ IVM.intersectionWith const mp tempMap -``` - -And run it all together with: - -```haskell -day05b :: IntervalSet Int -> [IntervalMap Int Int] -> IntervalSet Int -day05b = foldl' convertMany -``` - - -### Day 5 Benchmarks - -``` ->> Day 05a -benchmarking... -time 17.13 μs (16.83 μs .. 17.86 μs) - 0.997 R² (0.994 R² .. 1.000 R²) -mean 16.87 μs (16.80 μs .. 17.13 μs) -std dev 421.7 ns (78.33 ns .. 880.9 ns) -variance introduced by outliers: 26% (moderately inflated) - -* parsing and formatting times excluded - ->> Day 05b -benchmarking... -time 450.8 μs (450.1 μs .. 451.6 μs) - 1.000 R² (1.000 R² .. 1.000 R²) -mean 450.3 μs (450.0 μs .. 450.6 μs) -std dev 946.6 ns (694.4 ns .. 1.270 μs) - -* parsing and formatting times excluded -``` - diff --git a/reflections/day01.md b/reflections/day01.md deleted file mode 100644 index bb7c6ef..0000000 --- a/reflections/day01.md +++ /dev/null @@ -1,51 +0,0 @@ -This year's day 1 was definitely a little spicier than most! But Haskell's -stream processing works well for us, again. - -First let's get a nice utility function to make a number the first and last -seen digit: - -```haskell -firstAndLast :: [Int] -> Int -firstAndLast xs = head xs * 10 + last xs -``` - -And now we have to figure out how to extract the digits from each part. For -part 1, we need only to extract all of the `isDigit` characters: - -```haskell -extract1 :: String -> [Int] -extract1 = map digitToInt . filter isDigit -``` - -For part 2, it's a little bit trickier: we can look at each position and see if -it is a new number string: - -```haskell -extract2 :: String -> [Int] -extract2 = mapMaybe isNumberString . tails - where - isNumberString :: String -> Maybe Int - isNumberString str = listToMaybe - [ d - | (numStr, d) <- - [ ("one",1), ("two",2), ("three",3) - , ("four",4), ("five",5), ("six",6) - , ("seven",7), ("eight",8), ("nine",9) - ] - | numStr `isPrefixOf` str - ] -``` - -Note that `tails` is a function that takes `"abc"` and returns -`["abc","bc","c"]`, allowing us to look at each position in order. - -And that gives us the solutions: - -```haskell -day01 :: (String -> [Int]) -> String -> Int -day01 extractor = sum . map (sum . extractor) . lines - -day01a = day01 extract1 -day01b = day01 extract2 -``` - diff --git a/reflections/day02.md b/reflections/day02.md deleted file mode 100644 index 731174d..0000000 --- a/reflections/day02.md +++ /dev/null @@ -1,50 +0,0 @@ -This one was definitely a bit too heavy on the parsing side heh. But maybe the -more interesting Haskell insight would be that we can use a nice data type -- -`V3` from the *linear* library (`data V3 a = V3 a a a`) to make the actual -solving logic very simple. I've always held that `V2` and `V3` are pretty much -some of the most useful non-standard data types for advent of code, and they -were pretty helpful today. They have the most convenient `Num`, `Functor`, -`Applicative`, `Foldable`, and `Traversable` instances! - -If we encode each "set" as `V3 `, then the filters and -checksums become very simple. - -For part 1, we need to see if all `V3 Int`s in a line are legal. We can -implement that as `all isLegal`: - -```hasell -isLegal :: V3 Int -> Bool -isLegal colorVec = and do - allowed <- V3 12 13 14 - amount <- colorVec - pure (amount <= allowed) -``` - -It reads a little silly, but you just have to remember that `and` just checks -if every item is true, and here the `do` notation semantics are to compare each -`allowed` and `amount` point-wise, first the red-by-red, then the -green-by-green, and then the blue-by-blue. - -Part 2 is a little simpler, we just need to aggregate the max per-color, and -then find the product: - -```haskell -calcPower = product . foldr (liftA2 max) 0 -``` - -where `liftA2 max (V3 x y z) (V3 a b c) = V3 (max x a) (max y b) (max z c)`, -and `0` for `V3` is `V3 0 0 0`. And `product` works like how you'd think. - -Going back to parsing, one neat way we can leverage `V3` is with its `Functor` -instance: - -``` --- [("red", 1), ("blue", 2), ("green", 6)] => V3 1 6 2 -pairUp :: [(String, Int)] -> V3 Int -pairUp pairs = do - color <- V3 "red" "green" "blue" - pure $ fromMaybe 0 (lookup color pairs) -``` - -This performs an action per-color and fills in the spot with the result of -`lookup`, with `0` if the lookup fails. diff --git a/reflections/day05.md b/reflections/day05.md deleted file mode 100644 index ae72e1e..0000000 --- a/reflections/day05.md +++ /dev/null @@ -1,83 +0,0 @@ -Another year and another puzzle where I feel like using Haskell's -[data-interval][] is cheating :) It lets us work with ranged intervals and -gives us types like `IntervalSet` (a set of intervals) and `IntervalMap` -(mapping intervals to values), but with the correct implementations of map -intersection, set intersection, etc. that is aware of how intervals work. We -can generate a single `Interval`: - -[data-interval]: https://hackage.haskell.org/package/data-interval - -```haskell -import Data.Interval (Interval) -import qualified Data.Interval as IV - -fromRange :: Int -> Int -> Interval Int -fromRange x len = IV.Finite x IV.<=..< IV.Finite (x + len) -``` - -The main type we will be using here is the `IntervalMap` type. We will use it -to map intervals to deltas: if you fall inside the interval, your value will -update by `+ delta`. - -```haskell -import Data.IntervalMap.Strict (IntervalMap) -import qualified Data.IntervalMap.Strict as IVM - --- | Takes in the (a, b, c) triples that the problem gives map chunks -buildMap :: [(Int, Int, Int)] -> IntervalMap Int Int -buildMap = IVM.fromList - . map (\(dest, src, len) -> fromRange src len, dest - src) -``` - -Now we can run it on a single point: - -```haskell -convertSingle :: Int -> IntervalMap Int Int -> Int -convertSingle x mp = case IVM.lookup x mp of - Nothing -> x -- the value is not in any known interval - Just delta -> x + delta -- that interval has the given delta -``` - -So part 1 is simply finding the minimum after iterating `convertSingle` across -every range: - -```haskell -day05a :: [IntervalMap Int Int] -> [Int] -> Int -day05a imaps = minimum . map (\s0 -> foldl' convertSingle s0 imaps) -``` - -Part 2 is where it gets interesting. We actually want to keep track of an -`IntervalSet` -- the set of all intervals that we have seeds at. We need to -write a function `convertMany` that takes an `IntervalSet` and runs that -interval set through an `IntervalMap` appropriately. - -To do this, we find all of the "misses" (the intervals in the `IntervalSet` -that aren't mapped) and the "hits" (the intervals in the `IntervalSet` that do -exist in the map, shifted by the delta) and then recombine it: - -```haskell -import Data.IntervalSet (IntervalSet) -import qualified Data.IntervalSet as IVS - -convertMany :: IntervalMap Int Int -> IntervalSet Int -> IntervalSet Int -convertMany mp xs = misses <> hits - where - -- dummy map corresponding to putting `()` at every interval in our - -- `IntervalSet`, needed because interval map functions require maps - tempMap :: IntervalMap Int () - tempMap = IVM.fromList . map (,()) . IVS.toList $ xs - - misses = IVM.keysSet $ tempMap `IVM.difference` mp - hits = - IVS.fromList - . map (\(iv, delta) -> IV.mapMonotonic (+ delta) iv) - . IVM.toList - $ IVM.intersectionWith const mp tempMap -``` - -And run it all together with: - -```haskell -day05b :: IntervalSet Int -> [IntervalMap Int Int] -> IntervalSet Int -day05b = foldl' convertMany -```