├── .gitattributes ├── .gitignore ├── NodeTemplate.elm ├── NodeTemplate.js ├── README.md ├── Template.elm ├── Template.roc ├── elm.json ├── gleam.toml ├── manifest.toml ├── node_run.sh ├── package.json ├── run.sh ├── src ├── Advent.elm ├── Grid.elm ├── Grid │ └── Zipper.elm ├── Year2015 │ ├── Day01.elm │ ├── Day02.elm │ ├── Day03.elm │ ├── Day04.elm │ ├── Day05.elm │ ├── Day06.elm │ ├── Day07.elm │ ├── Day08.elm │ ├── Day09.elm │ ├── Day14.elm │ ├── Day15.elm │ ├── Day16.elm │ ├── Day17.elm │ ├── Day18.elm │ ├── Day19.clj │ ├── Day19.elm │ ├── Day20.elm │ ├── Day21.elm │ ├── Day22.hs │ ├── Day23.hs │ ├── Day24.pdf │ └── Day25.hs ├── Year2016 │ ├── .keep │ ├── Day01.elm │ ├── Day02.elm │ ├── Day03.elm │ ├── Day04.elm │ ├── Day05.elm │ ├── Day05.js │ ├── Day06.elm │ ├── Day07.elm │ ├── Day09.elm │ ├── Day10.fs │ ├── Day12.factor │ ├── Day19.elm │ ├── Day20.elm │ ├── Day21.elm │ ├── Day23.hs │ └── Day24.hs ├── Year2017 │ ├── .keep │ ├── Day01.elm │ ├── Day02.elm │ ├── Day03.elm │ ├── Day04.elm │ ├── Day05.elm │ ├── Day06.elm │ ├── Day07.elm │ ├── Day08.elm │ ├── Day09.elm │ ├── Day10.elm │ ├── Day11.elm │ ├── Day12.elm │ ├── Day13.elm │ ├── Day14.elm │ ├── Day15.elm │ ├── Day16.elm │ ├── Day17.elm │ ├── Day18.elm │ ├── Day19.elm │ ├── Day20.elm │ ├── Day21.elm │ ├── Day22.elm │ ├── Day23.elm │ ├── Day23.py │ ├── Day24.elm │ └── Day25.elm ├── Year2018 │ ├── Day01.elm │ ├── Day02.elm │ ├── Day03.elm │ ├── Day04.elm │ ├── Day05.elm │ ├── Day06.elm │ ├── Day07.elm │ ├── Day08.elm │ ├── Day09.elm │ ├── Day10.elm │ ├── Day11.elm │ ├── Day12.elm │ ├── Day13.elm │ ├── Day14.elm │ ├── Day14.py │ └── Day15.elm ├── Year2019 │ ├── 2019-15-1-musings.png │ ├── Day01.elm │ ├── Day02.elm │ ├── Day03.elm │ ├── Day04.elm │ ├── Day05.elm │ ├── Day06.elm │ ├── Day07.elm │ ├── Day08.elm │ ├── Day09.elm │ ├── Day10.elm │ ├── Day11.elm │ ├── Day12.elm │ ├── Day13.elm │ ├── Day13Browser.elm │ ├── Day14.elm │ ├── Day15.elm │ ├── Day16.elm │ ├── Day16Part2.pdf │ ├── Day17.elm │ ├── Day18.elm │ ├── Day19.elm │ ├── Day20.elm │ ├── Day21.elm │ ├── Day22.elm │ ├── Day22Part2.py │ ├── Day23.elm │ ├── Day24.elm │ ├── Day25Browser.elm │ ├── Intcode.elm │ ├── Intcode │ │ ├── Disasm.elm │ │ ├── Disasm │ │ │ ├── Main.elm │ │ │ └── index.html │ │ ├── Memory.elm │ │ └── Parameter.elm │ └── day13.html ├── Year2020 │ ├── Day01.fs │ ├── Day02.fs │ ├── Day03.fs │ ├── Day04.fs │ ├── Day05.fs │ ├── Day06.fs │ ├── Day07.fs │ ├── Day08.fs │ ├── Day09.fs │ ├── Day10.fs │ ├── Day11.fs │ ├── Day12.fs │ ├── Day13Part1.dyalog │ ├── Day13Part2.nb │ ├── Day14.fs │ ├── Day15Part1.fs │ ├── Day15Part2.hs │ ├── Day16.fs │ ├── Day17.hs │ ├── Day18.fs │ ├── Day19Part1.fs │ ├── Day19Part2.clj │ ├── Day20.fs │ ├── Day21.elm │ ├── Day22.hs │ ├── Day23.py │ ├── Day24.py │ └── Day25.py ├── Year2021 │ ├── Day01.apl │ ├── Day02.elm │ ├── Day02Part1.apl │ ├── Day03.elm │ ├── Day03Part1.apl │ ├── Day04.apl │ ├── Day05.elm │ ├── Day06.apl │ ├── Day06.elm │ ├── Day07.apl │ ├── Day08.elm │ ├── Day09.elm │ ├── Day09Part1.apl │ ├── Day10.apl │ ├── Day11.elm │ ├── Day12.elm │ ├── Day13.elm │ ├── Day14.elm │ ├── Day15.elm │ ├── Day16.elm │ ├── Day17.elm │ ├── Day18.elm │ ├── Day20.elm │ └── Day21.elm ├── Year2022 │ ├── Day01.kt │ ├── Day02.kt │ ├── Day03.kt │ ├── Day04.kt │ ├── Day05.kt │ ├── Day06.kt │ ├── Day07.kt │ ├── Day08.kt │ ├── Day09.kt │ ├── Day10.kt │ ├── Day11.kt │ ├── Day12.kt │ ├── Day13.elm │ ├── Day14.kt │ ├── Day15.kt │ ├── Day16.kt │ ├── Day17.kt │ ├── Day18.kt │ ├── Day19.kt │ ├── Day20.kt │ ├── Day21.kt │ ├── Day22.kt │ ├── Day23.kt │ ├── Day24.kt │ └── Day25.kt ├── Year2023 │ ├── Day01.roc │ ├── Day02.roc │ ├── Day03.roc │ ├── Day04.roc │ ├── Day05.roc │ ├── Day06.roc │ ├── Day07.roc │ ├── Day08.roc │ ├── Day09.roc │ ├── Day10.roc │ ├── Day11.roc │ └── Day12.roc ├── aoc.gleam ├── aoc_2024 │ ├── day_1.gleam │ ├── day_10.gleam │ ├── day_11.gleam │ ├── day_12.gleam │ ├── day_13.gleam │ ├── day_14.gleam │ ├── day_15.gleam │ ├── day_16.gleam │ ├── day_17.gleam │ ├── day_18.gleam │ ├── day_19.gleam │ ├── day_2.gleam │ ├── day_20.gleam │ ├── day_3.gleam │ ├── day_4.gleam │ ├── day_5.gleam │ ├── day_6.gleam │ ├── day_7.gleam │ ├── day_8.gleam │ └── day_9.gleam ├── extra.gleam └── grid.gleam ├── start-roc.sh ├── start.sh ├── tests └── Year2018Day15Tests.elm ├── watch-roc.sh └── watch.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | *.roc linguist-language=Elm 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /elm-stuff 2 | /js 3 | /node_modules 4 | /yarn.lock 5 | /input*.txt 6 | 7 | # Gleam 8 | *.beam 9 | *.ez 10 | /build 11 | erl_crash.dump 12 | /input 13 | -------------------------------------------------------------------------------- /NodeTemplate.elm: -------------------------------------------------------------------------------- 1 | port module Year201X.DayXX exposing (..) 2 | 3 | import Json.Decode 4 | 5 | 6 | port elmAsks : String -> Cmd msg 7 | 8 | 9 | port jsAnswers : (String -> msg) -> Sub msg 10 | 11 | 12 | main : Program Never Model Msg 13 | main = 14 | Platform.program 15 | { init = init1 16 | , update = update1 17 | 18 | -- { init = init2 19 | -- , update = update2 20 | , subscriptions = subscriptions 21 | } 22 | 23 | 24 | type alias Model = 25 | Int 26 | 27 | 28 | type Msg 29 | = JsAnswers String 30 | 31 | 32 | init1 : ( Model, Cmd Msg ) 33 | init1 = 34 | ( initModel1, elmAsks (makeString initModel.id initModel.counter) ) 35 | 36 | 37 | initModel1 : Model 38 | initModel1 = 39 | 0 40 | 41 | 42 | init2 : ( Model, Cmd Msg ) 43 | init2 = 44 | ( initModel2, elmAsks (makeString initModel.id initModel.counter) ) 45 | 46 | 47 | initModel2 : Model 48 | initModel2 = 49 | 0 50 | 51 | 52 | update1 : Msg -> Model -> ( Model, Cmd Msg ) 53 | update1 msg model = 54 | case msg of 55 | JsAnswers hash -> 56 | ( model, Cmd.none ) 57 | 58 | 59 | toInt : String -> Int 60 | toInt string = 61 | string 62 | |> String.toInt 63 | |> Result.mapError (\_ -> Debug.crash "Wrong input!") 64 | |> Result.withDefault 0 65 | 66 | 67 | update2 : Msg -> Model -> ( Model, Cmd Msg ) 68 | update2 msg model = 69 | case msg of 70 | JsAnswers hash -> 71 | ( model, Cmd.none ) 72 | 73 | 74 | subscriptions : Model -> Sub Msg 75 | subscriptions model = 76 | jsAnswers JsAnswers 77 | 78 | 79 | prepareForPort : String -> String 80 | prepareForPort string = 81 | string 82 | -------------------------------------------------------------------------------- /NodeTemplate.js: -------------------------------------------------------------------------------- 1 | const someDependency = require('some-dependency'); 2 | const Elm = require('../js/201X-XX.js'); 3 | const app = Elm.Year201X.DayXX.worker(); 4 | app.ports.elmAsks.subscribe(data => app.ports.jsAnswers.send(someDependency(data))); 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code 2 | 3 | ## Progress 4 | 5 | | Day | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 6 | | --- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | 7 | | 1 | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 8 | | 2 | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 9 | | 3 | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 10 | | 4 | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 11 | | 5 | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 12 | | 6 | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 13 | | 7 | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 14 | | 8 | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 15 | | 9 | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 16 | | 10 | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 17 | | 11 | ✔︎ | | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 18 | | 12 | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 19 | | 13 | ✔︎ | | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 20 | | 14 | ✔︎ | | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 21 | | 15 | ✔︎ | | ✔︎ | | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 22 | | 16 | ✔︎ | | ✔︎ | | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 23 | | 17 | ✔︎ | | ✔︎ | | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 24 | | 18 | ✔︎ | | ✔︎ | | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 25 | | 19 | ✔︎ | ✔︎⚠︎ | ✔︎ | | ✔︎ | ✔︎ | | ✔︎ | 26 | | 20 | ✔︎ | ✔︎⚠︎ | ✔︎ | | ✔︎ | ✔︎ | ✔︎ | ✔︎ | 27 | | 21 | ✔︎ | ✔︎ | ✔︎ | | ✔︎ | | ✔︎⚠︎ | ✔︎ | 28 | | 22 | ✔︎ | | ✔︎ | | ✔︎ | ✔︎ | | ✔︎ | 29 | | 23 | ✔︎ | ✔︎ | ✔︎ | | ✔︎ | ✔︎⚠︎ | | ✔︎ | 30 | | 24 | ✔︎ | ✔︎ | ✔︎ | | ✔︎ | ✔︎ | | ✔︎ | 31 | | 25 | ✔︎ | | ✔︎ | | ✔︎ | ✔︎ | | ✔︎ | 32 | 33 | -------- 34 | 35 | ``` 36 | $ ./start.sh 2018 01 # makes a new Elm module, copies the puzzle input into it, opens vim and watcher in two tmux panes 37 | ``` 38 | 39 | May the error messages lead you towards the right dependencies :smirk: 40 | -------------------------------------------------------------------------------- /Template.elm: -------------------------------------------------------------------------------- 1 | module YearXXX.DayXXX exposing (Input1, Input2, Output1, Output2, compute1, compute2, input_, main, parse1, parse2, tests1, tests2) 2 | 3 | import Advent 4 | exposing 5 | ( Test 6 | -- , unsafeToInt 7 | -- , unsafeMaybe 8 | ) 9 | 10 | 11 | 12 | -- 1. TYPES (what is the best representation of the problem?) 13 | 14 | 15 | type alias Input1 = 16 | Int 17 | 18 | 19 | type alias Input2 = 20 | Int 21 | 22 | 23 | type alias Output1 = 24 | Int 25 | 26 | 27 | type alias Output2 = 28 | Int 29 | 30 | 31 | 32 | -- 2. PARSE (mangle the input string into the representation we decided on) 33 | 34 | 35 | parse1 : String -> Input1 36 | parse1 string = 37 | -1 38 | 39 | 40 | parse2 : String -> Input2 41 | parse2 string = 42 | parse1 string 43 | 44 | 45 | 46 | -- 3. COMPUTE (actually solve the problem) 47 | 48 | 49 | compute1 : Input1 -> Output1 50 | compute1 input = 51 | input 52 | |> Debug.log "input" 53 | |> always -1 54 | 55 | 56 | compute2 : Input2 -> Output2 57 | compute2 input = 58 | -1 59 | 60 | 61 | 62 | -- 4. TESTS (uh-oh, is this problem a hard one?) 63 | 64 | 65 | tests1 : List (Test Input1 Output1) 66 | tests1 = 67 | [{- Test "example" 68 | "input" 69 | Nothing -- Just "parsed-input" 70 | -1 71 | -} 72 | ] 73 | 74 | 75 | tests2 : List (Test Input2 Output2) 76 | tests2 = 77 | [] 78 | 79 | 80 | 81 | -- BOILERPLATE (shouldn't have to touch this) 82 | 83 | 84 | input_ : String 85 | input_ = 86 | """ 87 | InputXXX 88 | """ 89 | |> Advent.removeNewlinesAtEnds 90 | 91 | 92 | main : Program () ( Output1, Output2 ) Never 93 | main = 94 | Advent.program 95 | { input = input_ 96 | , parse1 = parse1 97 | , parse2 = parse2 98 | , compute1 = compute1 99 | , compute2 = compute2 100 | , tests1 = tests1 101 | , tests2 = tests2 102 | } 103 | -------------------------------------------------------------------------------- /Template.roc: -------------------------------------------------------------------------------- 1 | app "hello" 2 | packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" } 3 | imports [ 4 | pf.Stdout, 5 | "../../inputXXX.txt" as realInput : Str 6 | ] 7 | provides [main] to pf 8 | 9 | 10 | main = part1 realInput 11 | #main = part2 realInput 12 | #main = part1 testInput 13 | #main = part2 testInput 14 | 15 | part1 = \input -> 16 | input 17 | |> Stdout.line 18 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "Janiczek/elm-bidict": "3.1.0", 10 | "Janiczek/elm-graph": "2.0.1", 11 | "Janiczek/elm-list-cartesian": "1.0.2", 12 | "avh4/elm-fifo": "1.0.4", 13 | "bitsoflogic/elm-radixint": "2.0.0", 14 | "cmditch/elm-bigint": "2.0.1", 15 | "elm/browser": "1.0.2", 16 | "elm/core": "1.0.5", 17 | "elm/html": "1.0.0", 18 | "elm/json": "1.1.3", 19 | "elm/parser": "1.1.0", 20 | "elm-community/array-extra": "2.4.0", 21 | "elm-community/dict-extra": "2.4.0", 22 | "elm-community/html-extra": "3.4.0", 23 | "elm-community/list-extra": "8.5.1", 24 | "elm-community/maybe-extra": "5.2.1", 25 | "elm-community/result-extra": "2.4.0", 26 | "elm-explorations/benchmark": "1.0.2", 27 | "fifth-postulate/priority-queue": "1.0.0", 28 | "krisajenkins/elm-astar": "2.1.3", 29 | "lovasoa/elm-rolling-list": "1.1.4", 30 | "lynn/elm-arithmetic": "3.0.0", 31 | "mhoare/elm-stack": "3.1.2", 32 | "owanturist/elm-union-find": "1.0.0", 33 | "pzp1997/assoc-list": "1.0.0", 34 | "rtfeldman/elm-sorter-experiment": "2.1.1", 35 | "the-sett/lazy-list": "1.1.2", 36 | "truqu/elm-md5": "1.1.0", 37 | "turboMaCk/any-set": "1.5.0", 38 | "wernerdegroot/listzipper": "4.0.0" 39 | }, 40 | "indirect": { 41 | "BrianHicks/elm-trend": "2.1.3", 42 | "elm/regex": "1.0.0", 43 | "elm/time": "1.0.0", 44 | "elm/url": "1.0.0", 45 | "elm/virtual-dom": "1.0.2", 46 | "erlandsona/assoc-set": "1.1.0", 47 | "mdgriffith/style-elements": "5.0.2", 48 | "robinheghan/murmur3": "1.0.0", 49 | "rtfeldman/elm-hex": "1.0.0", 50 | "skyqrose/assoc-list-extra": "1.0.0", 51 | "turboMaCk/any-dict": "2.6.0", 52 | "zwilias/elm-utf-tools": "2.0.1" 53 | } 54 | }, 55 | "test-dependencies": { 56 | "direct": { 57 | "elm-explorations/test": "1.2.2" 58 | }, 59 | "indirect": { 60 | "elm/random": "1.0.0" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /gleam.toml: -------------------------------------------------------------------------------- 1 | name = "aoc" 2 | version = "1.0.0" 3 | 4 | # Fill out these fields if you intend to generate HTML documentation or publish 5 | # your project to the Hex package manager. 6 | # 7 | # description = "" 8 | # licences = ["Apache-2.0"] 9 | # repository = { type = "github", user = "", repo = "" } 10 | # links = [{ title = "Website", href = "" }] 11 | # 12 | # For a full reference of all the available options, you can have a look at 13 | # https://gleam.run/writing-gleam/gleam-toml/. 14 | 15 | [dependencies] 16 | gleam_stdlib = ">= 0.34.0 and < 2.0.0" 17 | gladvent = ">= 2.0.2 and < 3.0.0" 18 | gleam_otp = ">= 0.14.1 and < 1.0.0" 19 | gleam_regexp = ">= 1.0.0 and < 2.0.0" 20 | gleamy_structures = ">= 1.0.0 and < 2.0.0" 21 | gleam_deque = ">= 1.0.0 and < 2.0.0" 22 | rememo = ">= 3.1.0 and < 4.0.0" 23 | pocket_watch = ">= 1.0.0 and < 2.0.0" 24 | gleam_yielder = ">= 1.1.0 and < 2.0.0" 25 | 26 | [dev-dependencies] 27 | gleeunit = ">= 1.0.0 and < 2.0.0" 28 | -------------------------------------------------------------------------------- /node_run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | INPUT="Year${1}/Day${2}.elm" 4 | OUTPUT="js/Year${1}-Day${2}.js" 5 | NODE="Year${1}/Day${2}.js" 6 | 7 | clear; 8 | elm-make --yes --warn "${INPUT}" --output "${OUTPUT}"; 9 | echo "--------------------------------------"; 10 | node "${NODE}"; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "md5-jkmyers": "^0.0.1" 4 | }, 5 | "devDependencies": { 6 | "elm": "^0.19.0-bugfix2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | YEAR="${1}" 4 | DAY="${2}" 5 | INPUT="src/Year${YEAR}/Day${DAY}.elm" 6 | OUTPUT="js/${YEAR}-${DAY}.js" 7 | MODULE="Year${YEAR}.Day${DAY}" 8 | 9 | 10 | 11 | clear; 12 | tput reset; 13 | echo -en "\033c\033[3J"; 14 | elm make "${INPUT}" --output "${OUTPUT}" && \ 15 | echo "let oldLog=console.log;console.log=function(...args){let x=args[0];if(x.startsWith('[START]')){console.time('time');}else if(x.startsWith('[LOG]')){console.timeLog('time');}else if(x.startsWith('[END]')){console.timeEnd('time');}else{oldLog(...args);}}" >>"${OUTPUT}" && \ 16 | echo "this.Elm.${MODULE}.init();" >>"${OUTPUT}" && \ 17 | echo "--------------------------------------" && \ 18 | node --max-old-space-size=12288 "${OUTPUT}"; 19 | #echo "try{this.Elm.${MODULE}.init();}catch(e){console.log(e.message);}" >>"${OUTPUT}" && \ 20 | -------------------------------------------------------------------------------- /src/Grid/Zipper.elm: -------------------------------------------------------------------------------- 1 | module Grid.Zipper exposing 2 | ( Zipper 3 | , current 4 | , currentFocus 5 | , currentOrDefault 6 | , doNTimes 7 | , doUntil 8 | , doWhile 9 | , duplicate 10 | , extend 11 | , extract 12 | , filter 13 | , fromGrid 14 | , fromGridAtOrigin 15 | , goDown 16 | , goLeft 17 | , goRight 18 | , goTo 19 | , goUp 20 | , gridFn 21 | , map 22 | , mapFocus 23 | , set 24 | , toGrid 25 | , withCurrentOrDefault 26 | ) 27 | 28 | import Grid exposing (Grid) 29 | 30 | 31 | type Zipper a 32 | = Zipper 33 | { grid : Grid a 34 | , focus : ( Int, Int ) 35 | } 36 | 37 | 38 | fromGrid : ( Int, Int ) -> Grid a -> Zipper a 39 | fromGrid focus grid = 40 | Zipper 41 | { grid = grid 42 | , focus = focus 43 | } 44 | 45 | 46 | fromGridAtOrigin : Grid a -> Zipper a 47 | fromGridAtOrigin = 48 | fromGrid ( 0, 0 ) 49 | 50 | 51 | current : Zipper a -> Maybe a 52 | current (Zipper z) = 53 | Grid.at z.focus z.grid 54 | 55 | 56 | currentOrDefault : Zipper a -> a 57 | currentOrDefault = 58 | extract 59 | 60 | 61 | currentFocus : Zipper a -> ( Int, Int ) 62 | currentFocus (Zipper z) = 63 | z.focus 64 | 65 | 66 | toGrid : Zipper a -> Grid a 67 | toGrid (Zipper z) = 68 | z.grid 69 | 70 | 71 | extract : Zipper a -> a 72 | extract (Zipper z) = 73 | Grid.atOrDefault z.focus z.grid 74 | 75 | 76 | duplicate : Zipper a -> Zipper (Zipper a) 77 | duplicate (Zipper z) = 78 | Zipper 79 | { grid = Grid.map (\pos a -> fromGrid pos z.grid) z.grid 80 | , focus = z.focus 81 | } 82 | 83 | 84 | extend : (Zipper a -> b) -> Zipper a -> Zipper b 85 | extend fn = 86 | map (\_ zipper -> fn zipper) << duplicate 87 | 88 | 89 | map : (( Int, Int ) -> a -> b) -> Zipper a -> Zipper b 90 | map fn (Zipper z) = 91 | Zipper 92 | { grid = Grid.map fn z.grid 93 | , focus = z.focus 94 | } 95 | 96 | 97 | goTo : ( Int, Int ) -> Zipper a -> Zipper a 98 | goTo focus (Zipper z) = 99 | Zipper { z | focus = focus } 100 | 101 | 102 | mapFocus : (( Int, Int ) -> ( Int, Int )) -> Zipper a -> Zipper a 103 | mapFocus fn (Zipper z) = 104 | Zipper { z | focus = fn z.focus } 105 | 106 | 107 | goLeft : Zipper a -> Zipper a 108 | goLeft = 109 | mapFocus (Grid.addPosition ( -1, 0 )) 110 | 111 | 112 | goRight : Zipper a -> Zipper a 113 | goRight = 114 | mapFocus (Grid.addPosition ( 1, 0 )) 115 | 116 | 117 | goUp : Zipper a -> Zipper a 118 | goUp = 119 | mapFocus (Grid.addPosition ( 0, -1 )) 120 | 121 | 122 | goDown : Zipper a -> Zipper a 123 | goDown = 124 | mapFocus (Grid.addPosition ( 0, 1 )) 125 | 126 | 127 | doNTimes : Int -> (Zipper a -> Zipper a) -> Zipper a -> Zipper a 128 | doNTimes n fn zipper = 129 | if n <= 0 then 130 | zipper 131 | 132 | else 133 | doNTimes (n - 1) fn (fn zipper) 134 | 135 | 136 | doWhile : (Zipper a -> Bool) -> (Zipper a -> Zipper a) -> Zipper a -> Zipper a 137 | doWhile pred fn zipper = 138 | if pred zipper then 139 | doWhile pred fn (fn zipper) 140 | 141 | else 142 | zipper 143 | 144 | 145 | doUntil : (Zipper a -> Bool) -> (Zipper a -> Zipper a) -> Zipper a -> Zipper a 146 | doUntil pred fn zipper = 147 | doWhile (not << pred) fn zipper 148 | 149 | 150 | set : a -> Zipper a -> Zipper a 151 | set val (Zipper z) = 152 | Zipper { z | grid = Grid.set z.focus val z.grid } 153 | 154 | 155 | withCurrent : (Zipper a -> b) -> Zipper a -> ( Maybe a, b ) 156 | withCurrent fn zipper = 157 | ( current zipper 158 | , fn zipper 159 | ) 160 | 161 | 162 | withCurrentOrDefault : (Zipper a -> b) -> Zipper a -> ( a, b ) 163 | withCurrentOrDefault fn zipper = 164 | ( currentOrDefault zipper 165 | , fn zipper 166 | ) 167 | 168 | 169 | filter : (( Int, Int ) -> a -> Bool) -> Zipper a -> Zipper a 170 | filter fn (Zipper z) = 171 | Zipper { z | grid = Grid.filter fn z.grid } 172 | 173 | 174 | gridFn : (( Int, Int ) -> Grid a -> b) -> Zipper a -> b 175 | gridFn fn (Zipper z) = 176 | fn z.focus z.grid 177 | -------------------------------------------------------------------------------- /src/Year2015/Day04.elm: -------------------------------------------------------------------------------- 1 | module Year2015.Day04 exposing (Input1, Input2, Output1, Output2, compute1, compute2, input_, main, parse1, parse2, tests1, tests2) 2 | 3 | import Advent 4 | exposing 5 | ( Test 6 | -- , unsafeToInt 7 | -- , unsafeMaybe 8 | ) 9 | import MD5 10 | 11 | 12 | 13 | -- 1. TYPES (what is the best representation of the problem?) 14 | 15 | 16 | type alias Input1 = 17 | String 18 | 19 | 20 | type alias Input2 = 21 | String 22 | 23 | 24 | type alias Output1 = 25 | Int 26 | 27 | 28 | type alias Output2 = 29 | Int 30 | 31 | 32 | 33 | -- 2. PARSE (mangle the input string into the representation we decided on) 34 | 35 | 36 | parse1 : String -> Input1 37 | parse1 string = 38 | string 39 | 40 | 41 | parse2 : String -> Input2 42 | parse2 string = 43 | parse1 string 44 | 45 | 46 | 47 | -- 3. COMPUTE (actually solve the problem) 48 | 49 | 50 | compute1 : Input1 -> Output1 51 | compute1 input = 52 | let 53 | search : Int -> String -> Int 54 | search suffix string = 55 | if String.left 5 (MD5.hex (string ++ String.fromInt suffix)) == "00000" then 56 | suffix 57 | 58 | else 59 | search (suffix + 1) string 60 | in 61 | search 1 input 62 | 63 | 64 | compute2 : Input2 -> Output2 65 | compute2 input = 66 | let 67 | search : Int -> String -> Int 68 | search suffix string = 69 | if 70 | String.left 6 71 | (MD5.hex 72 | (string 73 | ++ String.fromInt 74 | (if (suffix |> modBy 100000) == 0 then 75 | Debug.log "i" suffix 76 | 77 | else 78 | suffix 79 | ) 80 | ) 81 | ) 82 | == "000000" 83 | then 84 | suffix 85 | 86 | else 87 | search (suffix + 1) string 88 | in 89 | search 1 input 90 | 91 | 92 | 93 | -- 4. TESTS (uh-oh, is this problem a hard one?) 94 | 95 | 96 | tests1 : List (Test Input1 Output1) 97 | tests1 = 98 | [{- Test "example 1" 99 | "abcdef" 100 | "abcdef" 101 | 609043 102 | -} 103 | ] 104 | 105 | 106 | tests2 : List (Test Input2 Output2) 107 | tests2 = 108 | [] 109 | 110 | 111 | 112 | -- BOILERPLATE (shouldn't have to touch this) 113 | 114 | 115 | input_ : String 116 | input_ = 117 | """ 118 | yzbqklnj 119 | """ 120 | |> Advent.removeNewlinesAtEnds 121 | 122 | 123 | main : Program () ( Output1, Output2 ) Never 124 | main = 125 | Advent.program 126 | { input = input_ 127 | , parse1 = parse1 128 | , parse2 = parse2 129 | , compute1 = compute1 130 | , compute2 = compute2 131 | , tests1 = tests1 132 | , tests2 = tests2 133 | } 134 | -------------------------------------------------------------------------------- /src/Year2015/Day17.elm: -------------------------------------------------------------------------------- 1 | module Year2015.Day17 exposing (..) 2 | 3 | import Advent exposing (Test) 4 | import List.Extra 5 | 6 | 7 | main : Program Never ( Output, Output ) Never 8 | main = 9 | Advent.program 10 | { input = input 11 | , parse1 = parse 12 | , parse2 = parse 13 | , compute1 = compute1 14 | , compute2 = compute2 15 | , tests1 = tests1 16 | , tests2 = tests2 17 | } 18 | 19 | 20 | type alias Input = 21 | ( Int, List Container ) 22 | 23 | 24 | type alias Output = 25 | Int 26 | 27 | 28 | parse : String -> Input 29 | parse input = 30 | case String.lines input of 31 | liters :: rest -> 32 | ( Advent.toInt liters 33 | , List.indexedMap (\i str -> ( i, Advent.toInt str )) rest 34 | ) 35 | 36 | _ -> 37 | Debug.crash "wrong input!" 38 | 39 | 40 | type alias Container = 41 | ( Index, Size ) 42 | 43 | 44 | type alias Index = 45 | Int 46 | 47 | 48 | type alias Size = 49 | Int 50 | 51 | 52 | compute1 : Input -> Output 53 | compute1 ( liters, containers ) = 54 | containers 55 | |> List.Extra.subsequences 56 | |> List.filter (\combination -> sum combination == liters) 57 | |> List.length 58 | 59 | 60 | sum : List Container -> Int 61 | sum containers = 62 | containers 63 | |> List.map Tuple.second 64 | |> List.sum 65 | 66 | 67 | compute2 : Input -> Output 68 | compute2 ( liters, containers ) = 69 | let 70 | goodWays = 71 | containers 72 | |> List.Extra.subsequences 73 | |> List.filter (\combination -> sum combination == liters) 74 | 75 | lengthOfMinimal = 76 | goodWays 77 | |> List.sortBy List.length 78 | |> List.head 79 | |> Advent.unsafeMaybe 80 | |> List.length 81 | 82 | minimalWays = 83 | goodWays 84 | |> List.filter (\way -> List.length way == lengthOfMinimal) 85 | in 86 | minimalWays 87 | |> List.length 88 | 89 | 90 | tests1 : List (Test Input Output) 91 | tests1 = 92 | [ Test "example" 93 | """25 94 | 20 95 | 15 96 | 10 97 | 5 98 | 5""" 99 | ( 25, [ ( 0, 20 ), ( 1, 15 ), ( 2, 10 ), ( 3, 5 ), ( 4, 5 ) ] ) 100 | 4 101 | ] 102 | 103 | 104 | tests2 : List (Test Input Output) 105 | tests2 = 106 | [] 107 | 108 | 109 | input : String 110 | input = 111 | """150 112 | 33 113 | 14 114 | 18 115 | 20 116 | 45 117 | 35 118 | 16 119 | 35 120 | 1 121 | 13 122 | 18 123 | 13 124 | 50 125 | 44 126 | 48 127 | 6 128 | 24 129 | 41 130 | 30 131 | 42""" 132 | -------------------------------------------------------------------------------- /src/Year2015/Day19.clj: -------------------------------------------------------------------------------- 1 | (ns day19 2 | (:require [instaparse.core :as insta])) 3 | 4 | (def syntax " 5 | S = e 6 | 7 | = e_to_HF | e_to_NAl | e_to_OMg | <'e'> 8 | = Al_to_ThF | Al_to_ThRnFAr | <'Al'> 9 | = <'Ar'> 10 | = B_to_Ca | B_to_TiB | B_to_TiRnFAr | <'B'> 11 | = <'C'> 12 | = Ca_to_CaCa | Ca_to_PB | Ca_to_PRnFAr | Ca_to_SiRnFYFAr | Ca_to_SiRnMgAr | Ca_to_SiTh | <'Ca'> 13 | = F_to_CaF | F_to_PMg | F_to_SiAl | <'F'> 14 | = H_to_CRnAlAr | H_to_CRnFYFYFAr | H_to_CRnFYMgAr | H_to_CRnMgYFAr | H_to_HCa | H_to_NRnFYFAr | H_to_NRnMgAr | H_to_NTh | H_to_OB | H_to_ORnFAr | <'H'> 15 | = Mg_to_BF | Mg_to_TiMg | <'Mg'> 16 | = N_to_CRnFYFAr | N_to_HSi | <'N'> 17 | = O_to_CRnFYFAr | O_to_CRnMgAr | O_to_HP | O_to_NRnFAr | O_to_OTi | <'O'> 18 |

= P_to_CaP | P_to_PTi | P_to_SiRnFAr | <'P'> 19 | = <'Rn'> 20 | = Si_to_CaSi | <'Si'> 21 | = Th_to_ThCa | <'Th'> 22 | = Ti_to_BP | Ti_to_TiTi | <'Ti'> 23 | = <'Y'> 24 | 25 | e_to_HF = H F 26 | e_to_NAl = N Al 27 | e_to_OMg = O Mg 28 | Al_to_ThF = Th F 29 | Al_to_ThRnFAr = Th Rn F Ar 30 | B_to_Ca = Ca 31 | B_to_TiB = Ti B 32 | B_to_TiRnFAr = Ti Rn F Ar 33 | Ca_to_CaCa = Ca Ca 34 | Ca_to_PB = P B 35 | Ca_to_PRnFAr= P Rn F Ar 36 | Ca_to_SiRnFYFAr = Si Rn F Y F Ar 37 | Ca_to_SiRnMgAr = Si Rn Mg Ar 38 | Ca_to_SiTh = Si Th 39 | F_to_CaF = Ca F 40 | F_to_PMg = P Mg 41 | F_to_SiAl = Si Al 42 | H_to_CRnAlAr = C Rn Al Ar 43 | H_to_CRnFYFYFAr = C Rn F Y F Y F Ar 44 | H_to_CRnFYMgAr = C Rn F Y Mg Ar 45 | H_to_CRnMgYFAr = C Rn Mg Y F Ar 46 | H_to_HCa = H Ca 47 | H_to_NRnFYFAr = N Rn F Y F Ar 48 | H_to_NRnMgAr = N Rn Mg Ar 49 | H_to_NTh = N Th 50 | H_to_OB = O B 51 | H_to_ORnFAr = O Rn F Ar 52 | Mg_to_BF = B F 53 | Mg_to_TiMg = Ti Mg 54 | N_to_CRnFYFAr = C Rn F Y F Ar 55 | N_to_HSi = H Si 56 | O_to_CRnFYFAr = C Rn F Y F Ar 57 | O_to_CRnMgAr = C Rn Mg Ar 58 | O_to_HP = H P 59 | O_to_NRnFAr = N Rn F Ar 60 | O_to_OTi = O Ti 61 | P_to_CaP = Ca P 62 | P_to_PTi = P Ti 63 | P_to_SiRnFAr = Si Rn F Ar 64 | Si_to_CaSi = Ca Si 65 | Th_to_ThCa = Th Ca 66 | Ti_to_BP = B P 67 | Ti_to_TiTi = Ti Ti 68 | ") 69 | 70 | (def parser (insta/parser syntax)) 71 | 72 | (def input "ORnPBPMgArCaCaCaSiThCaCaSiThCaCaPBSiRnFArRnFArCaCaSiThCaCaSiThCaCaCaCaCaCaSiRnFYFArSiRnMgArCaSiRnPTiTiBFYPBFArSiRnCaSiRnTiRnFArSiAlArPTiBPTiRnCaSiAlArCaPTiTiBPMgYFArPTiRnFArSiRnCaCaFArRnCaFArCaSiRnSiRnMgArFYCaSiRnMgArCaCaSiThPRnFArPBCaSiRnMgArCaCaSiThCaSiRnTiMgArFArSiThSiThCaCaSiRnMgArCaCaSiRnFArTiBPTiRnCaSiAlArCaPTiRnFArPBPBCaCaSiThCaPBSiThPRnFArSiThCaSiThCaSiThCaPTiBSiRnFYFArCaCaPRnFArPBCaCaPBSiRnTiRnFArCaPRnFArSiRnCaCaCaSiThCaRnCaFArYCaSiRnFArBCaCaCaSiThFArPBFArCaSiRnFArRnCaCaCaFArSiRnFArTiRnPMgArF") 73 | 74 | (defn answer [tree] 75 | (- (count (flatten tree)) 1)) 76 | 77 | (defn -main [& args] 78 | (println 79 | (reduce (fn [smallest-answer tree] 80 | (let [current-answer (answer tree)] 81 | (println 82 | "currently minimal: " smallest-answer 83 | ", new: " current-answer) 84 | (min smallest-answer current-answer))) 85 | 9999 86 | (insta/parses parser input)))) 87 | -------------------------------------------------------------------------------- /src/Year2015/Day20.elm: -------------------------------------------------------------------------------- 1 | module Year2015.Day20 exposing (Input1, Input2, Output1, Output2, compute1, compute2, input_, main, parse1, parse2, tests1, tests2) 2 | 3 | import Advent 4 | exposing 5 | ( Test 6 | -- , unsafeToInt 7 | -- , unsafeMaybe 8 | ) 9 | import Arithmetic 10 | import List.Extra 11 | 12 | 13 | 14 | -- 1. TYPES (what is the best representation of the problem?) 15 | 16 | 17 | type alias Input1 = 18 | Int 19 | 20 | 21 | type alias Input2 = 22 | Int 23 | 24 | 25 | type alias Output1 = 26 | Int 27 | 28 | 29 | type alias Output2 = 30 | Int 31 | 32 | 33 | 34 | -- 2. PARSE (mangle the input string into the representation we decided on) 35 | 36 | 37 | parse1 : String -> Input1 38 | parse1 string = 39 | String.toInt string 40 | |> Maybe.withDefault -1 41 | 42 | 43 | parse2 : String -> Input2 44 | parse2 string = 45 | parse1 string 46 | 47 | 48 | 49 | -- 3. COMPUTE (actually solve the problem) 50 | 51 | 52 | compute1 : Input1 -> Output1 53 | compute1 input = 54 | compute1Help input 1 55 | 56 | 57 | compute1Help : Int -> Int -> Int 58 | compute1Help input n = 59 | if presentsAtHouse n >= input then 60 | n 61 | 62 | else 63 | compute1Help input (n + 1) 64 | 65 | 66 | presentsAtHouse : Int -> Int 67 | presentsAtHouse n = 68 | Arithmetic.divisors n 69 | |> List.sum 70 | |> (*) 10 71 | 72 | 73 | compute2 : Input2 -> Output2 74 | compute2 input = 75 | compute2Help input 1 76 | 77 | 78 | compute2Help : Int -> Int -> Int 79 | compute2Help input n = 80 | if presentsAtHouse2 n >= input then 81 | n 82 | 83 | else 84 | compute2Help input (n + 1) 85 | 86 | 87 | divisors2 : Int -> List Int 88 | divisors2 n = 89 | let 90 | f ( p, e ) = 91 | List.concatMap (\a -> List.map (\x -> p ^ x * a) (List.range 0 e)) 92 | in 93 | Arithmetic.primeExponents n 94 | |> List.foldr f [ 1 ] 95 | |> List.sort 96 | |> List.Extra.dropWhile (\d -> d * 50 < n) 97 | 98 | 99 | presentsAtHouse2 : Int -> Int 100 | presentsAtHouse2 n = 101 | divisors2 n 102 | |> List.sum 103 | |> (*) 11 104 | 105 | 106 | 107 | -- 4. TESTS (uh-oh, is this problem a hard one?) 108 | 109 | 110 | tests1 : List (Test Input1 Output1) 111 | tests1 = 112 | [{- Test "example" 113 | "input" 114 | -1 115 | -1 116 | -} 117 | ] 118 | 119 | 120 | tests2 : List (Test Input2 Output2) 121 | tests2 = 122 | [] 123 | 124 | 125 | 126 | -- BOILERPLATE (shouldn't have to touch this) 127 | 128 | 129 | input_ : String 130 | input_ = 131 | """ 132 | 33100000 133 | """ 134 | |> Advent.removeNewlinesAtEnds 135 | 136 | 137 | main : Program () ( Output1, Output2 ) Never 138 | main = 139 | Advent.program 140 | { input = input_ 141 | , parse1 = parse1 142 | , parse2 = parse2 143 | , compute1 = compute1 144 | , compute2 = compute2 145 | , tests1 = tests1 146 | , tests2 = tests2 147 | } 148 | -------------------------------------------------------------------------------- /src/Year2015/Day23.hs: -------------------------------------------------------------------------------- 1 | module Day23 (main) where 2 | 3 | import Data.Function ((&)) 4 | import Data.List (intercalate) 5 | import Data.Map (Map) 6 | import qualified Data.Map as Map 7 | import Helpers (debug) 8 | 9 | data Register 10 | = A 11 | | B 12 | deriving (Show) 13 | 14 | data Instruction 15 | = HLF Register 16 | | TPL Register 17 | | INC Register 18 | | JMP Int 19 | | JIE Register Int 20 | | JIO Register Int 21 | deriving (Show) 22 | 23 | data CPU = CPU 24 | { regA :: Int, 25 | regB :: Int, 26 | pc :: Int, 27 | memory :: Map Int Instruction 28 | } 29 | 30 | instance Show CPU where 31 | show cpu@CPU {..} = 32 | [ "A: " ++ show regA, 33 | "B: " ++ show regB, 34 | "PC: " ++ show pc 35 | ] 36 | & intercalate ", " 37 | 38 | step :: CPU -> Maybe CPU 39 | step cpu@CPU {..} = do 40 | instruction <- Map.lookup pc memory 41 | -- let !_ = debug "instruction" instruction 42 | let result = 43 | case instruction of 44 | HLF reg -> hlf reg cpu 45 | TPL reg -> tpl reg cpu 46 | INC reg -> inc reg cpu 47 | JMP offset -> jmp offset cpu 48 | JIE reg offset -> jie reg offset cpu 49 | JIO reg offset -> jio reg offset cpu 50 | return $ {-debug "result"-} result 51 | 52 | onReg :: Register -> (Int -> Int) -> CPU -> CPU 53 | onReg reg fn cpu@CPU {..} = 54 | case reg of 55 | A -> cpu {regA = fn regA} 56 | B -> cpu {regB = fn regB} 57 | 58 | ifReg :: Register -> (Int -> Bool) -> (CPU -> CPU) -> (CPU -> CPU) -> CPU -> CPU 59 | ifReg reg pred fnTrue fnFalse cpu@CPU {..} = 60 | case reg of 61 | A -> if pred regA then fnTrue cpu else fnFalse cpu 62 | B -> if pred regB then fnTrue cpu else fnFalse cpu 63 | 64 | hlf reg cpu = cpu & onReg reg (`div` 2) & jmp 1 65 | tpl reg cpu = cpu & onReg reg (* 3) & jmp 1 66 | inc reg cpu = cpu & onReg reg (+ 1) & jmp 1 67 | jmp offset cpu@CPU {..} = cpu {pc = pc + offset} 68 | jie reg offset = ifReg reg even (jmp offset) (jmp 1) 69 | jio reg offset = ifReg reg (== 1) (jmp offset) (jmp 1) 70 | 71 | run :: CPU -> CPU 72 | run = iterate' step 73 | 74 | iterate' :: (a -> Maybe a) -> a -> a 75 | iterate' fn x = 76 | case fn x of 77 | Nothing -> x 78 | Just x' -> iterate' fn x' 79 | 80 | main :: IO () 81 | main = 82 | let initCpu = fromInput input 83 | -- !_ = debug "init" initCpu 84 | finalCpu = run initCpu 85 | in putStrLn $ "reg B = " ++ show (regB finalCpu) 86 | 87 | fromInput :: [Instruction] -> CPU 88 | fromInput instructions = 89 | --CPU 0 0 0 (Map.fromList (zip [0 ..] instructions)) 90 | CPU 1 0 0 (Map.fromList (zip [0 ..] instructions)) 91 | 92 | input :: [Instruction] 93 | input = 94 | [JIO A 16, INC A, INC A, TPL A, TPL A, TPL A, INC A, INC A, TPL A, INC A, INC A, TPL A, TPL A, TPL A, INC A, JMP 23, TPL A, INC A, INC A, TPL A, INC A, INC A, TPL A, TPL A, INC A, INC A, TPL A, INC A, TPL A, INC A, TPL A, INC A, INC A, TPL A, INC A, TPL A, TPL A, INC A, JIO A 8, INC B, JIE A 4, TPL A, INC A, JMP 2, HLF A, JMP (-7)] 95 | -------------------------------------------------------------------------------- /src/Year2015/Day24.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janiczek/advent-of-code/8328d43e5dec90f9d3fba32dccd53a849533c39c/src/Year2015/Day24.pdf -------------------------------------------------------------------------------- /src/Year2015/Day25.hs: -------------------------------------------------------------------------------- 1 | module Year2015Day25 where 2 | 3 | row :: Int 4 | col :: Int 5 | (row, col) = 6 | (2978, 3083) 7 | 8 | -- 1st "row" 9 | -- (1,1) 10 | -- sum always 1+1 11 | 12 | -- 4th "row" 13 | -- (4,1) (3,2) (2,3) (1,4) 14 | -- sum always 4+1 15 | 16 | -- 5th "row" 17 | -- (5,1) (4,2) (3,3) (2,4) (1,5) 18 | -- sum always 5+1 19 | 20 | -- 4th number in the 5th row = (_, 4) 21 | 22 | triangleRow :: (Int, Int) -> Int 23 | triangleRow (r, c) = 24 | r + c - 1 25 | 26 | triangularNumber :: Int -> Int 27 | triangularNumber n = 28 | (n * (n + 1)) `div` 2 29 | 30 | numberOfIterations :: (Int, Int) -> Int 31 | numberOfIterations (r, c) = 32 | triangularNumber (triangleRow (r, c) - 1) -- one less row, the last one will not be necessarily complete 33 | + c -- unfinished last row 34 | 35 | initNumber :: Int 36 | initNumber = 37 | 20151125 38 | 39 | step :: Int -> Int 40 | step n = 41 | (n * 252533) `rem` 33554393 42 | 43 | numbers :: [Int] 44 | numbers = 45 | iterate step initNumber 46 | 47 | nthNumber :: Int -> Int 48 | nthNumber n = 49 | -- 1-based! 50 | numbers !! (n - 1) 51 | 52 | numberAtRC :: (Int,Int) -> Int 53 | numberAtRC (r,c) = 54 | nthNumber $ numberOfIterations (r,c) 55 | 56 | main :: IO () 57 | main = do 58 | print (row,col) 59 | print $ numberOfIterations (row,col) 60 | print $ numberAtRC (row,col) 61 | -------------------------------------------------------------------------------- /src/Year2016/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janiczek/advent-of-code/8328d43e5dec90f9d3fba32dccd53a849533c39c/src/Year2016/.keep -------------------------------------------------------------------------------- /src/Year2016/Day05.js: -------------------------------------------------------------------------------- 1 | const md5 = require('md5-jkmyers'); 2 | const Elm = require('../js/2016-05.js'); 3 | const app = Elm.Year2016.Day05.worker(); 4 | app.ports.elmAsks.subscribe(string => app.ports.jsAnswers.send(md5(string))); 5 | -------------------------------------------------------------------------------- /src/Year2016/Day12.factor: -------------------------------------------------------------------------------- 1 | ! Copyright (C) 2022 Martin Janiczek. 2 | ! See http://factorcode.org/license.txt for BSD license. 3 | USING: kernel sequences io.encodings.utf8 io.files io peg.ebnf 4 | multiline math.parser strings accessors combinators match math 5 | assocs arrays system prettyprint ; 6 | IN: aoc-2016-12 7 | 8 | EBNF: parse [=[ 9 | 10 | Register = [a-d] => [[ 1string ]] 11 | Literal = "-"? [0-9]+ => [[ concat string>number ]] 12 | Value = Register | Literal 13 | 14 | Cpy = "cpy" " "~ Value " "~ Register 15 | Inc = "inc" " "~ Register 16 | Dec = "dec" " "~ Register 17 | Jnz = "jnz" " "~ Value " "~ Literal 18 | 19 | Operation = (Cpy | Inc | Dec | Jnz) ("\n"?)~ 20 | 21 | Main = Operation+ 22 | 23 | ]=] 24 | 25 | TUPLE: state ops ip a b c d ; 26 | 27 | : ( ops -- state ) 28 | state new 29 | swap >>ops 30 | 0 >>ip 31 | 0 >>a 0 >>b 0 >>c 0 >>d ; 32 | 33 | : change-register! ( state fn reg -- state ) 34 | { 35 | { "a" [ change-a ] } 36 | { "b" [ change-b ] } 37 | { "c" [ change-c ] } 38 | { "d" [ change-d ] } 39 | } at call( state fn -- state ) ; 40 | 41 | : set-register! ( state val reg -- state ) 42 | { 43 | { "a" [ >>a ] } 44 | { "b" [ >>b ] } 45 | { "c" [ >>c ] } 46 | { "d" [ >>d ] } 47 | } at call( state fn -- state ) ; 48 | 49 | : get-val ( state val -- int ) 50 | { 51 | { [ dup "a" = ] [ drop a>> ] } 52 | { [ dup "b" = ] [ drop b>> ] } 53 | { [ dup "c" = ] [ drop c>> ] } 54 | { [ dup "d" = ] [ drop d>> ] } 55 | { [ dup number? ] [ nip ] } 56 | } cond 57 | ; 58 | 59 | : inc-ip! ( state -- state ) 60 | [ 1 + ] change-ip ; 61 | 62 | : ip-in-bounds? ( state -- ? ) 63 | [ ip>> ] [ ops>> ] bi bounds-check? ; 64 | 65 | : get-op ( state -- op ) 66 | [ ip>> ] [ ops>> ] bi nth ; 67 | 68 | MATCH-VARS: ?val ?lit ?reg ?other ; 69 | 70 | : step ( state -- state ? ) 71 | dup get-op 72 | { 73 | { { "cpy" ?val ?reg } [ dup ?val get-val ?reg set-register! inc-ip! ] } 74 | { { "inc" ?reg } [ [ 1 + ] ?reg change-register! inc-ip! ] } 75 | { { "dec" ?reg } [ [ 1 - ] ?reg change-register! inc-ip! ] } 76 | { { "jnz" ?val ?lit } [ dup ?val get-val zero? 77 | [ inc-ip! ] [ [ ?lit + ] change-ip ] if ] } 78 | } match-cond 79 | dup ip-in-bounds? ; 80 | 81 | : common ( filename -- state ) 82 | "/Applications/factor/work/aoc-2016-12/" prepend 83 | utf8 file-contents parse ; 84 | 85 | : part1 ( filename -- int-answer ) common [ step ] loop a>> ; 86 | : part2 ( filename -- int-answer ) common 1 >>c [ step ] loop a>> ; 87 | : ex1 ( -- int-answer ) "example.in" part1 ; 88 | : p1 ( -- int-answer ) "input.in" part1 ; 89 | : p2 ( -- int-answer ) "input.in" part2 ; 90 | : main ( -- ) p1 . flush p2 . flush ; 91 | 92 | MAIN: main 93 | -------------------------------------------------------------------------------- /src/Year2016/Day19.elm: -------------------------------------------------------------------------------- 1 | module Year2016.Day19 exposing (Input1, Input2, Output1, Output2, compute1, compute2, input_, main, parse1, parse2, tests1, tests2) 2 | 3 | import Advent 4 | exposing 5 | ( Test 6 | -- , unsafeToInt 7 | -- , unsafeMaybe 8 | ) 9 | import List.Zipper as Zipper exposing (Zipper) 10 | import Set exposing (Set) 11 | 12 | 13 | 14 | {- part 2 wrong answers: 15 | 754487 16 | 1173647 17 | -} 18 | -- 1. TYPES (what is the best representation of the problem?) 19 | 20 | 21 | type alias Input1 = 22 | Int 23 | 24 | 25 | type alias Input2 = 26 | Int 27 | 28 | 29 | type alias Output1 = 30 | Int 31 | 32 | 33 | type alias Output2 = 34 | Int 35 | 36 | 37 | 38 | -- 2. PARSE (mangle the input string into the representation we decided on) 39 | 40 | 41 | parse1 : String -> Input1 42 | parse1 string = 43 | Advent.unsafeToInt string 44 | 45 | 46 | parse2 : String -> Input2 47 | parse2 string = 48 | parse1 string 49 | 50 | 51 | 52 | -- 3. COMPUTE (actually solve the problem) 53 | 54 | 55 | compute1 : Input1 -> Output1 56 | compute1 input = 57 | Zipper.fromCons 1 (List.range 2 input) 58 | |> go1 59 | 60 | 61 | go1 : Zipper Int -> Int 62 | go1 ps = 63 | if Zipper.isFirst ps && Zipper.isLast ps then 64 | Zipper.current ps 65 | 66 | else 67 | go1 68 | (ps 69 | |> removeNext 70 | |> goToNext 71 | ) 72 | 73 | 74 | removeNext : Zipper Int -> Zipper Int 75 | removeNext ps = 76 | if Zipper.isLast ps then 77 | -- [1,2] 3 [] --> [2] 3 [] 78 | Zipper.mapBefore (List.drop 1) ps 79 | 80 | else 81 | -- [1,2] 3 [4,5] --> [1,2] 3 [5] 82 | Zipper.mapAfter (List.drop 1) ps 83 | 84 | 85 | goToNext : Zipper Int -> Zipper Int 86 | goToNext ps = 87 | if Zipper.isLast ps then 88 | -- [1,2] 3 [] --> [] 1 [2,3] 89 | Zipper.first ps 90 | 91 | else 92 | -- [1,2] 3 [4,5] -> [1,2,3] 4 [5] 93 | ps 94 | |> Zipper.next 95 | -- impossible: 96 | |> Maybe.withDefault ps 97 | 98 | 99 | compute2 : Input2 -> Output2 100 | compute2 input = 101 | -1 102 | 103 | 104 | 105 | -- 4. TESTS (uh-oh, is this problem a hard one?) 106 | 107 | 108 | tests1 : List (Test Input1 Output1) 109 | tests1 = 110 | [ Test "example" "5" (Just 5) 3 111 | ] 112 | 113 | 114 | tests2 : List (Test Input2 Output2) 115 | tests2 = 116 | [ Test "example" "5" (Just 5) 2 117 | , Test "example" "8" (Just 8) 7 118 | ] 119 | 120 | 121 | 122 | -- BOILERPLATE (shouldn't have to touch this) 123 | 124 | 125 | input_ : String 126 | input_ = 127 | """ 128 | 3017957 129 | """ 130 | |> Advent.removeNewlinesAtEnds 131 | 132 | 133 | main : Program () ( Output1, Output2 ) Never 134 | main = 135 | Advent.program 136 | { input = input_ 137 | , parse1 = parse1 138 | , parse2 = parse2 139 | , compute1 = compute1 140 | , compute2 = compute2 141 | , tests1 = tests1 142 | , tests2 = tests2 143 | } 144 | -------------------------------------------------------------------------------- /src/Year2017/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janiczek/advent-of-code/8328d43e5dec90f9d3fba32dccd53a849533c39c/src/Year2017/.keep -------------------------------------------------------------------------------- /src/Year2017/Day02.elm: -------------------------------------------------------------------------------- 1 | module Year2017.Day02 exposing (..) 2 | 3 | import Advent exposing (Test, toInt) 4 | import List.Extra 5 | 6 | 7 | main : Program Never ( Output1, Output2 ) Never 8 | main = 9 | Advent.program 10 | { input = input 11 | , parse1 = parse 12 | , parse2 = parse 13 | , compute1 = compute1 14 | , compute2 = compute2 15 | , tests1 = tests1 16 | , tests2 = tests2 17 | } 18 | 19 | 20 | type alias Input = 21 | List (List Int) 22 | 23 | 24 | type alias Output1 = 25 | Int 26 | 27 | 28 | type alias Output2 = 29 | Int 30 | 31 | 32 | input : String 33 | input = 34 | """3751 3769 2769 2039 2794 240 3579 1228 4291 220 324 3960 211 1346 237 1586 35 | 550 589 538 110 167 567 99 203 524 288 500 111 118 185 505 74 36 | 2127 1904 199 221 1201 250 1119 377 1633 1801 2011 1794 394 238 206 680 37 | 435 1703 1385 1461 213 1211 192 1553 1580 197 571 195 326 1491 869 1282 38 | 109 104 3033 120 652 2752 1822 2518 1289 1053 1397 951 3015 3016 125 1782 39 | 2025 1920 1891 99 1057 1909 2237 106 97 920 603 1841 2150 1980 1970 88 40 | 1870 170 167 176 306 1909 1825 1709 168 1400 359 817 1678 1718 1594 1552 41 | 98 81 216 677 572 295 38 574 403 74 91 534 662 588 511 51 42 | 453 1153 666 695 63 69 68 58 524 1088 75 1117 1192 1232 1046 443 43 | 3893 441 1825 3730 3660 115 4503 4105 3495 4092 48 3852 132 156 150 4229 44 | 867 44 571 40 884 922 418 328 901 845 42 860 932 53 432 569 45 | 905 717 162 4536 4219 179 990 374 4409 4821 393 4181 4054 4958 186 193 46 | 2610 2936 218 2552 3281 761 204 3433 3699 2727 3065 3624 193 926 1866 236 47 | 2602 216 495 3733 183 4688 2893 4042 3066 3810 189 4392 3900 4321 2814 159 48 | 166 136 80 185 135 78 177 123 82 150 121 145 115 63 68 24 49 | 214 221 265 766 959 1038 226 1188 1122 117 458 1105 1285 1017 274 281""" 50 | 51 | 52 | parse : String -> Input 53 | parse input = 54 | input 55 | |> String.lines 56 | |> List.map (String.split " ") 57 | |> List.map (List.map toInt) 58 | 59 | 60 | compute1 : Input -> Output1 61 | compute1 input = 62 | input 63 | |> List.map differenceOfRow 64 | |> List.sum 65 | 66 | 67 | compute2 : Input -> Output2 68 | compute2 input = 69 | input 70 | |> List.map resultOfDivision 71 | |> List.sum 72 | 73 | 74 | differenceOfRow : List Int -> Int 75 | differenceOfRow row = 76 | let 77 | highest = 78 | List.maximum row 79 | 80 | lowest = 81 | List.minimum row 82 | in 83 | Maybe.map2 (-) 84 | highest 85 | lowest 86 | |> Maybe.withDefault 0 87 | 88 | 89 | resultOfDivision : List Int -> Int 90 | resultOfDivision row = 91 | let 92 | getCombinations : List Int -> List ( Int, Int ) 93 | getCombinations row = 94 | row 95 | |> List.Extra.andThen 96 | (\x -> 97 | row 98 | |> List.Extra.andThen (\y -> [ ( x, y ) ]) 99 | ) 100 | 101 | divides : Int -> Int -> Bool 102 | divides y x = 103 | x % y == 0 104 | in 105 | row 106 | |> getCombinations 107 | |> List.filter (\( x, y ) -> x /= y) 108 | |> List.filter (\( x, y ) -> x |> divides y) 109 | |> List.map (\( x, y ) -> x // y) 110 | |> List.sum 111 | 112 | 113 | tests1 : List (Test Input Output1) 114 | tests1 = 115 | [ Test "example" 116 | "5 1 9 5\n7 5 3\n2 4 6 8" 117 | [ [ 5, 1, 9, 5 ], [ 7, 5, 3 ], [ 2, 4, 6, 8 ] ] 118 | 18 119 | ] 120 | 121 | 122 | tests2 : List (Test Input Output2) 123 | tests2 = 124 | [ Test "example" 125 | "5 9 2 8\n9 4 7 3\n3 8 6 5" 126 | [ [ 5, 9, 2, 8 ], [ 9, 4, 7, 3 ], [ 3, 8, 6, 5 ] ] 127 | 9 128 | ] 129 | -------------------------------------------------------------------------------- /src/Year2017/Day06.elm: -------------------------------------------------------------------------------- 1 | module Year2017.Day06 exposing (..) 2 | 3 | import Advent exposing (Test) 4 | import Array.Hamt as Array exposing (Array) 5 | 6 | 7 | main : Program Never ( Output, Output ) Never 8 | main = 9 | Advent.program 10 | { input = input 11 | , parse1 = parse 12 | , parse2 = parse 13 | , compute1 = compute1 14 | , compute2 = compute2 15 | , tests1 = tests1 16 | , tests2 = tests2 17 | } 18 | 19 | 20 | type alias Input = 21 | Array Int 22 | 23 | 24 | type alias Output = 25 | Int 26 | 27 | 28 | input : String 29 | input = 30 | "0 5 10 0 11 14 13 4 11 8 8 7 1 4 12 11" 31 | 32 | 33 | parse : String -> Input 34 | parse input = 35 | input 36 | |> String.split " " 37 | |> List.map Advent.toInt 38 | |> Array.fromList 39 | 40 | 41 | compute1 : Input -> Output 42 | compute1 input = 43 | -- TODO sets? 44 | process input [] 45 | 46 | 47 | process : Array Int -> List (Array Int) -> Int 48 | process input memory = 49 | let 50 | newState = 51 | redistribute input 52 | in 53 | if memory |> List.member newState then 54 | List.length memory + 1 55 | else 56 | process newState (newState :: memory) 57 | 58 | 59 | redistribute : Array Int -> Array Int 60 | redistribute input = 61 | let 62 | ( maxIndex, max ) = 63 | maximum input 64 | 65 | inputWithoutMax = 66 | input 67 | |> Array.set maxIndex 0 68 | in 69 | redistributeHelper inputWithoutMax max (maxIndex + 1) 70 | 71 | 72 | redistributeHelper : Array Int -> Int -> Index -> Array Int 73 | redistributeHelper array toGive index = 74 | if index >= Array.length array then 75 | redistributeHelper array toGive 0 76 | else if toGive > 0 then 77 | let 78 | newArray = 79 | array 80 | |> update index (\x -> x + 1) 81 | in 82 | redistributeHelper newArray (toGive - 1) (index + 1) 83 | else 84 | array 85 | 86 | 87 | update : Index -> (a -> a) -> Array a -> Array a 88 | update index fn array = 89 | array 90 | |> Array.get index 91 | |> Maybe.map 92 | (\old -> 93 | let 94 | new = 95 | fn old 96 | in 97 | array 98 | |> Array.set index new 99 | ) 100 | |> Maybe.withDefault array 101 | 102 | 103 | type alias Index = 104 | Int 105 | 106 | 107 | maximum : Array Int -> ( Index, Int ) 108 | maximum array = 109 | array 110 | |> Array.indexedMap (,) 111 | |> Array.foldl 112 | (\( i, n ) ( mi, mn ) -> 113 | if n > mn then 114 | ( i, n ) 115 | else 116 | ( mi, mn ) 117 | ) 118 | ( -1, -1 ) 119 | 120 | 121 | compute2 : Input -> Output 122 | compute2 input = 123 | process2 input [ input ] 124 | 125 | 126 | process2 : Array Int -> List (Array Int) -> Int 127 | process2 input memory = 128 | let 129 | newState = 130 | redistribute input 131 | in 132 | if memory |> List.member newState then 133 | let 134 | startIndex = 135 | memory 136 | |> List.reverse 137 | |> List.indexedMap (,) 138 | |> List.filter (\( i, state ) -> state == newState) 139 | |> List.head 140 | |> Maybe.map Tuple.first 141 | |> Maybe.withDefault -1 142 | 143 | endIndex = 144 | List.length memory 145 | in 146 | endIndex - startIndex 147 | else 148 | process2 newState (newState :: memory) 149 | 150 | 151 | tests1 : List (Test Input Output) 152 | tests1 = 153 | [ Test "example" 154 | "0 2 7 0" 155 | ([ 0, 2, 7, 0 ] |> Array.fromList) 156 | 5 157 | ] 158 | 159 | 160 | tests2 : List (Test Input Output) 161 | tests2 = 162 | [ Test "example" 163 | "0 2 7 0" 164 | ([ 0, 2, 7, 0 ] |> Array.fromList) 165 | 4 166 | ] 167 | -------------------------------------------------------------------------------- /src/Year2017/Day15.elm: -------------------------------------------------------------------------------- 1 | module Year2017.Day15 exposing (..) 2 | 3 | import Advent exposing (Test) 4 | import Bitwise 5 | 6 | 7 | main : Program Never ( Output, Output ) Never 8 | main = 9 | Advent.program 10 | { input = input 11 | , parse1 = parse 12 | , parse2 = parse 13 | , compute1 = compute1 14 | , compute2 = compute2 15 | , tests1 = tests1 16 | , tests2 = tests2 17 | } 18 | 19 | 20 | factorA : Int 21 | factorA = 22 | 16807 23 | 24 | 25 | factorB : Int 26 | factorB = 27 | 48271 28 | 29 | 30 | mod : Int 31 | mod = 32 | 2147483647 33 | 34 | 35 | type alias Input = 36 | ( Int, Int ) 37 | 38 | 39 | type alias Output = 40 | Int 41 | 42 | 43 | parse : String -> Input 44 | parse input = 45 | case 46 | input 47 | |> String.lines 48 | |> List.map (String.dropLeft 24) 49 | |> List.map Advent.toInt 50 | of 51 | [ a, b ] -> 52 | ( a, b ) 53 | 54 | _ -> 55 | Debug.crash "wrong input!" 56 | 57 | 58 | compute1 : Input -> Output 59 | compute1 ( seedA, seedB ) = 60 | iterate 40000000 0 seedA seedB False 61 | 62 | 63 | compute2 : Input -> Output 64 | compute2 ( seedA, seedB ) = 65 | iterate 5000000 0 seedA seedB True 66 | 67 | 68 | iterate : Int -> Int -> Int -> Int -> Bool -> Int 69 | iterate howManyLeft goodCount currentA currentB findMultiples = 70 | if howManyLeft == 0 then 71 | goodCount 72 | else 73 | let 74 | newA = 75 | if findMultiples then 76 | findGood 4 currentA factorA 77 | else 78 | currentA * factorA % mod 79 | 80 | newB = 81 | if findMultiples then 82 | findGood 8 currentB factorB 83 | else 84 | currentB * factorB % mod 85 | 86 | good = 87 | isGood newA newB 88 | 89 | newGoodCount = 90 | if good then 91 | goodCount + 1 92 | else 93 | goodCount 94 | 95 | newHowManyLeft = 96 | howManyLeft - 1 97 | in 98 | iterate newHowManyLeft newGoodCount newA newB findMultiples 99 | 100 | 101 | findGood : Int -> Int -> Int -> Int 102 | findGood multiple current factor = 103 | let 104 | try = 105 | current * factor % mod 106 | in 107 | if try % multiple == 0 then 108 | try 109 | else 110 | findGood multiple try factor 111 | 112 | 113 | mask : Int 114 | mask = 115 | 0xFFFF 116 | 117 | 118 | isGood : Int -> Int -> Bool 119 | isGood a b = 120 | (Bitwise.and a mask) == (Bitwise.and b mask) 121 | 122 | 123 | tests1 : List (Test Input Output) 124 | tests1 = 125 | [ Test "example" 126 | """Generator A starts with 65 127 | Generator B starts with 8921""" 128 | ( 65, 8921 ) 129 | 588 130 | ] 131 | 132 | 133 | tests2 : List (Test Input Output) 134 | tests2 = 135 | [ Test "example" 136 | """Generator A starts with 65 137 | Generator B starts with 8921""" 138 | ( 65, 8921 ) 139 | 309 140 | ] 141 | 142 | 143 | input : String 144 | input = 145 | """Generator A starts with 679 146 | Generator B starts with 771""" 147 | -------------------------------------------------------------------------------- /src/Year2017/Day17.elm: -------------------------------------------------------------------------------- 1 | module Year2017.Day17 exposing (..) 2 | 3 | import Advent exposing (Test) 4 | import Array.Hamt as Array exposing (Array) 5 | 6 | 7 | main : Program Never ( Output, Output ) Never 8 | main = 9 | Advent.program 10 | { input = input 11 | , parse1 = parse 12 | , parse2 = parse 13 | , compute1 = compute1 14 | , compute2 = compute2 15 | , tests1 = tests1 16 | , tests2 = tests2 17 | } 18 | 19 | 20 | type alias Input = 21 | Int 22 | 23 | 24 | type alias Output = 25 | Int 26 | 27 | 28 | steps : Int 29 | steps = 30 | 2017 31 | 32 | 33 | 34 | -- insert AFTER the value you stop on 35 | 36 | 37 | initial : Array Int 38 | initial = 39 | [ 0 ] 40 | |> Array.fromList 41 | 42 | 43 | parse : String -> Input 44 | parse input = 45 | Advent.toInt input 46 | 47 | 48 | compute1 : Input -> Output 49 | compute1 input = 50 | List.range 1 2017 51 | |> List.foldl (spinAndAdd input) ( initial, 0 ) 52 | |> valueAfter2017 53 | 54 | 55 | spinAndAdd : Int -> Int -> ( Array Int, Int ) -> ( Array Int, Int ) 56 | spinAndAdd moveTimes current ( array, index ) = 57 | let 58 | length = 59 | Array.length array 60 | 61 | indexAfterMoving = 62 | (index + moveTimes) % length 63 | 64 | indexAfterInserting = 65 | indexAfterMoving + 1 66 | 67 | newArray = 68 | Array.slice 0 (indexAfterMoving + 1) array 69 | |> Array.push current 70 | |> (flip Array.append) (Array.slice (indexAfterMoving + 1) length array) 71 | in 72 | ( newArray, indexAfterInserting ) 73 | 74 | 75 | valueAfter2017 : ( Array Int, Int ) -> Int 76 | valueAfter2017 ( array, index ) = 77 | array 78 | |> Array.get ((index + 1) % (Array.length array)) 79 | |> Advent.unsafeMaybe 80 | 81 | 82 | compute2 : Input -> Output 83 | compute2 input = 84 | recurse input 1 -1 0 85 | 86 | 87 | end : Int 88 | end = 89 | 50000000 90 | 91 | 92 | recurse : Int -> Int -> Int -> Int -> Int 93 | recurse moveTimes current valueAtIndex1 index = 94 | if current > end then 95 | valueAtIndex1 96 | else 97 | let 98 | length = 99 | current 100 | 101 | indexAfterMoving = 102 | (index + moveTimes) % length 103 | 104 | indexAfterInserting = 105 | indexAfterMoving + 1 106 | 107 | newValueAtIndex1 = 108 | if indexAfterInserting == 1 then 109 | current 110 | else 111 | valueAtIndex1 112 | in 113 | recurse moveTimes (current + 1) newValueAtIndex1 indexAfterInserting 114 | 115 | 116 | tests1 : List (Test Input Output) 117 | tests1 = 118 | [ Test "example" "3" 3 638 ] 119 | 120 | 121 | tests2 : List (Test Input Output) 122 | tests2 = 123 | [] 124 | 125 | 126 | input : String 127 | input = 128 | "345" 129 | -------------------------------------------------------------------------------- /src/Year2017/Day23.py: -------------------------------------------------------------------------------- 1 | def is_prime(n): 2 | if n == 2 or n == 3: return True 3 | if n < 2 or n%2 == 0: return False 4 | if n < 9: return True 5 | if n%3 == 0: return False 6 | r = int(n**0.5) 7 | f = 5 8 | while f <= r: 9 | if n%f == 0: return False 10 | if n%(f+2) == 0: return False 11 | f +=6 12 | return True 13 | 14 | b = 108100 15 | c = 125100 16 | step = 17 17 | h = 1001 18 | primes = 0 19 | 20 | for current in range(b,c+1,step): 21 | if is_prime(current): 22 | primes += 1 23 | 24 | print(h - primes) 25 | -------------------------------------------------------------------------------- /src/Year2018/Day14.elm: -------------------------------------------------------------------------------- 1 | module Year2018.Day14 exposing (Input1, Input2, Output1, Output2, compute1, compute2, input_, main, parse1, parse2, tests1, tests2) 2 | 3 | import Advent 4 | exposing 5 | ( Test 6 | -- , unsafeToInt 7 | -- , unsafeMaybe 8 | ) 9 | import Array exposing (Array) 10 | 11 | 12 | 13 | -- 1. TYPES (what is the best representation of the problem?) 14 | 15 | 16 | type alias Input1 = 17 | Int 18 | 19 | 20 | type alias Input2 = 21 | Int 22 | 23 | 24 | type alias Output1 = 25 | Int 26 | 27 | 28 | type alias Output2 = 29 | Int 30 | 31 | 32 | 33 | -- 2. PARSE (mangle the input string into the representation we decided on) 34 | 35 | 36 | parse1 : String -> Input1 37 | parse1 string = 38 | Advent.unsafeToInt string 39 | 40 | 41 | parse2 : String -> Input2 42 | parse2 string = 43 | parse1 string 44 | 45 | 46 | 47 | -- 3. COMPUTE (actually solve the problem) 48 | 49 | 50 | compute1 : Input1 -> Output1 51 | compute1 input = 52 | step (input + 10) start 53 | |> output 54 | 55 | 56 | compute2 : Input2 -> Output2 57 | compute2 input = 58 | Debug.todo "done in Python :(" 59 | 60 | 61 | output : String -> Int 62 | output string = 63 | String.right 10 string 64 | |> Advent.unsafeToInt 65 | 66 | 67 | step : Int -> ( String, Int, Int ) -> String 68 | step neededRecipes ( state, i1, i2 ) = 69 | let 70 | length = 71 | String.length state 72 | in 73 | if length >= neededRecipes then 74 | trim neededRecipes state 75 | 76 | else 77 | let 78 | n1 : Int 79 | n1 = 80 | state 81 | |> String.slice i1 (i1 + 1) 82 | |> Advent.unsafeToInt 83 | 84 | n2 : Int 85 | n2 = 86 | state 87 | |> String.slice i2 (i2 + 1) 88 | |> Advent.unsafeToInt 89 | 90 | newNs : String 91 | newNs = 92 | new n1 n2 93 | 94 | newNsLength : Int 95 | newNsLength = 96 | String.length newNs 97 | 98 | newState : String 99 | newState = 100 | state ++ newNs 101 | 102 | newLength : Int 103 | newLength = 104 | length + newNsLength 105 | 106 | newI1 : Int 107 | newI1 = 108 | (1 + n1 + i1) |> modBy newLength 109 | 110 | newI2 : Int 111 | newI2 = 112 | (1 + n2 + i2) |> modBy newLength 113 | in 114 | step neededRecipes ( newState, newI1, newI2 ) 115 | 116 | 117 | get : Int -> Array Int -> Int 118 | get index array = 119 | Array.get index array 120 | |> Advent.unsafeMaybe 121 | 122 | 123 | getFromEnd : Int -> Array Int -> Int 124 | getFromEnd index array = 125 | -- getFromEnd 0 [1,2,3] == 3 126 | -- getFromEnd -1 [1,2,3] == 2 127 | let 128 | length = 129 | Array.length array 130 | in 131 | Array.get (length - index - 1) array 132 | |> Advent.unsafeMaybe 133 | 134 | 135 | last : Int -> Array Int -> Array Int 136 | last num array = 137 | let 138 | length = 139 | Array.length array 140 | in 141 | Array.slice (length - num) length array 142 | 143 | 144 | butLast : Int -> Array Int -> Array Int 145 | butLast num array = 146 | Array.slice 0 (Array.length array - num) array 147 | 148 | 149 | trim : Int -> String -> String 150 | trim neededLength state = 151 | let 152 | length = 153 | String.length state 154 | 155 | toTrim = 156 | length - neededLength 157 | in 158 | String.dropRight toTrim state 159 | 160 | 161 | new : Int -> Int -> String 162 | new n1 n2 = 163 | String.fromInt (n1 + n2) 164 | 165 | 166 | start : ( String, Int, Int ) 167 | start = 168 | ( "37", 0, 1 ) 169 | 170 | 171 | 172 | -- 4. TESTS (uh-oh, is this problem a hard one?) 173 | 174 | 175 | tests1 : List (Test Input1 Output1) 176 | tests1 = 177 | {- 178 | [ Test "example 1" "9" 9 "5158916779" 179 | , Test "example 2" "5" 5 "0124515891" 180 | , Test "example 3" "18" 18 "9251071085" 181 | , Test "example 4" "2018" 2018 "5941429882" 182 | ] 183 | -} 184 | [] 185 | 186 | 187 | tests2 : List (Test Input2 Output2) 188 | tests2 = 189 | {- 190 | [ Test "example 1" "51589" (Array.fromList [ 5, 1, 5, 8, 9 ]) 9 191 | , Test "example 2" "01245" (Array.fromList [ 0, 1, 2, 4, 5 ]) 5 192 | , Test "example 3" "92510" (Array.fromList [ 9, 2, 5, 1, 0 ]) 18 193 | , Test "example 4" "59414" (Array.fromList [ 5, 9, 4, 1, 4 ]) 2018 194 | ] 195 | -} 196 | [] 197 | 198 | 199 | 200 | -- BOILERPLATE (shouldn't have to touch this) 201 | 202 | 203 | input_ : String 204 | input_ = 205 | """ 206 | 503761 207 | """ 208 | |> Advent.removeNewlinesAtEnds 209 | 210 | 211 | main : Program () ( Output1, Output2 ) Never 212 | main = 213 | Advent.program 214 | { input = input_ 215 | , parse1 = parse1 216 | , parse2 = parse2 217 | , compute1 = compute1 218 | , compute2 = compute2 219 | , tests1 = tests1 220 | , tests2 = tests2 221 | } 222 | -------------------------------------------------------------------------------- /src/Year2018/Day14.py: -------------------------------------------------------------------------------- 1 | def step2(input, input_len, input0, input1, state, state_len, i1, i2): 2 | while True: 3 | n1 = state[i1] 4 | n2 = state[i2] 5 | new_n = n1 + n2 6 | new_n0 = new_n % 10 7 | new_n1 = new_n // 10 8 | 9 | if new_n0 == input0: 10 | if new_n1 == input1: 11 | if state[-(input_len - 2):] == input[:-2]: 12 | return state_len - input_len + 2 13 | else: 14 | if state[-1] == input1: 15 | if state[-(input_len - 1):] == input[:-1]: 16 | return state_len - input_len + 1 17 | 18 | 19 | elif new_n > 9 and new_n1 == input0: 20 | if state[-(input_len - 1):] == input[:-1]: 21 | return state_len - input_len + 1 22 | 23 | if new_n > 9: 24 | state.append(new_n1) 25 | state.append(new_n0) 26 | state_len += 2 27 | else: 28 | state.append(new_n0) 29 | state_len += 1 30 | 31 | i1 = (1 + n1 + i1) % state_len 32 | i2 = (1 + n2 + i2) % state_len 33 | 34 | 35 | def go(str): 36 | lst = [int(c) for c in str] 37 | length = len(str) 38 | print(str, step2(lst, length, lst[-1], lst[-2], [3,7], 2, 0, 1)) 39 | 40 | go("51589") 41 | go("01245") 42 | go("92510") 43 | go("59414") 44 | go("503761") 45 | -------------------------------------------------------------------------------- /src/Year2019/2019-15-1-musings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janiczek/advent-of-code/8328d43e5dec90f9d3fba32dccd53a849533c39c/src/Year2019/2019-15-1-musings.png -------------------------------------------------------------------------------- /src/Year2019/Day01.elm: -------------------------------------------------------------------------------- 1 | module Year2019.Day01 exposing (Input1, Input2, Output1, Output2, compute1, compute2, input_, main, parse1, parse2, tests1, tests2) 2 | 3 | import Advent 4 | exposing 5 | ( Test 6 | -- , unsafeToInt 7 | -- , unsafeMaybe 8 | ) 9 | import List.Extra 10 | 11 | 12 | 13 | -- 1. TYPES (what is the best representation of the problem?) 14 | 15 | 16 | type alias Input1 = 17 | List Int 18 | 19 | 20 | type alias Input2 = 21 | List Int 22 | 23 | 24 | type alias Output1 = 25 | Int 26 | 27 | 28 | type alias Output2 = 29 | Int 30 | 31 | 32 | 33 | -- 2. PARSE (mangle the input string into the representation we decided on) 34 | 35 | 36 | parse1 : String -> Input1 37 | parse1 string = 38 | string 39 | |> String.lines 40 | |> List.map Advent.unsafeToInt 41 | 42 | 43 | parse2 : String -> Input2 44 | parse2 string = 45 | parse1 string 46 | 47 | 48 | 49 | -- 3. COMPUTE (actually solve the problem) 50 | 51 | 52 | compute1 : Input1 -> Output1 53 | compute1 input = 54 | input 55 | |> List.map fuelRequired 56 | |> List.sum 57 | 58 | 59 | fuelRequired : Int -> Int 60 | fuelRequired mass = 61 | floor (toFloat mass / 3) - 2 62 | 63 | 64 | compute2 : Input2 -> Output2 65 | compute2 input = 66 | input 67 | |> List.map fuelRequired2 68 | |> List.sum 69 | 70 | 71 | fuelRequired2 : Int -> Int 72 | fuelRequired2 mass = 73 | (List.Extra.iterate 74 | (\m -> 75 | let 76 | needed = 77 | fuelRequired m 78 | in 79 | if needed <= 0 then 80 | Nothing 81 | 82 | else 83 | Just needed 84 | ) 85 | mass 86 | |> List.sum 87 | ) 88 | - mass 89 | 90 | 91 | 92 | -- 4. TESTS (uh-oh, is this problem a hard one?) 93 | 94 | 95 | tests1 : List (Test Input1 Output1) 96 | tests1 = 97 | [{- Test "example" 98 | "input" 99 | -1 100 | -1 101 | -} 102 | ] 103 | 104 | 105 | tests2 : List (Test Input2 Output2) 106 | tests2 = 107 | [] 108 | 109 | 110 | 111 | -- BOILERPLATE (shouldn't have to touch this) 112 | 113 | 114 | input_ : String 115 | input_ = 116 | """ 117 | 50669 118 | 83199 119 | 108957 120 | 102490 121 | 121216 122 | 57848 123 | 76120 124 | 121042 125 | 143774 126 | 106490 127 | 76671 128 | 119551 129 | 109598 130 | 124949 131 | 148026 132 | 119862 133 | 112854 134 | 96289 135 | 73573 136 | 142714 137 | 109875 138 | 126588 139 | 69748 140 | 62766 141 | 104446 142 | 61766 143 | 133199 144 | 118306 145 | 141410 146 | 64420 147 | 129370 148 | 69444 149 | 104178 150 | 109696 151 | 54654 152 | 126517 153 | 138265 154 | 87100 155 | 130934 156 | 138946 157 | 118078 158 | 135109 159 | 137330 160 | 130913 161 | 50681 162 | 53071 163 | 63070 164 | 144616 165 | 64122 166 | 122603 167 | 86612 168 | 144666 169 | 62572 170 | 72247 171 | 79005 172 | 102223 173 | 82585 174 | 54975 175 | 68539 176 | 107964 177 | 148333 178 | 100269 179 | 134101 180 | 115960 181 | 75866 182 | 99371 183 | 56685 184 | 142199 185 | 102107 186 | 84075 187 | 112733 188 | 92180 189 | 131056 190 | 142803 191 | 145495 192 | 70900 193 | 73129 194 | 60764 195 | 77576 196 | 99160 197 | 61897 198 | 78675 199 | 100890 200 | 74787 201 | 131911 202 | 93106 203 | 91267 204 | 142663 205 | 130649 206 | 103857 207 | 81178 208 | 78896 209 | 73745 210 | 84463 211 | 92926 212 | 121715 213 | 74959 214 | 71911 215 | 89102 216 | 53428 217 | """ 218 | |> Advent.removeNewlinesAtEnds 219 | 220 | 221 | main : Program () ( Output1, Output2 ) Never 222 | main = 223 | Advent.program 224 | { input = input_ 225 | , parse1 = parse1 226 | , parse2 = parse2 227 | , compute1 = compute1 228 | , compute2 = compute2 229 | , tests1 = tests1 230 | , tests2 = tests2 231 | } 232 | -------------------------------------------------------------------------------- /src/Year2019/Day04.elm: -------------------------------------------------------------------------------- 1 | module Year2019.Day04 exposing (Input1, Input2, Output1, Output2, compute1, compute2, input_, main, parse1, parse2, tests1, tests2) 2 | 3 | import Advent 4 | exposing 5 | ( Test 6 | -- , unsafeToInt 7 | -- , unsafeMaybe 8 | ) 9 | import List.Extra 10 | import Seq 11 | 12 | 13 | 14 | -- 1. TYPES (what is the best representation of the problem?) 15 | 16 | 17 | type alias Input1 = 18 | ( Int, Int ) 19 | 20 | 21 | type alias Input2 = 22 | ( Int, Int ) 23 | 24 | 25 | type alias Output1 = 26 | Int 27 | 28 | 29 | type alias Output2 = 30 | Int 31 | 32 | 33 | 34 | -- 2. PARSE (mangle the input string into the representation we decided on) 35 | 36 | 37 | parse1 : String -> Input1 38 | parse1 string = 39 | case String.split "-" string of 40 | [ a, b ] -> 41 | ( Advent.unsafeToInt a 42 | , Advent.unsafeToInt b 43 | ) 44 | 45 | _ -> 46 | Debug.todo "what" 47 | 48 | 49 | toChars : Int -> List Char 50 | toChars int = 51 | int 52 | |> String.fromInt 53 | |> String.toList 54 | 55 | 56 | parse2 : String -> Input2 57 | parse2 string = 58 | parse1 string 59 | 60 | 61 | 62 | -- 3. COMPUTE (actually solve the problem) 63 | 64 | 65 | twoOrMore : List Char -> Bool 66 | twoOrMore digits = 67 | List.Extra.group digits 68 | |> List.map (nonemptyListToList >> List.length) 69 | |> List.filter (\count -> count >= 2) 70 | |> List.length 71 | |> (/=) 0 72 | 73 | 74 | atLeastOneTwo : List Char -> Bool 75 | atLeastOneTwo digits = 76 | List.Extra.group digits 77 | |> List.map (nonemptyListToList >> List.length) 78 | |> List.filter (\count -> count == 2) 79 | |> List.length 80 | |> (/=) 0 81 | 82 | 83 | monotonic : List Char -> Bool 84 | monotonic digits = 85 | List.Extra.groupWhile (<=) digits 86 | |> List.length 87 | |> (==) 1 88 | 89 | 90 | nonemptyListToList : ( a, List a ) -> List a 91 | nonemptyListToList ( x, xs ) = 92 | x :: xs 93 | 94 | 95 | compute1 : Input1 -> Output1 96 | compute1 ( low, high ) = 97 | Seq.iterate ((+) 1) low 98 | |> Seq.take (high - low + 1) 99 | |> Seq.filterMap 100 | (\n -> 101 | let 102 | digits = 103 | toChars n 104 | in 105 | if twoOrMore digits && monotonic digits then 106 | Just n 107 | 108 | else 109 | Nothing 110 | ) 111 | |> Seq.length 112 | 113 | 114 | compute2 : Input2 -> Output2 115 | compute2 ( low, high ) = 116 | Seq.iterate ((+) 1) low 117 | |> Seq.take (high - low + 1) 118 | |> Seq.filterMap 119 | (\n -> 120 | let 121 | digits = 122 | toChars n 123 | in 124 | if atLeastOneTwo digits && monotonic digits then 125 | Just n 126 | 127 | else 128 | Nothing 129 | ) 130 | |> Seq.length 131 | 132 | 133 | 134 | -- 4. TESTS (uh-oh, is this problem a hard one?) 135 | 136 | 137 | tests1 : List (Test Input1 Output1) 138 | tests1 = 139 | [{- Test "example" 140 | "input" 141 | -1 142 | -1 143 | -} 144 | ] 145 | 146 | 147 | tests2 : List (Test Input2 Output2) 148 | tests2 = 149 | [] 150 | 151 | 152 | 153 | -- BOILERPLATE (shouldn't have to touch this) 154 | 155 | 156 | input_ : String 157 | input_ = 158 | """ 159 | 206938-679128 160 | """ 161 | |> Advent.removeNewlinesAtEnds 162 | 163 | 164 | main : Program () ( Output1, Output2 ) Never 165 | main = 166 | Advent.program 167 | { input = input_ 168 | , parse1 = parse1 169 | , parse2 = parse2 170 | , compute1 = compute1 171 | , compute2 = compute2 172 | , tests1 = tests1 173 | , tests2 = tests2 174 | } 175 | -------------------------------------------------------------------------------- /src/Year2019/Day16Part2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Janiczek/advent-of-code/8328d43e5dec90f9d3fba32dccd53a849533c39c/src/Year2019/Day16Part2.pdf -------------------------------------------------------------------------------- /src/Year2019/Day22Part2.py: -------------------------------------------------------------------------------- 1 | m = 119315717514047 2 | a = 18870914573696 3 | b = 115490606888493 4 | n = 101741582076661 5 | 6 | def compose(m,f,g): 7 | (a,b) = f 8 | (c,d) = g 9 | new_a = (a * c) % m 10 | new_b = ((a * d) % m + b) % m 11 | return (new_a, new_b) 12 | 13 | def repeat(m,n,x): 14 | if n <= 0: 15 | return (1,0) 16 | y = (1,0) 17 | while n > 1: 18 | if n % 2 == 0: 19 | x = compose(m,x,x) 20 | n = n // 2 21 | else: 22 | y = compose(m,x,y) 23 | x = compose(m,x,x) 24 | n = (n - 1) // 2 25 | return compose(m,x,y) 26 | 27 | #print(compose(10,(3,1),(3,1))) 28 | #for n in range(1,10): 29 | # print(n,repeat(10,n,(3,1))) 30 | 31 | print(repeat(m,n,(a,b))) 32 | -------------------------------------------------------------------------------- /src/Year2019/Intcode/Disasm.elm: -------------------------------------------------------------------------------- 1 | module Year2019.Intcode.Disasm exposing (Data(..), disassemble) 2 | 3 | import Array 4 | import Year2019.Intcode as Intcode exposing (Op(..), ToOp(..)) 5 | import Year2019.Intcode.Memory as Memory exposing (Memory) 6 | 7 | 8 | type Data 9 | = Instruction Op 10 | | Data Int 11 | 12 | 13 | disassemble : Memory -> List ( Int, Data ) 14 | disassemble mem = 15 | disassembleHelp [] Intcode.supportedOps 0 (Memory.length mem) mem 16 | 17 | 18 | disassembleHelp : List ( Int, Data ) -> List ( Int, ToOp ) -> Int -> Int -> Memory -> List ( Int, Data ) 19 | disassembleHelp listSoFar supportedOps position length mem = 20 | if position >= length then 21 | List.reverse listSoFar 22 | 23 | else 24 | case disassembleOne supportedOps position mem of 25 | Nothing -> 26 | let 27 | newList = 28 | ( position, Data (Memory.get position mem) ) 29 | :: listSoFar 30 | 31 | newPosition = 32 | position + 1 33 | in 34 | disassembleHelp newList supportedOps newPosition length mem 35 | 36 | Just ( a, op ) -> 37 | let 38 | newList = 39 | ( position, Instruction a ) 40 | :: listSoFar 41 | 42 | newPosition = 43 | case op of 44 | Op0 _ -> 45 | position + 1 46 | 47 | Op1 _ _ -> 48 | position + 2 49 | 50 | Op2 _ _ -> 51 | position + 3 52 | 53 | Op3 _ _ -> 54 | position + 4 55 | in 56 | disassembleHelp newList supportedOps newPosition length mem 57 | 58 | 59 | disassembleOne : List ( Int, ToOp ) -> Int -> Memory -> Maybe ( Op, ToOp ) 60 | disassembleOne supportedOps position mem = 61 | let 62 | rawOpcode = 63 | Memory.get position mem 64 | 65 | opcode = 66 | rawOpcode |> remainderBy 100 67 | in 68 | disassembleOneHelp 69 | ( rawOpcode, opcode ) 70 | supportedOps 71 | position 72 | mem 73 | 74 | 75 | disassembleOneHelp : ( Int, Int ) -> List ( Int, ToOp ) -> Int -> Memory -> Maybe ( Op, ToOp ) 76 | disassembleOneHelp ( rawOpcode, opcode ) supportedOps position mem = 77 | case supportedOps of 78 | [] -> 79 | Nothing 80 | 81 | ( wantedOpcode, op ) :: restOfSupportedOps -> 82 | if opcode == wantedOpcode then 83 | (case op of 84 | Op0 a -> 85 | Just a 86 | 87 | Op1 mask fn -> 88 | Intcode.opcode1 rawOpcode mask fn position mem 89 | 90 | Op2 masks fn -> 91 | Intcode.opcode2 rawOpcode masks fn position mem 92 | 93 | Op3 masks fn -> 94 | Intcode.opcode3 rawOpcode masks fn position mem 95 | ) 96 | |> Maybe.map (\op_ -> ( op_, op )) 97 | 98 | else 99 | disassembleOneHelp ( rawOpcode, opcode ) restOfSupportedOps position mem 100 | -------------------------------------------------------------------------------- /src/Year2019/Intcode/Memory.elm: -------------------------------------------------------------------------------- 1 | module Year2019.Intcode.Memory exposing 2 | ( Memory 3 | , fromList 4 | , fromString 5 | , get 6 | , getParam 7 | , length 8 | , set 9 | , setMany 10 | , setParam 11 | ) 12 | 13 | import Advent 14 | import Array exposing (Array) 15 | import Dict exposing (Dict) 16 | import Maybe.Extra 17 | import Year2019.Intcode.Parameter as Parameter exposing (Parameter(..)) 18 | 19 | 20 | type alias Memory = 21 | { program : Array Int 22 | , programLength : Int 23 | , extra : Dict Int Int 24 | } 25 | 26 | 27 | fromList : List Int -> Memory 28 | fromList list = 29 | { program = Array.fromList list 30 | , programLength = List.length list 31 | , extra = Dict.empty 32 | } 33 | 34 | 35 | {-| Needs CSV: 36 | 37 | 1,2,3,4,5 38 | 39 | -} 40 | fromString : String -> Maybe Memory 41 | fromString string = 42 | string 43 | |> String.split "," 44 | |> List.map String.toInt 45 | |> Maybe.Extra.combine 46 | |> Maybe.map fromList 47 | 48 | 49 | get : Int -> Memory -> Int 50 | get position mem = 51 | if position < mem.programLength then 52 | Array.get position mem.program 53 | |> Advent.unsafeMaybe "Memory.get - shouldn't happen" 54 | 55 | else 56 | Dict.get position mem.extra 57 | |> Maybe.withDefault 0 58 | 59 | 60 | getParam : Int -> Parameter -> Memory -> Int 61 | getParam relativeBase parameter mem = 62 | case parameter of 63 | Immediate n -> 64 | n 65 | 66 | Position position -> 67 | get position mem 68 | 69 | Relative position -> 70 | get (relativeBase + position) mem 71 | 72 | 73 | set : Int -> Int -> Memory -> Memory 74 | set position value mem = 75 | if position < mem.programLength then 76 | { mem | program = Array.set position value mem.program } 77 | 78 | else 79 | { mem | extra = Dict.insert position value mem.extra } 80 | 81 | 82 | setParam : Int -> Parameter -> Int -> Memory -> Memory 83 | setParam relativeBase parameter value mem = 84 | case parameter of 85 | Immediate _ -> 86 | Debug.todo "Can't write to an immediate position, likely error in Mask" 87 | 88 | Position position -> 89 | set position value mem 90 | 91 | Relative position -> 92 | set (relativeBase + position) value mem 93 | 94 | 95 | setMany : List ( Int, Int ) -> Memory -> Memory 96 | setMany list mem = 97 | List.foldl 98 | (\( position, value ) mem_ -> set position value mem_) 99 | mem 100 | list 101 | 102 | 103 | length : Memory -> Int 104 | length mem = 105 | let 106 | dictMax : Maybe Int 107 | dictMax = 108 | mem.extra 109 | |> Dict.keys 110 | |> List.maximum 111 | in 112 | case dictMax of 113 | Nothing -> 114 | mem.programLength 115 | 116 | Just max -> 117 | max 118 | -------------------------------------------------------------------------------- /src/Year2019/Intcode/Parameter.elm: -------------------------------------------------------------------------------- 1 | module Year2019.Intcode.Parameter exposing (Parameter(..)) 2 | 3 | 4 | type Parameter 5 | = Immediate Int 6 | | Position Int 7 | | Relative Int 8 | -------------------------------------------------------------------------------- /src/Year2020/Day01.fs: -------------------------------------------------------------------------------- 1 | module AoC.Day01 2 | 3 | let exampleInput: string = 4 | "1721 5 | 979 6 | 366 7 | 299 8 | 675 9 | 1456" 10 | 11 | let realInput: string = 12 | "1962 13 | 1577 14 | 1750 15 | 1836 16 | 1762 17 | 1691 18 | 1726 19 | 1588 20 | 1370 21 | 1043 22 | 1307 23 | 1552 24 | 1813 25 | 1804 26 | 1765 27 | 1893 28 | 1610 29 | 764 30 | 1512 31 | 1404 32 | 1711 33 | 1000 34 | 1694 35 | 1546 36 | 1880 37 | 1721 38 | 2006 39 | 1787 40 | 1510 41 | 1850 42 | 1420 43 | 1712 44 | 1926 45 | 1707 46 | 1983 47 | 1680 48 | 1436 49 | 389 50 | 1448 51 | 1875 52 | 1333 53 | 1733 54 | 1935 55 | 1794 56 | 1337 57 | 1863 58 | 1769 59 | 1635 60 | 1499 61 | 1807 62 | 1326 63 | 1989 64 | 1705 65 | 1673 66 | 1829 67 | 1684 68 | 1716 69 | 456 70 | 1696 71 | 1398 72 | 1942 73 | 1851 74 | 1690 75 | 1328 76 | 1356 77 | 1775 78 | 1564 79 | 1466 80 | 1273 81 | 1896 82 | 766 83 | 1814 84 | 1810 85 | 1537 86 | 1463 87 | 1755 88 | 1341 89 | 1665 90 | 1520 91 | 1366 92 | 1387 93 | 1976 94 | 1717 95 | 1737 96 | 1551 97 | 1760 98 | 1496 99 | 1664 100 | 1450 101 | 1319 102 | 1674 103 | 1630 104 | 1301 105 | 1330 106 | 1658 107 | 1637 108 | 1655 109 | 1439 110 | 1832 111 | 1948 112 | 1339 113 | 1656 114 | 1449 115 | 1296 116 | 1489 117 | 1758 118 | 1939 119 | 1857 120 | 1402 121 | 1394 122 | 1882 123 | 1446 124 | 1412 125 | 1430 126 | 1212 127 | 1377 128 | 1501 129 | 1873 130 | 1812 131 | 1667 132 | 1560 133 | 1654 134 | 1575 135 | 1999 136 | 1581 137 | 1792 138 | 1299 139 | 1843 140 | 1383 141 | 1351 142 | 1297 143 | 1822 144 | 1801 145 | 1977 146 | 1316 147 | 1477 148 | 1980 149 | 1693 150 | 1220 151 | 1554 152 | 1607 153 | 1903 154 | 1669 155 | 1593 156 | 1955 157 | 1286 158 | 1909 159 | 1280 160 | 1854 161 | 2005 162 | 1820 163 | 1803 164 | 1763 165 | 1660 166 | 1410 167 | 1974 168 | 1808 169 | 1816 170 | 1723 171 | 1936 172 | 1423 173 | 1818 174 | 1800 175 | 1294 176 | 857 177 | 496 178 | 1248 179 | 1670 180 | 1993 181 | 1929 182 | 1966 183 | 1381 184 | 1259 185 | 1285 186 | 1797 187 | 1644 188 | 1919 189 | 1267 190 | 1509 191 | 399 192 | 1300 193 | 1662 194 | 1556 195 | 1747 196 | 1517 197 | 1972 198 | 1729 199 | 1506 200 | 1544 201 | 1957 202 | 1930 203 | 1956 204 | 1753 205 | 1284 206 | 1389 207 | 1689 208 | 1709 209 | 1627 210 | 1770 211 | 847" 212 | 213 | let parse1 (input: string): int list = 214 | input.Split "\n" 215 | |> seq 216 | |> Seq.map int 217 | |> Seq.toList 218 | 219 | let isOk (x: int) (y: int): bool = x + y = 2020 220 | 221 | // x always before y in the list... we try all ys for one x, then go for next x 222 | // a b c d e f g 223 | // ^ ^ 224 | // ^ ^ 225 | // ^ ^ 226 | // ^ ^ 227 | // ... 228 | // ^ ^ 229 | // ^ ^ 230 | let rec go1 (x: int) (xs: int list): int = 231 | match xs with 232 | | [] -> failwith "didn't find anything" 233 | | y :: ys -> 234 | match checkAllForX x xs with 235 | | None -> go1 y ys 236 | | Some ((a, b)) -> a * b 237 | 238 | and checkAllForX (x: int) (xs: int list): (int * int) option = 239 | match xs with 240 | | [] -> None 241 | | y :: ys -> if isOk x y then Some(x, y) else checkAllForX x ys 242 | 243 | let compute1 (input: int list): int = 244 | match input with 245 | | [] -> failwith "empty list?" 246 | | x :: xs -> go1 x xs 247 | 248 | let compute2 (input: int list): int option list = 249 | [ for x in input do 250 | for y in input do 251 | for z in input -> 252 | if x <> y && y <> z && x + y + z = 2020 then 253 | Some(x * y * z) 254 | else 255 | None ] 256 | |> List.filter (fun thing -> thing.IsSome) 257 | 258 | let main = realInput |> parse1 |> compute2 259 | -------------------------------------------------------------------------------- /src/Year2020/Day10.fs: -------------------------------------------------------------------------------- 1 | module AoC.Day10 2 | 3 | open System.Collections.Generic 4 | open FSharpPlus 5 | 6 | let exampleInput = "16 7 | 10 8 | 15 9 | 5 10 | 1 11 | 11 12 | 7 13 | 19 14 | 6 15 | 12 16 | 4" 17 | 18 | let example2Input = "28 19 | 33 20 | 18 21 | 42 22 | 31 23 | 14 24 | 46 25 | 20 26 | 48 27 | 47 28 | 24 29 | 23 30 | 49 31 | 45 32 | 19 33 | 38 34 | 39 35 | 11 36 | 1 37 | 32 38 | 25 39 | 35 40 | 8 41 | 17 42 | 7 43 | 9 44 | 4 45 | 2 46 | 34 47 | 10 48 | 3" 49 | 50 | let realInput = "56 51 | 139 52 | 42 53 | 28 54 | 3 55 | 87 56 | 142 57 | 57 58 | 147 59 | 6 60 | 117 61 | 95 62 | 2 63 | 112 64 | 107 65 | 54 66 | 146 67 | 104 68 | 40 69 | 26 70 | 136 71 | 127 72 | 111 73 | 47 74 | 8 75 | 24 76 | 13 77 | 92 78 | 18 79 | 130 80 | 141 81 | 37 82 | 81 83 | 148 84 | 31 85 | 62 86 | 50 87 | 80 88 | 91 89 | 33 90 | 77 91 | 1 92 | 96 93 | 100 94 | 9 95 | 120 96 | 27 97 | 97 98 | 60 99 | 102 100 | 25 101 | 83 102 | 55 103 | 118 104 | 19 105 | 113 106 | 49 107 | 133 108 | 14 109 | 119 110 | 88 111 | 124 112 | 110 113 | 145 114 | 65 115 | 21 116 | 7 117 | 74 118 | 72 119 | 61 120 | 103 121 | 20 122 | 41 123 | 53 124 | 32 125 | 44 126 | 10 127 | 34 128 | 121 129 | 114 130 | 67 131 | 69 132 | 66 133 | 82 134 | 101 135 | 68 136 | 84 137 | 48 138 | 73 139 | 17 140 | 43 141 | 140" 142 | 143 | let parse (input: string): Set = 144 | input.Split "\n" |> Array.map int32 |> Set.ofArray 145 | 146 | let maxJoltage (input: Set): int = input |> Set.maxElement |> (+) 3 147 | 148 | let rec findPath (todo: int list list) (goal: int) (all: Set): int list = 149 | match todo with 150 | | [] -> failwith "empty queue!" 151 | | pathSoFar :: restOfTodo -> 152 | let current = List.head pathSoFar 153 | if current = goal then 154 | pathSoFar 155 | else 156 | let usable = 157 | all 158 | |> Set.filter (fun n -> n - current > 0 && n - current < 4) 159 | 160 | let newTodo: int list = 161 | usable 162 | |> Set.minElement 163 | |> (fun lowestUsableJolt -> lowestUsableJolt :: pathSoFar) 164 | 165 | findPath (newTodo :: restOfTodo) goal all 166 | 167 | 168 | 169 | let part1 (input: Set): int = 170 | let max = maxJoltage input 171 | let inputWithEnd: Set = input |> Set.add max 172 | let path: int list = findPath [ [ 0 ] ] max inputWithEnd 173 | printfn "found path %A" path 174 | 175 | let diffs = 176 | List.pairwise path 177 | |> List.map (fun (a, b) -> a - b) 178 | 179 | let oneJolts = 180 | diffs 181 | |> List.filter (fun x -> x = 1) 182 | |> List.length 183 | 184 | let threeJolts = 185 | diffs 186 | |> List.filter (fun x -> x = 3) 187 | |> List.length 188 | 189 | oneJolts * threeJolts 190 | 191 | // http://www.fssnip.net/8P/title/Memoization-for-dynamic-programming 192 | let memoize f = 193 | let cache = new Dictionary<_, _>() 194 | (fun x -> 195 | let succ, v = cache.TryGetValue(x) 196 | if succ then 197 | v 198 | else 199 | let v = f (x) 200 | cache.Add(x, v) 201 | v) 202 | 203 | let part2 (input: Set): int64 = 204 | let max = maxJoltage input 205 | let inputWithEnds: Set = input |> Set.add 0 |> Set.add max 206 | 207 | let rec ways = 208 | memoize (fun n -> 209 | if not (Set.contains n inputWithEnds) 210 | then 0L 211 | else if n < 2 212 | then 1L 213 | else ways (n - 1) + ways (n - 2) + ways (n - 3)) 214 | 215 | ways max 216 | 217 | 218 | let main = realInput |> parse |> part2 219 | -------------------------------------------------------------------------------- /src/Year2020/Day13Part1.dyalog: -------------------------------------------------------------------------------- 1 | exampleInput←7 13 59 31 19 2 | exampleTs←939 3 | 4 | part1←{⍵[tss⍳mints]×⍺-⍨mints←⌊/tss←⍵×⌈⍺÷⍵} 5 | 6 | exampleTs part1 exampleInput 7 | 8 | realInput←⊃(//','⎕VFI'41,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,37,x,x,x,x,x,971,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,17,13,x,x,x,x,23,x,x,x,x,x,29,x,487,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,19') 9 | realTs←1000299 10 | 11 | realTs part1 realInput 12 | -------------------------------------------------------------------------------- /src/Year2020/Day13Part2.nb: -------------------------------------------------------------------------------- 1 | (* Mathematica / Wolfram Cloud *) 2 | Values[First[First[FindInstance[41a==37b-35==971c-41==17d-7==13e-7==23f-18==29g-12==487h-72==19i-15,{a,b,c,d,e,f,g,h,i},Integers]]]]*41 3 | -------------------------------------------------------------------------------- /src/Year2020/Day15Part1.fs: -------------------------------------------------------------------------------- 1 | module AoC.Day15 2 | 3 | let exampleInput: int64 list = [0L;3L;6L] 4 | let realInput: int64 list = [14L;1L;17L;0L;3L;20L] 5 | 6 | let generic (stop: int64) (input: int64 list): int64 = 7 | let rec go (i: int64) (last: int64) (lasts: Map) (prevs: Map) = 8 | if i = stop then 9 | last 10 | else if i <= int64 input.Length then 11 | let current = input.[int (i-1L)] 12 | let newPrevs = 13 | match lasts.TryFind current with 14 | | None -> prevs 15 | | Some lastI -> prevs.Add(current,lastI) 16 | let newLasts = lasts.Add(current,i) 17 | go (i+1L) current newLasts newPrevs 18 | else 19 | let current = 20 | match prevs.TryFind last with 21 | | None -> 0L 22 | | Some p -> i - p - 1L 23 | let newPrevs = 24 | match lasts.TryFind current with 25 | | None -> prevs 26 | | Some lastI -> prevs.Add(current,lastI) 27 | let newLasts = lasts.Add(current,i) 28 | go (i+1L) current newLasts newPrevs 29 | go 1L 0L Map.empty Map.empty 30 | 31 | 32 | let part1 = generic 2020L 33 | 34 | let main = part1 realInput 35 | -------------------------------------------------------------------------------- /src/Year2020/Day15Part2.hs: -------------------------------------------------------------------------------- 1 | import qualified Data.Map as Map 2 | 3 | inputLen = length input 4 | initIndexes input = Map.fromList $ zip input [1..] 5 | 6 | get n = go inputLen (input !! (inputLen - 1)) (initIndexes input) 7 | where 8 | go lastI last indexes = 9 | if lastI == n then 10 | last 11 | else 12 | let current = 13 | case Map.lookup last indexes of 14 | Just n -> lastI - n 15 | _ -> 0 16 | newIndexes = Map.insert last lastI indexes 17 | in 18 | go (lastI+1) current newIndexes 19 | 20 | 21 | input = [14,1,17,0,3,20] 22 | maxn = 30000000 23 | main = print $ get maxn 24 | -------------------------------------------------------------------------------- /src/Year2020/Day17.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TupleSections #-} 2 | 3 | module Year2020Day17 (main) where 4 | 5 | import Data.Maybe (mapMaybe) 6 | import qualified Data.Set as Set 7 | import Data.Set (Set) 8 | import Data.Function ((&)) 9 | 10 | go1 :: Int -> Set (Int,Int,Int) -> Int 11 | go1 0 active = Set.size active 12 | go1 turnsToDo active = 13 | go1 (turnsToDo - 1) newActive 14 | where 15 | d :: [Int] 16 | d = [-1,0,1] 17 | 18 | neighboursDelta :: [(Int,Int,Int)] 19 | neighboursDelta = 20 | [(x,y,z) | x <- d, y <- d, z <- d, (x,y,z) /= (0,0,0)] 21 | 22 | neighbours :: (Int,Int,Int) -> [(Int,Int,Int)] 23 | neighbours (x,y,z) = 24 | [(x+dx,y+dy,z+dz) | (dx,dy,dz) <- neighboursDelta] 25 | 26 | aliveNeighbours :: (Int,Int,Int) -> Int 27 | aliveNeighbours cell = 28 | neighbours cell 29 | & Set.fromList 30 | & Set.intersection active 31 | & Set.size 32 | 33 | isAlive :: (Bool,(Int,Int,Int)) -> Bool 34 | isAlive (wasAlive,cell) = 35 | n == 3 || (wasAlive && n == 2) 36 | where 37 | n = aliveNeighbours cell 38 | 39 | newActive :: Set (Int,Int,Int) 40 | newActive = 41 | active 42 | & Set.toList 43 | & concatMap (\cell -> (True,cell) : fmap (False,) (neighbours cell)) 44 | & Set.fromList 45 | & Set.filter isAlive 46 | & Set.map snd 47 | 48 | go2 :: Int -> Set (Int,Int,Int,Int) -> Int 49 | go2 0 active = Set.size active 50 | go2 turnsToDo active = 51 | go2 (turnsToDo - 1) newActive 52 | where 53 | d :: [Int] 54 | d = [-1,0,1] 55 | 56 | neighboursDelta :: [(Int,Int,Int,Int)] 57 | neighboursDelta = 58 | [(x,y,z,w) | x <- d, y <- d, z <- d, w <-d, (x,y,z,w) /= (0,0,0,0)] 59 | 60 | neighbours :: (Int,Int,Int,Int) -> [(Int,Int,Int,Int)] 61 | neighbours (x,y,z,w) = 62 | [(x+dx,y+dy,z+dz,w+dw) | (dx,dy,dz,dw) <- neighboursDelta] 63 | 64 | aliveNeighbours :: (Int,Int,Int,Int) -> Int 65 | aliveNeighbours cell = 66 | neighbours cell 67 | & Set.fromList 68 | & Set.intersection active 69 | & Set.size 70 | 71 | isAlive :: (Bool,(Int,Int,Int,Int)) -> Bool 72 | isAlive (wasAlive,cell) = 73 | n == 3 || (wasAlive && n == 2) 74 | where 75 | n = aliveNeighbours cell 76 | 77 | newActive :: Set (Int,Int,Int,Int) 78 | newActive = 79 | active 80 | & Set.toList 81 | & concatMap (\cell -> (True,cell) : fmap (False,) (neighbours cell)) 82 | & Set.fromList 83 | & Set.filter isAlive 84 | & Set.map snd 85 | 86 | input :: String 87 | input = "###..#..\n\ 88 | \.#######\n\ 89 | \#####...\n\ 90 | \#..##.#.\n\ 91 | \###..##.\n\ 92 | \##...#..\n\ 93 | \..#...#.\n\ 94 | \.#....##" 95 | 96 | testInput0 :: Set (Int,Int) 97 | testInput0 = Set.fromList [(1,0),(2,1),(0,2),(1,2),(2,2)] 98 | 99 | testInput1 :: Set (Int,Int,Int) 100 | testInput1 = testInput0 & Set.map (\(x,y) -> (x,y,0)) 101 | 102 | testInput2 :: Set (Int,Int,Int,Int) 103 | testInput2 = testInput0 & Set.map (\(x,y) -> (x,y,0,0)) 104 | 105 | parse0 :: String -> Set (Int,Int) 106 | parse0 input = 107 | input 108 | & lines 109 | & map (zip [0..]) 110 | & zip [0..] 111 | & concatMap (\(y,xs) -> map (\(x,char) -> (x,y,char)) xs) 112 | & mapMaybe (\(x,y,char) -> if char == '#' then Just (x,y) else Nothing) 113 | & Set.fromList 114 | 115 | parse1 :: String -> Set (Int,Int,Int) 116 | parse1 input = 117 | parse0 input 118 | & Set.map (\(x,y) -> (x,y,0)) 119 | 120 | parse2 :: String -> Set (Int,Int,Int,Int) 121 | parse2 input = 122 | parse0 input 123 | & Set.map (\(x,y) -> (x,y,0,0)) 124 | 125 | main :: IO () 126 | main = 127 | --print $ go1 6 (parse1 input) 128 | print $ go2 6 (parse2 input) 129 | -------------------------------------------------------------------------------- /src/Year2020/Day22.hs: -------------------------------------------------------------------------------- 1 | module Year2020Day22 (main) where 2 | 3 | import qualified Data.Set as Set 4 | import Data.Set (Set) 5 | import qualified Data.Map as Map 6 | import Data.Map (Map) 7 | 8 | playerCards :: [Int] 9 | playerCards = [43,36,13,11,20,25,37,38,4,18,1,8,27,23,7,22,10,5,50,40,45,26,15,32,33] 10 | 11 | crabCards :: [Int] 12 | crabCards = [21,29,12,28,46,9,44,6,16,39,19,24,17,14,47,48,42,34,31,3,41,35,2,30,49] 13 | 14 | play1 :: ([Int],[Int]) -> [Int] 15 | play1 (p1,p2) = 16 | case (p1,p2) of 17 | ([],_) -> p2 18 | (_,[]) -> p1 19 | (a:as,b:bs) -> 20 | if a > b then 21 | play1 (as ++ [a,b], bs) 22 | else if b > a then 23 | play1 (as, bs ++ [b,a]) 24 | else 25 | error "draw!" 26 | 27 | type Memo = Map ([Int],[Int]) (Bool, [Int]) 28 | type Players = ([Int],[Int]) 29 | 30 | play2 :: Players -> [Int] 31 | play2 (p1,p2) = 32 | cards 33 | where 34 | (_,_,cards) = go Map.empty Set.empty (p1,p2) (p1,p2) 35 | go :: Memo -> Set Players -> Players -> Players -> (Bool, Memo, [Int]) 36 | go memo seen (p1,p2) (orig1, orig2) = 37 | if Set.member (p1,p2) seen then 38 | (True, memo, p1) 39 | else 40 | case Map.lookup (p1,p2) memo of 41 | Nothing -> 42 | case (p1,p2) of 43 | ([],_) -> (False, Map.insert (orig1,orig2) (False,p2) memo, p2) 44 | (_,[]) -> (True, Map.insert (orig1,orig2) (True,p1) memo, p1) 45 | (a:as,b:bs) -> 46 | let (resultP1Won, (resultAs, resultBs)) = 47 | if length as >= a && length bs >= b then 48 | let newPlayers = (take a as, take b bs) 49 | (p1Won,_, _) = go memo Set.empty newPlayers newPlayers 50 | in 51 | ( p1Won 52 | , if p1Won then 53 | (as ++ [a,b], bs) 54 | else 55 | (as, bs ++ [b,a]) 56 | ) 57 | else if a > b then 58 | (True, (as ++ [a,b], bs)) 59 | else if b > a then 60 | (False, (as, bs ++ [b,a])) 61 | else 62 | error "draw!" 63 | in 64 | go 65 | (Map.insert (p1,p2) (resultP1Won, if resultP1Won then resultAs else resultBs) memo) 66 | (Set.insert (p1,p2) seen) 67 | (resultAs, resultBs) 68 | (orig1,orig2) 69 | Just (resultP1Won, winCards) -> 70 | (resultP1Won, memo, winCards) 71 | 72 | 73 | 74 | 75 | 76 | 77 | eval :: [Int] -> Int 78 | eval list = 79 | sum $ zipWith (*) list [len,len-1..1] 80 | where 81 | len = length list 82 | 83 | main :: IO () 84 | main = 85 | --print $ eval $ play1 (playerCards, crabCards) 86 | print $ eval $ play2 (playerCards, crabCards) 87 | --print $ eval $ play2 ([9,2,6,3,1],[5,8,4,7,10]) 88 | -------------------------------------------------------------------------------- /src/Year2020/Day23.py: -------------------------------------------------------------------------------- 1 | input_ = "463528179" 2 | i = [int(c) for c in input_] 3 | indices = [-1] + [i.index(n) for n in range(1,len(i)+1)] 4 | 5 | # Part 2: make more elements 6 | i += range(10,1_000_001) 7 | indices += range(9,1_000_000) 8 | 9 | # TODO why do we _not_ need to maintain the index as we move items around? 10 | 11 | highest = max(i) 12 | 13 | class Node: 14 | def __init__(self, next=None, val=None): 15 | self.next = next 16 | self.val = val 17 | 18 | def print_cycle(self): 19 | n = self.next 20 | l = [self.val] 21 | while n is not self: 22 | l.append(n.val) 23 | n = n.next 24 | print(l) 25 | 26 | # Create the circular list 27 | first_node = Node(None, i[0]) 28 | nodes = [first_node] 29 | previous_node = first_node 30 | for c in i[1:]: 31 | current_node = Node(None, c) 32 | nodes.append(current_node) 33 | previous_node.next = current_node 34 | previous_node = current_node 35 | current_node.next = first_node 36 | 37 | 38 | def find_destination_node(node): 39 | # figure out the wanted value 40 | unwanted_vals = [ 41 | node.next.val, 42 | node.next.next.val, 43 | node.next.next.next.val, 44 | ] 45 | wanted_val = node.val - 1 46 | if wanted_val <= 0: 47 | wanted_val = highest 48 | while wanted_val in unwanted_vals: 49 | wanted_val -= 1 50 | if wanted_val <= 0: 51 | wanted_val = highest 52 | 53 | return nodes[indices[wanted_val]] 54 | 55 | def swap_three_next_to(source_node, destination_node): 56 | node1 = source_node.next 57 | node3 = source_node.next.next.next 58 | node4 = source_node.next.next.next.next 59 | after_destination_node = destination_node.next 60 | 61 | source_node.next = node4 62 | destination_node.next = node1 63 | node3.next = after_destination_node 64 | 65 | 66 | def run(n): 67 | step = n 68 | current_node = first_node 69 | while step > 0: 70 | destination_node = find_destination_node(current_node) 71 | swap_three_next_to(current_node, destination_node) 72 | current_node = current_node.next 73 | step -= 1 74 | 75 | #run(100) 76 | run(10_000_000) 77 | node_with_1 = nodes[indices[1]] 78 | 79 | #node_with_1.print_cycle() # Part 1 80 | print(node_with_1.next.val * node_with_1.next.next.val) # Part 2 81 | -------------------------------------------------------------------------------- /src/Year2020/Day25.py: -------------------------------------------------------------------------------- 1 | #card_pub_key = 5764801 2 | #door_pub_key = 17807724 3 | 4 | card_pub_key = 14788856 5 | door_pub_key = 19316454 6 | 7 | def step(n,subject_number): 8 | return (n * subject_number) % 20201227 9 | 10 | card_loop_size = 0 11 | card_current = 1 12 | while card_current != card_pub_key: 13 | card_loop_size += 1 14 | card_current = step(card_current, 7) 15 | 16 | door_loop_size = 0 17 | door_current = 1 18 | while door_current != door_pub_key: 19 | door_loop_size += 1 20 | door_current = step(door_current, 7) 21 | 22 | i = 0 23 | current = 1 24 | while i < card_loop_size: 25 | i += 1 26 | current = step(current, door_pub_key) 27 | 28 | print(current) 29 | -------------------------------------------------------------------------------- /src/Year2021/Day01.apl: -------------------------------------------------------------------------------- 1 | in←⍎¨⊃⎕NGET'/Users/martinjaniczek/Localhost/elm/advent-of-code/Year2021/day01.txt'1 2 | p1←+/2 Input1 39 | parse1 string = 40 | string 41 | |> String.split "," 42 | |> List.filterMap String.toInt 43 | 44 | 45 | parse2 : String -> Input2 46 | parse2 string = 47 | parse1 string 48 | 49 | 50 | 51 | -- 3. COMPUTE (actually solve the problem) 52 | 53 | 54 | nTimes : Int -> (a -> a) -> a -> a 55 | nTimes n fn val = 56 | if n <= 0 then 57 | val 58 | 59 | else 60 | nTimes (n - 1) fn (fn val) 61 | 62 | 63 | step : Dict Int Int -> Dict Int Int 64 | step counts = 65 | let 66 | decremented = 67 | Dict.mapKeys (\n -> n - 1) counts 68 | in 69 | case Dict.get -1 decremented of 70 | Nothing -> 71 | decremented 72 | 73 | Just n -> 74 | decremented 75 | |> Dict.remove -1 76 | |> Dict.update 6 (Just << Maybe.unwrap n ((+) n)) 77 | |> Dict.update 8 (Just << Maybe.unwrap n ((+) n)) 78 | 79 | 80 | compute1 : Input1 -> Output1 81 | compute1 input = 82 | input 83 | |> Dict.frequencies 84 | |> nTimes 80 step 85 | |> Dict.values 86 | |> List.sum 87 | 88 | 89 | compute2 : Input2 -> Output2 90 | compute2 input = 91 | input 92 | |> Dict.frequencies 93 | |> nTimes 256 step 94 | |> Dict.values 95 | |> List.sum 96 | 97 | 98 | 99 | -- 4. TESTS (uh-oh, is this problem a hard one?) 100 | 101 | 102 | tests1 : List (Test Input1 Output1) 103 | tests1 = 104 | [ Test "example" 105 | "3,4,3,1,2" 106 | Nothing 107 | -- Just "parsed-input" 108 | 5934 109 | ] 110 | 111 | 112 | tests2 : List (Test Input2 Output2) 113 | tests2 = 114 | [] 115 | 116 | 117 | 118 | -- BOILERPLATE (shouldn't have to touch this) 119 | 120 | 121 | input_ : String 122 | input_ = 123 | """ 124 | 5,3,2,2,1,1,4,1,5,5,1,3,1,5,1,2,1,4,1,2,1,2,1,4,2,4,1,5,1,3,5,4,3,3,1,4,1,3,4,4,1,5,4,3,3,2,5,1,1,3,1,4,3,2,2,3,1,3,1,3,1,5,3,5,1,3,1,4,2,1,4,1,5,5,5,2,4,2,1,4,1,3,5,5,1,4,1,1,4,2,2,1,3,1,1,1,1,3,4,1,4,1,1,1,4,4,4,1,3,1,3,4,1,4,1,2,2,2,5,4,1,3,1,2,1,4,1,4,5,2,4,5,4,1,2,1,4,2,2,2,1,3,5,2,5,1,1,4,5,4,3,2,4,1,5,2,2,5,1,4,1,5,1,3,5,1,2,1,1,1,5,4,4,5,1,1,1,4,1,3,3,5,5,1,5,2,1,1,3,1,1,3,2,3,4,4,1,5,5,3,2,1,1,1,4,3,1,3,3,1,1,2,2,1,2,2,2,1,1,5,1,2,2,5,2,4,1,1,2,4,1,2,3,4,1,2,1,2,4,2,1,1,5,3,1,4,4,4,1,5,2,3,4,4,1,5,1,2,2,4,1,1,2,1,1,1,1,5,1,3,3,1,1,1,1,4,1,2,2,5,1,2,1,3,4,1,3,4,3,3,1,1,5,5,5,2,4,3,1,4 125 | """ 126 | |> Advent.removeNewlinesAtEnds 127 | 128 | 129 | main : Program () ( Output1, Output2 ) Never 130 | main = 131 | Advent.program 132 | { input = input_ 133 | , parse1 = parse1 134 | , parse2 = parse2 135 | , compute1 = compute1 136 | , compute2 = compute2 137 | , tests1 = tests1 138 | , tests2 = tests2 139 | } 140 | -------------------------------------------------------------------------------- /src/Year2021/Day07.apl: -------------------------------------------------------------------------------- 1 | in←2⊃','⎕VFI⊃⊃⎕NGET'/Users/martinjaniczek/Localhost/elm/advent-of-code/Year2021/day07.txt'1 2 | p1←{⌊/+⌿⍵∘.(|-)¯1+⍳1+⌈/⍵}in 3 | p2←{⌊/+⌿⍵∘.{2!1+|⍵-⍺}¯1+⍳1+⌈/⍵}in 4 | -------------------------------------------------------------------------------- /src/Year2021/Day09Part1.apl: -------------------------------------------------------------------------------- 1 | in←↑⍎¨¨⊃⎕NGET'/Users/martinjaniczek/Localhost/elm/advent-of-code/Year2021/day09.txt'1 2 | p1←+/∊z×({x←∊⍵⋄∧/(y[2×⍳4])>5⌷y←(⌈/x)@(0∘=)x}⌺3 3)z←1+in 3 | -------------------------------------------------------------------------------- /src/Year2021/Day10.apl: -------------------------------------------------------------------------------- 1 | in←⊃⎕NGET'/Users/martinjaniczek/Localhost/elm/advent-of-code/Year2021/day10.txt'1 2 | o c←↓⍉↑s←'()' '[]' '{}' '<>' 3 | r←({⍵/⍨∧⌿↑(⍵∘{~{(1@⍵)b}1+⍸b←⍵⍷⍺})¨s}⍣≡) 4 | p1←+/{⊃0~⍨3 57 1197 25137 0[c⍳w/⍨~∧/o∊⍨w←r⍵]}¨in 5 | p2←{⍵[⌈2÷⍨≢⍵]}{⍵[⍋⍵]}0~⍨{g×{⍺+5×⍵}/0,⍨o⍳w⊣g←∧/o∊⍨w←r⍵}¨in 6 | -------------------------------------------------------------------------------- /src/Year2021/Day21.elm: -------------------------------------------------------------------------------- 1 | module Year2021.Day21 exposing (Input1, Input2, Output1, Output2, compute1, compute2, input_, main, parse1, parse2, tests1, tests2) 2 | 3 | import Advent 4 | exposing 5 | ( Test 6 | -- , unsafeToInt 7 | -- , unsafeMaybe 8 | ) 9 | 10 | 11 | 12 | -- 1. TYPES (what is the best representation of the problem?) 13 | 14 | 15 | type alias Input1 = 16 | ( Int, Int ) 17 | 18 | 19 | type alias Input2 = 20 | ( Int, Int ) 21 | 22 | 23 | type alias Output1 = 24 | Int 25 | 26 | 27 | type alias Output2 = 28 | Int 29 | 30 | 31 | 32 | -- 2. PARSE (mangle the input string into the representation we decided on) 33 | 34 | 35 | parse1 : String -> Input1 36 | parse1 string = 37 | let 38 | parseLine line = 39 | line 40 | |> String.right 1 41 | |> String.toInt 42 | |> Advent.unsafeMaybe "parseLine" 43 | in 44 | case List.map parseLine (String.lines string) of 45 | [ a, b ] -> 46 | ( a, b ) 47 | 48 | _ -> 49 | Debug.todo "parse1" 50 | 51 | 52 | parse2 : String -> Input2 53 | parse2 string = 54 | parse1 string 55 | 56 | 57 | 58 | -- 3. COMPUTE (actually solve the problem) 59 | 60 | 61 | type alias State1 = 62 | { n : Int 63 | , rolls : Int 64 | , space : Int 65 | , otherSpace : Int 66 | , score : Int 67 | , otherScore : Int 68 | } 69 | 70 | 71 | biasedModBy : Int -> Int -> Int 72 | biasedModBy m n = 73 | let 74 | newN = 75 | modBy m n 76 | in 77 | if newN == 0 then 78 | m 79 | 80 | else 81 | newN 82 | 83 | 84 | compute1 : Input1 -> Output1 85 | compute1 ( p1Space, p2Space ) = 86 | let 87 | go : State1 -> Int 88 | go s = 89 | let 90 | nextRolls = 91 | s.rolls + 3 92 | 93 | nextN = 94 | (s.n + 3) |> biasedModBy 100 95 | 96 | rolledSum = 97 | List.range (s.n + 1) (s.n + 3) 98 | |> List.map (biasedModBy 100) 99 | |> List.sum 100 | 101 | nextSpace = 102 | (s.space + rolledSum) 103 | |> biasedModBy 10 104 | 105 | nextScore = 106 | s.score + nextSpace 107 | in 108 | if nextScore >= 1000 then 109 | s.otherScore * nextRolls 110 | 111 | else 112 | go 113 | { n = nextN 114 | , rolls = nextRolls 115 | 116 | -- we're switching 117 | , space = s.otherSpace 118 | , otherSpace = nextSpace 119 | , score = s.otherScore 120 | , otherScore = nextScore 121 | } 122 | in 123 | go 124 | { n = 0 125 | , rolls = 0 126 | , space = p1Space 127 | , otherSpace = p2Space 128 | , score = 0 129 | , otherScore = 0 130 | } 131 | 132 | 133 | compute2 : Input2 -> Output2 134 | compute2 ( p1Space, p2Space ) = 135 | -1 136 | 137 | 138 | 139 | -- 4. TESTS (uh-oh, is this problem a hard one?) 140 | 141 | 142 | tests1 : List (Test Input1 Output1) 143 | tests1 = 144 | [ Test "example" 145 | """Player 1 starting position: 4 146 | Player 2 starting position: 8""" 147 | Nothing 148 | 739785 149 | ] 150 | 151 | 152 | tests2 : List (Test Input2 Output2) 153 | tests2 = 154 | [ Test "example" 155 | """Player 1 starting position: 4 156 | Player 2 starting position: 8""" 157 | Nothing 158 | 444356092776315 159 | ] 160 | 161 | 162 | 163 | -- BOILERPLATE (shouldn't have to touch this) 164 | 165 | 166 | input_ : String 167 | input_ = 168 | """ 169 | Player 1 starting position: 7 170 | Player 2 starting position: 4 171 | """ 172 | |> Advent.removeNewlinesAtEnds 173 | 174 | 175 | main : Program () ( Output1, Output2 ) Never 176 | main = 177 | Advent.program 178 | { input = input_ 179 | , parse1 = parse1 180 | , parse2 = parse2 181 | , compute1 = compute1 182 | , compute2 = compute2 183 | , tests1 = tests1 184 | , tests2 = tests2 185 | } 186 | -------------------------------------------------------------------------------- /src/Year2022/Day01.kt: -------------------------------------------------------------------------------- 1 | class Day01 { 2 | companion object { 3 | const val inputFile = "input202201.txt" 4 | 5 | fun parse1(input: String) = 6 | input 7 | .split("\n\n") 8 | .map { it.lines().map { it.toInt() } } 9 | 10 | fun parse2(input: String) = parse1(input) 11 | 12 | fun compute1(input: List>) = 13 | input.maxOf { it.sum() } 14 | 15 | fun compute2(input: List>) = 16 | input 17 | .map { it.sum() }.sortedDescending() 18 | .take(3).sum() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Year2022/Day02.kt: -------------------------------------------------------------------------------- 1 | import Day02.Companion.Hand.* 2 | import Day02.Companion.Outcome.* 3 | 4 | class Day02 { 5 | companion object { 6 | const val inputFile = "input202202.txt" 7 | 8 | fun parse1(input: String): List> = 9 | input.lines().map { it.split(" ") } 10 | 11 | fun parse2(input: String) = parse1(input) 12 | 13 | fun compute1(input: List>) = 14 | input.sumOf { scoreRound1(it) } 15 | 16 | fun compute2(input: List>) = 17 | input.sumOf { scoreRound2(it) } 18 | 19 | enum class Hand { Rock, Paper, Scissors } 20 | enum class Outcome { Lose, Draw, Win } 21 | 22 | private fun scoreRound1(round: List): Int { 23 | val elf = elfHand[round[0]]!! 24 | val you = p1Hand[round[1]]!! 25 | val handScore = handScores[you]!! 26 | val outcome = outcomeFromHands[elf to you]!! 27 | val outcomeScore = outcomeScores[outcome]!! 28 | return handScore + outcomeScore 29 | } 30 | 31 | private fun scoreRound2(round: List): Int { 32 | val elf = elfHand[round[0]]!! 33 | val outcome = p2Outcome[round[1]]!! 34 | val outcomeScore = outcomeScores[outcome]!! 35 | val you = handFromOutcome[elf to outcome]!! 36 | val handScore = handScores[you]!! 37 | return handScore + outcomeScore 38 | } 39 | 40 | private val elfHand = mapOf("A" to Rock, "B" to Paper, "C" to Scissors) 41 | private val p1Hand = mapOf("X" to Rock, "Y" to Paper, "Z" to Scissors) 42 | private val p2Outcome = mapOf("X" to Lose, "Y" to Draw, "Z" to Win) 43 | private val handScores = mapOf(Rock to 1, Paper to 2, Scissors to 3) 44 | private val outcomeScores = mapOf(Lose to 0, Draw to 3, Win to 6) 45 | private val outcomeFromHands = mapOf( // (Elf to You) to Outcome 46 | (Rock to Rock) to Draw, (Rock to Paper) to Win, (Rock to Scissors) to Lose, 47 | (Paper to Rock) to Lose, (Paper to Paper) to Draw, (Paper to Scissors) to Win, 48 | (Scissors to Rock) to Win, (Scissors to Paper) to Lose, (Scissors to Scissors) to Draw, 49 | ) 50 | private val handFromOutcome = mapOf( // (Elf to Outcome) to You 51 | (Rock to Draw) to Rock, (Rock to Win) to Paper, (Rock to Lose) to Scissors, 52 | (Paper to Lose) to Rock, (Paper to Draw) to Paper, (Paper to Win) to Scissors, 53 | (Scissors to Win) to Rock, (Scissors to Lose) to Paper, (Scissors to Draw) to Scissors, 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Year2022/Day03.kt: -------------------------------------------------------------------------------- 1 | class Day03 { 2 | companion object { 3 | const val inputFile = "input202203.txt" 4 | 5 | fun parse1(input: String): List> = 6 | input.lines().map { 7 | val len = it.length 8 | val len2 = len/2 9 | val fst = it.substring(0,len2) 10 | val snd = it.substring(len2) 11 | fst to snd 12 | } 13 | fun parse2(input: String) = 14 | input.lines().chunked(3) 15 | fun compute1(input: List>) = 16 | input.sumOf { compute1Single(it) } 17 | fun compute2(input: List>) = 18 | input.sumOf { compute2Group(it) } 19 | 20 | private fun compute1Single(pair: Pair): Int { 21 | val (fst,snd) = pair 22 | val char = fst.filter { snd.contains(it) }[0] 23 | return priority(char) 24 | } 25 | private fun compute2Group(group: List): Int { 26 | val sets = group.map { it.toSet() } 27 | val common = sets.reduce { a,b -> a.intersect(b) } 28 | return priority(common.first()) 29 | } 30 | private fun priority(char: Char) = 31 | if (char.isUpperCase()) 32 | char.code - 'A'.code + 27 33 | else 34 | char.code - 'a'.code + 1 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Year2022/Day04.kt: -------------------------------------------------------------------------------- 1 | class Day04 { 2 | companion object { 3 | const val inputFile = "input202204.txt" 4 | 5 | fun parse1(input: String) = 6 | input.lines().map { 7 | val list = it.split(",").map { it.split("-") } 8 | (list[0][0].toInt() to list[0][1].toInt()) to (list[1][0].toInt() to list[1][1].toInt()) 9 | } 10 | fun parse2(input: String) = parse1(input) 11 | fun compute1(input: List,Pair>>) = 12 | input.count { containsFully(it.first,it.second) || containsFully(it.second,it.first) } 13 | fun compute2(input: List,Pair>>) = 14 | input.count { contains(it.first,it.second) || contains(it.second,it.first) } 15 | 16 | private fun containsFully(big: Pair, small: Pair) = 17 | big.first <= small.first && big.second >= small.second 18 | private fun contains(big: Pair, small: Pair) = 19 | big.first <= small.first && big.second >= small.first 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Year2022/Day05.kt: -------------------------------------------------------------------------------- 1 | class Day05 { 2 | companion object { 3 | const val inputFile = "input202205.txt" 4 | 5 | fun parse1(input: String): Pair>,List>> { 6 | val nl = input.split("\n\n") 7 | val towerStr = nl[0] 8 | val transp = towerStr.lines().map { it.toList() }.transpose() 9 | val stacks = transp 10 | .filter { it.any { it.isLetter() } } 11 | .map { it.filter { it.isLetter() } } 12 | val instructions = nl[1].lines().map { parseInstruction(it) } 13 | return Pair(stacks, instructions) 14 | } 15 | fun parse2(input: String) = parse1(input) 16 | fun compute1(input: Pair>, List>>): String { 17 | val (stacks, instructions) = input 18 | val endStacks = instructions.fold(stacks) { acc, instr -> runInstruction(instr,acc) } 19 | return endStacks.map { it[0] }.joinToString("") 20 | } 21 | fun compute2(input: Pair>, List>>): String { 22 | val (stacks, instructions) = input 23 | val endStacks = instructions.fold(stacks) { acc, instr -> runInstruction(instr,acc,false) } 24 | return endStacks.map { it[0] }.joinToString("") 25 | } 26 | 27 | private fun parseInstruction(str: String): Triple { 28 | val words = str.split(" ") 29 | val count = words[1].toInt() 30 | val src = words[3].toInt() - 1 31 | val dst = words[5].toInt() - 1 32 | return Triple(count,src,dst) 33 | } 34 | 35 | private fun runInstruction(instr: Triple, stacks: List>, reverse: Boolean = true): List> { 36 | val (count, src, dst) = instr 37 | val taken = stacks[src].take(count) 38 | val takenReversed = if (reverse) taken.reversed() else taken 39 | val newStacks = stacks.toMutableList() 40 | newStacks[src] = newStacks[src].drop(count) 41 | newStacks[dst] = takenReversed + newStacks[dst] 42 | return newStacks.toList() 43 | } 44 | } 45 | } 46 | 47 | fun List>.transpose(): List> { 48 | // [ [1,2,3,4], [5,6,7,8] ] 49 | // -> 50 | // [ [1,5], [2,6], [3,7], [4,8] ] 51 | val outerLength = this.size 52 | val innerLength = this.maxOf { it.size } 53 | val xs = mutableListOf>() 54 | for (i in 0 until innerLength) { 55 | xs.add(mutableListOf()) 56 | } 57 | for (o in 0 until outerLength) { 58 | for (i in 0 until innerLength) { 59 | xs[i].add(this[o][i]) 60 | } 61 | } 62 | return xs.map { it.toList() }.toList() 63 | } -------------------------------------------------------------------------------- /src/Year2022/Day06.kt: -------------------------------------------------------------------------------- 1 | class Day06 { 2 | companion object { 3 | const val inputFile = "input202206.txt" 4 | 5 | fun parse1(input: String) = input 6 | fun parse2(input: String) = input 7 | fun compute1(input: String): Int = go(4,0,"",input) 8 | fun compute2(input: String): Int = go(14,0,"",input) 9 | 10 | private fun go(len: Int, n: Int, acc: String, rest: String): Int { 11 | if (acc.toSet().size == len) return n 12 | val next = rest[0] 13 | val newRest = rest.substring(1) 14 | val newAcc = (acc + next).takeLast(len) 15 | return go(len,n+1,newAcc,newRest) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Year2022/Day07.kt: -------------------------------------------------------------------------------- 1 | class Day07 { 2 | companion object { 3 | const val inputFile = "input202207.txt" 4 | 5 | sealed class FsEntry 6 | data class Dir(val name: String): FsEntry() 7 | data class File(val name: String, val size: Int): FsEntry() 8 | 9 | fun parse1(input: String): Map> { 10 | fun go( 11 | accPath: List, 12 | accEntries: List, 13 | accMap: Map>, 14 | lines: List 15 | ): Pair>,List> { 16 | // base cases 17 | if (lines.isEmpty() || lines[0] == "$ cd ..") { 18 | return if (accPath == listOf("")) { 19 | // finish! 20 | Pair(accMap + ("/" to accEntries), lines.drop(1)) 21 | } else { 22 | val path = accPath.joinToString("/") 23 | val newMap = accMap + (path to accEntries) 24 | // finish nested case 25 | Pair(newMap, lines.drop(1)) 26 | } 27 | } 28 | // recursive cases 29 | val line = lines[0] 30 | if (line == "$ ls") return go(accPath, accEntries, accMap, lines.drop(1)) 31 | if (line.startsWith("$ cd ")) { 32 | val name = line.substring(5) 33 | val (entries, rest) = go(accPath + name, listOf(), mapOf(), lines.drop(1)) 34 | return go(accPath, accEntries, accMap + entries, rest) 35 | } 36 | if (line.startsWith("dir ")) { 37 | val name = line.substring(4) 38 | return go(accPath, accEntries + Dir(name), accMap, lines.drop(1)) 39 | } 40 | // only possibility left is the file entry 41 | val split = line.split(" ") 42 | val size = split[0].toInt() 43 | val name = split[1] 44 | return go(accPath, accEntries + File(name, size), accMap, lines.drop(1)) 45 | } 46 | val (map, rest) = go(listOf(""),listOf(),mapOf(),input.lines().drop(1)) // special casing for `cd /` 47 | assert(rest.isEmpty()) 48 | return map 49 | } 50 | 51 | fun parse2(input: String) = parse1(input) 52 | 53 | fun compute1(input: Map>): Int { 54 | val dirSizes = computeDirSizes(input) 55 | return dirSizes.filterValues { it <= 100_000 }.values.sum() 56 | } 57 | 58 | fun compute2(input: Map>): Int { 59 | val dirSizes = computeDirSizes(input) 60 | val total = 70_000_000 61 | val needed = 30_000_000 62 | val maxUsed = total - needed 63 | val used = dirSizes["/"]!! 64 | val bigEnoughDirs = dirSizes.filterValues { used - it <= maxUsed } 65 | val dirToDelete = bigEnoughDirs.minBy { it.value } 66 | return dirToDelete.value 67 | } 68 | 69 | private fun computeDirSizes(input: Map>): Map { 70 | fun go(accDirSizes: Map, cwd: List, todo: List): Map { 71 | if (todo.isEmpty()) { 72 | return accDirSizes 73 | } 74 | val path = 75 | if (cwd == listOf("")) "/" 76 | else cwd.joinToString("/") 77 | val newTodo = todo.drop(1) 78 | if (accDirSizes.containsKey(path)) { 79 | // already done! 80 | return go(accDirSizes, cwd, newTodo) 81 | } 82 | val entries = input[path]!! 83 | val (newDirSizes, thisSize) = entries.fold(Pair(accDirSizes, 0)) { (currentDirSizes, accSize), entry -> 84 | when (entry) { 85 | is File -> Pair(currentDirSizes, accSize + entry.size) 86 | is Dir -> { 87 | val newCwd = cwd + entry.name 88 | val resolved = go(currentDirSizes, newCwd, listOf(entry.name)) 89 | val pathWithName = newCwd.joinToString("/") 90 | val newDirSize = resolved[pathWithName]!! 91 | Pair(resolved, accSize + newDirSize) 92 | } 93 | } 94 | } 95 | return go(newDirSizes + (path to thisSize), cwd, newTodo) 96 | } 97 | return go(mapOf(),listOf(""),input.keys.toList()) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Year2022/Day08.kt: -------------------------------------------------------------------------------- 1 | class Day08 { 2 | companion object { 3 | const val inputFile = "input202208.txt" 4 | 5 | fun parse1(input: String) = input.lines().map { it.toList().map { it.digitToInt() } } 6 | fun parse2(input: String) = parse1(input) 7 | fun compute1(input: List>): Int { 8 | val w = input[0].size 9 | val h = input.size 10 | return input 11 | .mapIndexed { y,row -> row.mapIndexed { x,tree -> isTreeVisible(tree, x, y, input, w, h) } } 12 | .flatten().count { it } 13 | } 14 | fun compute2(input: List>): Int { 15 | val w = input[0].size 16 | val h = input.size 17 | return input 18 | .mapIndexed { y,row -> row.mapIndexed { x,tree -> scenicScore(tree, x, y, input, w, h) } } 19 | .flatten().maxOf { it } 20 | } 21 | 22 | private fun isTreeVisible(tree: Int, x: Int, y: Int, input: List>, w: Int, h: Int): Boolean { 23 | val left = (0 until x).all { input[y][it] < tree } 24 | val up = (0 until y).all { input[it][x] < tree } 25 | val right = ((x + 1) until w).all { input[y][it] < tree } 26 | val down = ((y + 1) until h).all { input[it][x] < tree } 27 | return left || up || right || down 28 | } 29 | 30 | private fun scenicScore(tree: Int, x: Int, y: Int, input: List>, w: Int, h: Int): Int { 31 | val fix: (Int, IntProgression) -> Int = { n, range -> if (n == 0) 0 else if (n == range.count()) n else n + 1 } 32 | val iLeft = x-1 downTo 0 33 | val iUp = y - 1 downTo 0 34 | val iRight = (x + 1) until w 35 | val iDown = (y + 1) until h 36 | val left = fix(iLeft.takeWhile { input[y][it] < tree }.size, iLeft) 37 | val up = fix(iUp.takeWhile { input[it][x] < tree }.size, iUp) 38 | val right = fix(iRight.takeWhile { input[y][it] < tree }.size, iRight) 39 | val down = fix(iDown.takeWhile { input[it][x] < tree }.size, iDown) 40 | return left * up * right * down 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Year2022/Day10.kt: -------------------------------------------------------------------------------- 1 | class Day10 { 2 | companion object { 3 | const val inputFile = "input202210.txt" 4 | 5 | fun parse1(input: String) = input.lines().map { parseInstruction(it) } 6 | fun parse2(input: String) = parse1(input) 7 | fun compute1(input: List): Int { 8 | return input.fold(initCpu to 0) { (cpu, strength), inst -> 9 | val newCpu = update1(inst, cpu) 10 | val cycleNum: Int? = findCycleNum(cpu, newCpu) 11 | val newStrength = 12 | if (cycleNum != null) 13 | strength + cycleNum * cpu.regX 14 | else 15 | strength 16 | newCpu to newStrength 17 | }.second 18 | } 19 | 20 | fun compute2(input: List): String { 21 | val width = 40 22 | val height = 6 23 | val result = input.fold(initCpu to setOf>()) { (cpu, display), inst -> 24 | val newCpu = update1(inst, cpu) 25 | val newPixels: List> = 26 | (cpu.ticks until newCpu.ticks) 27 | .filter { hit(it % width, cpu.regX) } 28 | .map { it % width to it / width } 29 | val newDisplay = display + newPixels 30 | newCpu to newDisplay 31 | }.second 32 | return "\n" + 33 | (0 until height).joinToString("\n") { y -> 34 | (0 until width).joinToString("") { x -> 35 | if (result.contains(x to y)) "█" else "░" 36 | } 37 | } 38 | } 39 | 40 | private fun hit(x: Int, maskCenter: Int): Boolean = 41 | x in maskCenter-1..maskCenter+1 42 | 43 | // COMMON 44 | 45 | sealed class Instruction 46 | object NoOp : Instruction() 47 | data class AddX(val i: Int): Instruction() 48 | 49 | private fun parseInstruction(line: String): Instruction { 50 | val words = line.split(" ") 51 | return when (words[0]) { 52 | "noop" -> NoOp 53 | "addx" -> AddX(words[1].toInt()) 54 | else -> TODO("What") 55 | } 56 | } 57 | 58 | data class CPU( 59 | val regX: Int, 60 | val ticks: Int, 61 | ) 62 | 63 | private val initCpu = CPU(1, 0) 64 | 65 | // PART 1 66 | 67 | private fun update1(inst: Instruction, cpu: CPU) = 68 | when (inst) { 69 | NoOp -> cpu.copy(ticks = cpu.ticks + 1) 70 | is AddX -> CPU(cpu.regX + inst.i, cpu.ticks + 2) 71 | } 72 | 73 | private val cycles = listOf(20,60,100,140,180,220) 74 | private fun findCycleNum(cpu: CPU, newCpu: CPU): Int? = 75 | cycles.firstOrNull { it > cpu.ticks && it <= newCpu.ticks } 76 | 77 | // PART 2 78 | 79 | 80 | } 81 | } -------------------------------------------------------------------------------- /src/Year2022/Day11.kt: -------------------------------------------------------------------------------- 1 | import Day11.Companion.Op.Add 2 | import Day11.Companion.Op.Mul 3 | 4 | class Day11 { 5 | companion object { 6 | const val inputFile = "input202211.txt" 7 | 8 | fun parse1(input: String): List = input.split(Regex("\n\n")).map { parseMonkey(it) } 9 | fun parse2(input: String) = parse1(input) 10 | fun compute1(initMonkeys: List): Long { 11 | val lcm = initMonkeys.map { it.div }.fold(1) { acc,n -> acc * n } 12 | return go1(20,mapOf(),initMonkeys, 3, lcm) 13 | } 14 | fun compute2(initMonkeys: List): Long { 15 | val lcm = initMonkeys.map { it.div }.fold(1) { acc, n -> acc * n } 16 | return go1(10000, mapOf(), initMonkeys, 1, lcm) 17 | } 18 | 19 | // TYPES 20 | 21 | data class Monkey( 22 | val id: Int, 23 | val items: List, 24 | val fn: Pair, 25 | val div: Int, 26 | val ifTrue: Int, 27 | val ifFalse: Int, 28 | ) 29 | 30 | enum class Op { Add, Mul } 31 | 32 | sealed class What 33 | object Old: What() 34 | data class Num(val n: Int): What() 35 | 36 | // PARSING 37 | 38 | private fun parseMonkey(input: String): Monkey { 39 | val lines = input.lines().map { it.trim().split(" ") } 40 | return Monkey( 41 | id = lines[0][1].filter { it.isDigit() }.toInt(), 42 | items = lines[1].map { it.filter { it.isDigit() } }.filterNot { it.isEmpty() }.map { it.toInt() }, 43 | fn = parseFn(lines[2].drop(4)), 44 | div = lines[3][3].toInt(), 45 | ifTrue = lines[4][5].toInt(), 46 | ifFalse = lines[5][5].toInt(), 47 | ) 48 | } 49 | 50 | private fun parseFn(expr: List): Pair { 51 | val op = when (expr[0]) { 52 | "+" -> Add 53 | "*" -> Mul 54 | else -> TODO("parseFn: unknown operator") 55 | } 56 | val what = when (expr[1]) { 57 | "old" -> Old 58 | else -> Num(expr[1].toInt()) 59 | } 60 | return op to what 61 | } 62 | 63 | // PART 1 64 | 65 | private tailrec fun go1(cyclesLeft: Int, inspectCounter: Map, monkeys: List, divisor: Int, lcm: Int): Long { 66 | return if (cyclesLeft <= 0) { 67 | inspectCounter.toList().sortedByDescending { it.second }.take(2).fold(1) { acc, (_,n) -> acc * n } 68 | } 69 | else { 70 | val (newCounter, newMonkeys) = go1Cycle(monkeys.map { it.id }, inspectCounter, monkeys, divisor, lcm) 71 | go1(cyclesLeft - 1, newCounter, newMonkeys, divisor, lcm) 72 | } 73 | } 74 | 75 | private tailrec fun go1Cycle(todoMonkeyIds: List, inspectCounter: Map, monkeys: List, divisor: Int, lcm: Int): Pair,List> { 76 | return if (todoMonkeyIds.isEmpty()) 77 | inspectCounter to monkeys 78 | else { 79 | val monkeyId = todoMonkeyIds[0] 80 | val monkey = monkeys[monkeyId] 81 | val (newCounter, newMonkeys) = go1Item(monkey.items, monkey, inspectCounter, monkeys, divisor, lcm) 82 | go1Cycle(todoMonkeyIds.drop(1), newCounter, newMonkeys, divisor, lcm) 83 | } 84 | } 85 | 86 | private tailrec fun go1Item(todoItems: List, monkey: Monkey, inspectCounter: Map, monkeys: List, divisor: Int, lcm: Int): Pair,List> { 87 | return if (todoItems.isEmpty()) 88 | inspectCounter to monkeys 89 | else { 90 | val item = todoItems[0] 91 | val afterInspection = evalFn(item, monkey.fn, lcm) / divisor // already %'d! 92 | val throwTo = 93 | if (afterInspection % monkey.div == 0) monkey.ifTrue 94 | else monkey.ifFalse 95 | val newCounter = inspectCounter + (monkey.id to (inspectCounter[monkey.id]?.plus(1) ?: 1)) 96 | val newMonkeys = monkeys.mapIndexed { i, m -> 97 | when (i) { 98 | throwTo -> m.copy(items = m.items + afterInspection) 99 | monkey.id -> m.copy(items = m.items.drop(1)) 100 | else -> m 101 | } 102 | } 103 | go1Item(todoItems.drop(1), monkey, newCounter, newMonkeys, divisor, lcm) 104 | } 105 | } 106 | 107 | private fun evalFn(item: Int, fn: Pair, lcm: Int): Int { 108 | val (op, what) = fn 109 | return when (op) { 110 | Add -> when (what) { 111 | Old -> (item + item) % lcm 112 | is Num -> (item + what.n) % lcm 113 | } 114 | Mul -> when (what) { 115 | Old -> item.toBigInteger().modPow(2.toBigInteger(), lcm.toBigInteger()).toInt() 116 | is Num -> (item * what.n) % lcm 117 | } 118 | } 119 | } 120 | 121 | } 122 | } -------------------------------------------------------------------------------- /src/Year2022/Day12.kt: -------------------------------------------------------------------------------- 1 | import java.util.PriorityQueue 2 | 3 | class Day12 { 4 | companion object { 5 | const val inputFile = "input202212.txt" 6 | 7 | fun parse1(input: String): Input = parseGrid(input) 8 | fun parse2(input: String) = parse1(input) 9 | fun compute1(input: Input): Int { 10 | val costs = dijkstra(input, input.start) 11 | return costs[input.end.first][input.end.second] 12 | } 13 | 14 | fun compute2(input: Input): Int { 15 | return findAll(1, input.grid).minOf { 16 | val costs = dijkstra(input, it) 17 | costs[input.end.first][input.end.second] 18 | } 19 | } 20 | 21 | private fun dijkstra(input: Input, init: Pair): List> { 22 | val height = input.grid.size 23 | val width = input.grid[0].size 24 | val costs: MutableList> = MutableList(width) { MutableList(height) { Int.MAX_VALUE } } 25 | val visited: MutableSet> = mutableSetOf() 26 | val queue = PriorityQueue() 27 | queue.add(TodoCell(pos = init, cost = 0)) 28 | costs[init.first][init.second] = 0 29 | while (!visited.contains(input.end) && queue.isNotEmpty()) { 30 | val cell = queue.poll() 31 | val (curX,curY) = cell.pos 32 | if (visited.contains(cell.pos)) continue 33 | visited.add(cell.pos) 34 | listOf(-1 to 0, 1 to 0, 0 to -1, 0 to 1) 35 | .map { addPos(cell.pos, it) } 36 | .filter { isInBounds(it, width, height) && !visited.contains(it) } 37 | .forEach { 38 | val (newX,newY) = it 39 | val curHeight = input.grid[curY][curX] 40 | val newHeight = input.grid[newY][newX] 41 | if (newHeight - curHeight <= 1) { 42 | val newCost = minOf( 43 | costs[newX][newY], 44 | costs[curX][curY] + 1, 45 | ) 46 | costs[newX][newY] = newCost 47 | queue.add(TodoCell(pos = it, cost = newCost)) 48 | } 49 | } 50 | } 51 | return costs.map{ it.toList() }.toList() 52 | } 53 | 54 | private fun isInBounds(pos: Pair, width: Int, height: Int): Boolean = 55 | (pos.first in 0 until width) && (pos.second in 0 until height) 56 | 57 | private fun addPos(pos: Pair, delta: Pair) = 58 | pos.first + delta.first to pos.second + delta.second 59 | 60 | data class TodoCell( 61 | val pos: Pair, 62 | val cost: Int, 63 | ) : Comparable { 64 | override fun compareTo(other: TodoCell): Int = 65 | compareValuesBy(this, other) { it.cost } 66 | } 67 | 68 | data class Input( 69 | val grid: List>, 70 | val start: Pair, 71 | val end: Pair, 72 | ) 73 | 74 | private fun parseGrid(input: String): Input { 75 | val grid = 76 | input.lines() 77 | .map { line -> 78 | line.map { char -> 79 | when (char) { 80 | 'S' -> 0 81 | 'E' -> 27 82 | else -> char - 'a' + 1 83 | } 84 | } 85 | } 86 | 87 | val start = find(0,grid) 88 | val end = find(27,grid) 89 | 90 | val fixedGrid = grid.map { it.map { when (it) { 91 | 0 -> 1 92 | 27 -> 26 93 | else -> it 94 | }}} 95 | 96 | return Input(fixedGrid,start,end) 97 | } 98 | 99 | private fun findAll(n: Int, grid: List>): List> = 100 | grid.flatMapIndexed { y, row -> 101 | row.flatMapIndexed { x, it -> 102 | if (it == n) listOf(x to y) else listOf() 103 | } 104 | } 105 | 106 | private fun find(n: Int, grid: List>): Pair = 107 | findAll(n, grid).first() 108 | } 109 | } -------------------------------------------------------------------------------- /src/Year2022/Day15.kt: -------------------------------------------------------------------------------- 1 | import java.math.BigInteger 2 | import kotlin.math.abs 3 | import kotlin.math.max 4 | 5 | class Day15 { 6 | companion object { 7 | const val inputFile = "input202215.txt" 8 | 9 | val exampleLine = 10 10 | val realLine = 2000000 11 | 12 | val exampleMax = 20 13 | val realMax = 4000000 14 | 15 | val line = realLine 16 | val max = realMax 17 | 18 | data class Input( 19 | val list: List> 20 | ) 21 | 22 | fun parse1(input: String): Input = Input(input.lines().map { parseLine(it) }) 23 | fun parse2(input: String) = parse1(input) 24 | private fun parseLine(line: String): Pair { 25 | val ints = line.split(" ").filter { it.any { c -> c == '-' || c.isDigit() }}.map { it.filter { c -> c == '-' || c.isDigit() }.toInt() } 26 | return (ints[0] to ints[1]) to (ints[2] to ints[3]) 27 | } 28 | 29 | fun compute1(input: Input): Int { 30 | val beacons = input.list.filter { it.second.y == line }.map { it.second.x }.toSet() 31 | val positions = input.list.fold(setOf()) { acc,reading -> acc.union(positionsAt(line, reading)) } 32 | return (positions - beacons).size 33 | } 34 | 35 | private fun positionsAt(line: Int, reading: Pair): Set = 36 | rangeAt(line, reading)?.toSet() ?: setOf() 37 | 38 | private fun rangeAt(y: Int, reading: Pair): IntRange? { 39 | val dist = reading.manhattanDistance() 40 | return if (y in (reading.first.y - dist)..(reading.first.y + dist)) { 41 | val lineDist = abs(reading.first.y - y) 42 | val toEachSide = dist - lineDist 43 | return (reading.first.x - toEachSide)..(reading.first.x + toEachSide) 44 | } else null 45 | } 46 | 47 | fun compute2(input: Input): BigInteger { 48 | val range = 0..max 49 | for (y in range) { 50 | val ranges = input.list.mapNotNull { rangeAt(y, it) }.sortedBy { it.first } 51 | val x = go2(y, 0, ranges) 52 | if (x != null) { 53 | return x.toBigInteger() * 4000000 + y 54 | } 55 | } 56 | TODO("We should have found a beacon!") 57 | } 58 | 59 | private tailrec fun go2(y: Int, x: Int, todo: List): Int? = 60 | if (x > max) null 61 | else { 62 | if (todo.isEmpty()) null 63 | else { 64 | val range = todo.first() 65 | if (range.first <= x + 1) go2(y, max(x, range.last), todo.drop(1)) 66 | else range.first - 1 67 | } 68 | } 69 | } 70 | } 71 | 72 | operator fun BigInteger.times(i: Int): BigInteger = 73 | this.times(i.toBigInteger()) 74 | 75 | operator fun BigInteger.plus(i: Int): BigInteger = 76 | this.plus(i.toBigInteger()) 77 | 78 | private fun IntRange.times(other: IntRange): List> = 79 | this.flatMap { a -> other.map { b -> a to b } } 80 | 81 | private fun Pair.manhattanDistance(): Int = 82 | abs(this.first.x - this.second.x) + abs(this.first.y - this.second.y) -------------------------------------------------------------------------------- /src/Year2022/Day18.kt: -------------------------------------------------------------------------------- 1 | import kotlin.math.abs 2 | import kotlin.math.pow 3 | import kotlin.math.sqrt 4 | 5 | class Day18 { 6 | companion object { 7 | const val inputFile = "input202218.txt" 8 | 9 | fun parse1(input: String): List = input.lines().map { parseLine(it) } 10 | fun parse2(input: String) = parse1(input) 11 | 12 | private fun parseLine(line: String): XYZ { 13 | val ints = line.split(",").map { it.toInt() } 14 | val (x,y,z) = ints 15 | return Triple(x,y,z) 16 | } 17 | 18 | fun compute1(input: List): Int = 19 | allSides(input).size 20 | 21 | fun compute2(input: List): Int { 22 | val set = input.toSet() 23 | val min = input.flatMap { it.toList() }.minOf { it } - 1 24 | val max = input.flatMap { it.toList() }.maxOf { it } + 1 25 | val range = min..max 26 | val start = Triple(min,min,min) 27 | val todos = mutableSetOf(start) 28 | val outside = mutableSetOf() 29 | val seen = mutableSetOf() 30 | while (todos.isNotEmpty()) { 31 | val todo = todos.first() 32 | todos -= todo 33 | val neighbours = todo.neighbours().filter { it.x in range && it.y in range && it.z in range } 34 | if (neighbours.any { it in set }) outside += todo 35 | todos += neighbours.filter { it !in set && it !in seen } 36 | seen += todo 37 | } 38 | return allSides(seen.toList()).intersect(allSides(input)).size 39 | } 40 | 41 | private fun sides(point: XYZ): List> { 42 | val (a,b,c) = point 43 | val m = Triple(a,b,c) 44 | val n = Triple(a,b,c+1) 45 | val o = Triple(a+1,b,c+1) 46 | val p = Triple(a+1,b,c) 47 | val q = Triple(a,b+1,c) 48 | val r = Triple(a,b+1,c+1) 49 | val s = Triple(a+1,b+1,c+1) 50 | val t = Triple(a+1,b+1,c) 51 | return listOf( 52 | setOf(m,n,o,p), 53 | setOf(m,n,r,q), 54 | setOf(n,o,s,r), 55 | setOf(o,p,t,s), 56 | setOf(p,m,q,t), 57 | setOf(q,r,s,t), 58 | ) 59 | } 60 | 61 | private fun allSides(input: List): Set> = 62 | input.flatMap { sides(it) }.frequencies().filterValues { it == 1 }.keys 63 | } 64 | } 65 | 66 | typealias XYZ = Triple 67 | 68 | val XYZ.x: Int get() { return first } 69 | val XYZ.y: Int get() { return second } 70 | val XYZ.z: Int get() { return third } 71 | 72 | fun XYZ.addX(x: Int): XYZ = this.copy(first = this.first + x) 73 | fun XYZ.addY(y: Int): XYZ = this.copy(second = this.second + y) 74 | fun XYZ.addZ(z: Int): XYZ = this.copy(third = this.third + z) 75 | 76 | fun XYZ.neighbours(): Set = 77 | setOf( 78 | this.addX(1), 79 | this.addX(-1), 80 | this.addY(1), 81 | this.addY(-1), 82 | this.addZ(1), 83 | this.addZ(-1), 84 | ) 85 | 86 | fun XYZ.euclideanDistanceTo(other: XYZ): Double = 87 | sqrt( 88 | (this.x - other.x).toDouble().pow(2) 89 | + (this.y - other.y).toDouble().pow(2) 90 | + (this.z - other.z).toDouble().pow(2) 91 | ) 92 | 93 | fun XYZ.manhattanDistanceTo(other: XYZ): Int = 94 | abs(this.x - other.x) + 95 | abs(this.y - other.y) + 96 | abs(this.z - other.z) 97 | 98 | fun List.frequencies(): Map = 99 | this.groupingBy { it }.eachCount() -------------------------------------------------------------------------------- /src/Year2022/Day20.kt: -------------------------------------------------------------------------------- 1 | class Day20 { 2 | companion object { 3 | const val inputFile = "input202220.txt" 4 | 5 | fun parse1(input: String): List = input.lines().map { it.toLong() } 6 | fun parse2(input: String) = parse1(input) 7 | 8 | fun compute1(input: List): Long { 9 | val listWithIndexes = input.mapIndexed { i,n -> n to i.toLong() } 10 | val mixed = mix(listWithIndexes, listWithIndexes, 1) 11 | val zero = mixed.indexOfFirst { it.first == 0L } 12 | return mixed[(zero+1000)%input.size].first + mixed[(zero+2000)%input.size].first + mixed[(zero+3000)%input.size].first 13 | } 14 | fun compute2(input: List): Long { 15 | val key = 811589153L 16 | val listWithIndexes: List> = input.mapIndexed { i,n -> n to i.toLong() } 17 | var mixed = listWithIndexes 18 | println("initial: ${mixed.map { it.first * key }}") 19 | for (i in 1..10) { 20 | println("mix $i/10") 21 | mixed = mix(mixed, listWithIndexes, key) 22 | println("after mix: ${mixed.map { it.first * key }}") 23 | } 24 | val encrypted = mixed.map { it.first * key } 25 | val zero = encrypted.indexOfFirst { it == 0L } 26 | return encrypted[(zero+1000)%input.size] + encrypted[(zero+2000)%input.size] + encrypted[(zero+3000)%input.size] 27 | } 28 | 29 | private fun mix(list: List>, order: List>, k: Long): List> { 30 | return order.fold(list) { accList, x -> 31 | val (n,_) = x 32 | val nn = n * k 33 | val index = accList.indexOf(x) 34 | val newIndex1 = (index + nn).mod(list.size - 1) 35 | val newIndex = if (index != 0 && newIndex1 == 0) list.size - 1 else newIndex1 36 | val withoutN = accList.take(index) + accList.drop(index + 1) 37 | withoutN.take(newIndex) + listOf(x) + withoutN.drop(newIndex) 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/Year2022/Day23.kt: -------------------------------------------------------------------------------- 1 | import Day23.Companion.Entity.* 2 | import Day23.Companion.Dir.* 3 | import Day23.Companion.Dir 4 | 5 | class Day23 { 6 | companion object { 7 | const val inputFile = "input202223.txt" 8 | 9 | enum class Entity {Space,Elf} 10 | enum class Dir {N,S,W,E} 11 | 12 | fun parse1(input: String): Map = 13 | input.lines().mapIndexed { y,line -> 14 | line.mapIndexed { x,char -> 15 | val entity = when (char) { 16 | '.' -> Space 17 | '#' -> Elf 18 | else -> TODO("parse1") 19 | } 20 | (x.toLong() to y.toLong()) to entity 21 | } 22 | }.flatten().toMap() 23 | fun parse2(input: String) = parse1(input) 24 | 25 | private fun dirListForRound(round: Long): List

= 26 | when (round % 4) { 27 | 0L -> listOf(N,S,W,E) 28 | 1L -> listOf(S,W,E,N) 29 | 2L -> listOf(W,E,N,S) 30 | 3L -> listOf(E,N,S,W) 31 | else -> TODO("Math broke") 32 | } 33 | 34 | fun compute1(input: Map): Long { 35 | var round = 0L 36 | val map = input.toMutableMap() 37 | //println("init") 38 | //println(map.draw(".", showBounds = false) { when (it) { 39 | // Space -> "." 40 | // Elf -> "#" 41 | //} }) 42 | while (round < 10) { 43 | doRound(map,round) 44 | round++ 45 | } 46 | val elfXYs = map.filterValues { it == Elf }.keys 47 | val minY = elfXYs.minOf { it.y } 48 | val maxY = elfXYs.maxOf { it.y } 49 | val minX = elfXYs.minOf { it.x } 50 | val maxX = elfXYs.maxOf { it.x } 51 | //println("smushed") 52 | //println("X: $minX..$maxX, Y: $minY..$maxY") 53 | //println(map.filterValues {it == Elf}.draw(".") { when (it) { 54 | // Space -> "." 55 | // Elf -> "#" 56 | //} }) 57 | return (minX..maxX).times(minY..maxY) 58 | .count { (x,y) -> (map[x to y] ?: Space) != Elf } 59 | .toLong() 60 | } 61 | fun compute2(input: Map): Long { 62 | var round = 0L 63 | val map = input.toMutableMap() 64 | while (true) { 65 | val oldMap = map.toMap() 66 | doRound(map,round) 67 | round++ 68 | if (map == oldMap) break 69 | } 70 | return round 71 | } 72 | 73 | private fun doRound(map: MutableMap, round: Long) { 74 | val list = dirListForRound(round) 75 | val elves: List> = map.filterValues { it == Elf }.toList() 76 | val wantedXYs: Map = elves.map { 77 | if (it.first.neighbours().none { (map[it]?:Space) == Elf}) it.first 78 | else 79 | list.firstNotNullOfOrNull { dir -> 80 | if (it.first.cone(dir).any { (map[it] ?: Space) == Elf }) null 81 | else it.first.step(dir) 82 | } ?: it.first 83 | }.frequencies() 84 | val allowedXYs = wantedXYs.filterValues { it == 1 }.keys 85 | val wantedMoves: List> = elves.mapNotNull { 86 | val wantedXY = 87 | if (it.first.neighbours().none { (map[it]?:Space) == Elf}) null 88 | else 89 | list.firstNotNullOfOrNull { dir -> 90 | if (it.first.cone(dir).any { (map[it]?:Space) == Elf }) null 91 | else it.first.step(dir) 92 | } 93 | if (wantedXY != null && wantedXY in allowedXYs) it.first to wantedXY 94 | else null 95 | } 96 | wantedMoves.forEach { 97 | map -= it.first 98 | map += it.second to Elf 99 | } 100 | //println("end of round ${round + 1}: $list") 101 | //println(map.draw(".", showBounds = false) { 102 | // when (it) { 103 | // Space -> "." 104 | // Elf -> "#" 105 | // } 106 | //}) 107 | } 108 | } 109 | } 110 | 111 | fun XY.neighbours(): Set = 112 | setOf( 113 | this.decX().decY(), this.decY(), this.incX().decY(), 114 | this.decX(), this.incX(), 115 | this.decX().incY(), this.incY(), this.incX().incY(), 116 | ) 117 | 118 | private fun XY.step(dir: Dir): XY = 119 | when (dir) { 120 | N -> this.decY() 121 | S -> this.incY() 122 | W -> this.decX() 123 | E -> this.incX() 124 | } 125 | private fun XY.cone(dir: Dir): Set = 126 | when (dir) { 127 | N -> setOf(this.decY(),this.decY().incX(),this.decY().decX()) 128 | S -> setOf(this.incY(),this.incY().incX(),this.incY().decX()) 129 | W -> setOf(this.decX(),this.decX().incY(),this.decX().decY()) 130 | E -> setOf(this.incX(),this.incX().incY(),this.incX().decY()) 131 | } 132 | -------------------------------------------------------------------------------- /src/Year2022/Day25.kt: -------------------------------------------------------------------------------- 1 | import java.lang.Math.pow 2 | 3 | class Day25 { 4 | companion object { 5 | const val inputFile = "input202225.txt" 6 | 7 | fun parse1(input: String): List = input.lines() 8 | fun parse2(input: String) = parse1(input) 9 | 10 | // Do the rest with Mathematica: 11 | // FindInstance[n = a*5^0 + b*5^1 + ... && -2<=a<=2 && -2<=b<=2 && ..., {a,b,...}, Integers] 12 | fun compute1(input: List): Long = input.sumOf { convertFromSnafu(it) } 13 | fun compute2(input: List): String = "-3" 14 | 15 | private fun convertFromSnafu(line: String): Long = 16 | line.reversed().mapIndexed { i,c -> 17 | pow(5.0,i.toDouble()).toLong() * when (c) { 18 | '2' -> 2 19 | '1' -> 1 20 | '0' -> 0 21 | '-' -> -1 22 | '=' -> -2 23 | else -> TODO("huh") 24 | } 25 | }.sum() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Year2023/Day02.roc: -------------------------------------------------------------------------------- 1 | app "hello" 2 | packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } 3 | imports [ 4 | pf.Stdout, 5 | pf.Task, 6 | "../../input202302.txt" as realInput : Str 7 | ] 8 | provides [main] to pf 9 | 10 | 11 | main = 12 | {} <- part1 realInput |> Task.await 13 | part2 realInput 14 | #main = part1 testInput 15 | #main = part2 testInput 16 | 17 | parseRow = \rowStr -> 18 | when rowStr |> Str.split ": " is 19 | [idStr, logStr] -> 20 | id <- 21 | idStr 22 | |> # Missing Str.left or Str.dropLeft and so on 23 | Str.graphemes 24 | |> List.dropFirst 5 25 | |> List.walk "" Str.concat 26 | |> Str.toNat 27 | |> Result.try 28 | 29 | games <- 30 | logStr 31 | |> Str.split "; " 32 | |> List.mapTry parseDraw 33 | |> Result.try 34 | 35 | Ok { 36 | id: id, 37 | games: games, 38 | } 39 | 40 | _ -> crash "parse error" 41 | 42 | parseDraw = \drawStr -> 43 | drawStr 44 | |> Str.split ", " 45 | |> List.mapTry \colorDrawStr -> 46 | when colorDrawStr |> Str.split " " is 47 | [nStr, color] -> 48 | n <- Str.toNat nStr |> Result.try 49 | Ok (color,n) 50 | _ -> Err NoColor 51 | 52 | 53 | isValidPart1Row = \row -> 54 | row.games 55 | |> List.all \game -> 56 | game 57 | |> List.all \(color,n) -> 58 | if color == "red" then n <= 12 else 59 | if color == "green" then n <= 13 else 60 | if color == "blue" then n <= 14 else 61 | Bool.true 62 | 63 | findMinCount = \row -> 64 | find = \wantedColor -> 65 | row.games 66 | |> List.walk 0 (\acc,draw -> 67 | draw 68 | |> List.walk acc (\acc2,(color,n) -> 69 | if color == wantedColor then 70 | Num.max acc2 n 71 | else 72 | acc2 73 | ) 74 | ) 75 | 76 | { 77 | red: find "red", 78 | green: find "green", 79 | blue: find "blue", 80 | } 81 | 82 | power = \count -> 83 | count.red * count.green * count.blue 84 | 85 | parseInput = \input -> 86 | input 87 | |> Str.split "\n" 88 | |> List.keepIf (\s -> s |> Str.isEmpty |> Bool.not) 89 | |> List.mapTry parseRow 90 | |> Result.mapErr \err -> 91 | when err is 92 | InvalidNumStr -> -1 93 | NoColor -> -2 94 | |> Task.fromResult 95 | 96 | part1 = \input -> 97 | rows <- input |> parseInput |> Task.await 98 | rows 99 | |> List.keepIf isValidPart1Row 100 | |> List.map .id 101 | |> List.sum 102 | |> Num.toStr 103 | |> Stdout.line 104 | 105 | part2 = \input -> 106 | rows <- input |> parseInput |> Task.await 107 | rows 108 | |> List.map \x -> x |> findMinCount |> power 109 | |> List.sum 110 | |> Num.toStr 111 | |> Stdout.line 112 | -------------------------------------------------------------------------------- /src/Year2023/Day04.roc: -------------------------------------------------------------------------------- 1 | app "hello" 2 | packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" } 3 | imports [ 4 | pf.Stdout, 5 | "../../input202304.txt" as realInput : Str 6 | ] 7 | provides [main] to pf 8 | 9 | #main = part1 realInput 10 | main = part2 realInput 11 | #main = part1 testInput 12 | #main = part2 testInput 13 | 14 | parse : Str -> List {id: Nat, winning: Set Nat, your: Set Nat} 15 | parse = \input -> 16 | input 17 | |> Str.split "\n" 18 | |> List.dropIf (\s -> s == "") 19 | |> List.map parseRow 20 | 21 | dropFirst : Str,Nat -> Str 22 | dropFirst = \str,n -> 23 | str 24 | |> Str.graphemes 25 | |> List.dropFirst n 26 | |> List.walk "" Str.concat 27 | 28 | parseRow : Str -> Card 29 | parseRow = \line -> 30 | when line |> Str.split ": " is 31 | [cardStr, numsStr] -> 32 | id = 33 | cardStr 34 | |> dropFirst 5 35 | |> Str.trimStart 36 | |> Str.toNat 37 | |> Result.withDefault 999999999 38 | 39 | when numsStr |> Str.split " | " is 40 | [winningStr, yourStr] -> 41 | { 42 | id: id, 43 | winning: parseList winningStr, 44 | your: parseList yourStr, 45 | } 46 | _ -> crash "parseRow didn't see ' | '" 47 | _ -> crash "parseRow didn't see ': '" 48 | 49 | Card : 50 | { 51 | id: Nat, 52 | winning: Set Nat, 53 | your: Set Nat, 54 | } 55 | 56 | parseList : Str -> Set Nat 57 | parseList = \line -> 58 | line 59 | |> Str.split " " 60 | |> List.dropIf (\str -> str == "") 61 | |> List.mapTry Str.toNat 62 | |> Result.withDefault [] 63 | |> Set.fromList 64 | 65 | score : Card -> Nat 66 | score = \card -> 67 | Set.intersection card.winning card.your 68 | |> Set.len 69 | |> (\p -> 70 | if p == 0 then 71 | 0 72 | else 73 | Num.powInt 2 (Num.subSaturated p 1) 74 | ) 75 | 76 | go2 : List Card, Dict Nat Nat -> Nat 77 | go2 = \todos,inventory -> 78 | when todos is 79 | [todo, .. as rest] -> 80 | matches = Set.len (Set.intersection todo.winning todo.your) 81 | copies = Dict.get inventory todo.id |> Result.withDefault 88888888 82 | newInventory = 83 | if matches == 0 then 84 | inventory 85 | else 86 | List.range {start: At (todo.id + 1), end: At (todo.id + matches)} 87 | |> List.walk inventory (\accInv,id -> 88 | wonCurrentCopies = Dict.get accInv id |> Result.withDefault 7777777777 89 | Dict.insert accInv id (wonCurrentCopies + copies) 90 | ) 91 | 92 | go2 rest newInventory 93 | 94 | [] -> 95 | inventory 96 | |> Dict.values 97 | |> List.sum 98 | 99 | 100 | 101 | part1 = \input -> 102 | input 103 | |> parse 104 | |> List.map score 105 | |> List.sum 106 | |> Num.toStr 107 | |> Stdout.line 108 | 109 | part2 = \input -> 110 | cards = parse input 111 | initInventory = 112 | List.range { start: At 1, end: At (List.len cards) } 113 | |> List.map (\id -> (id, 1)) 114 | |> Dict.fromList 115 | 116 | cards 117 | |> go2 initInventory 118 | |> Num.toStr 119 | |> Stdout.line 120 | 121 | testInput : Str 122 | testInput = 123 | "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53\nCard 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19\nCard 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1\nCard 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83\nCard 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36\nCard 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11" 124 | -------------------------------------------------------------------------------- /src/Year2023/Day05.roc: -------------------------------------------------------------------------------- 1 | app "hello" 2 | packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" } 3 | imports [ 4 | pf.Stdout, 5 | "../../input202305.txt" as realInput : Str 6 | ] 7 | provides [main] to pf 8 | 9 | 10 | #main = part1 realInput 11 | main = part2 realInput 12 | #main = part1 testInput 13 | #main = part2 testInput 14 | 15 | Range : 16 | { 17 | destRangeStart: Nat, 18 | sourceRangeStart: Nat, 19 | length: Nat, 20 | } 21 | 22 | Input : 23 | { 24 | seeds: List Nat, 25 | maps: List (List Range), 26 | } 27 | 28 | parse : Str -> Input 29 | parse = \str -> 30 | sections = 31 | str 32 | |> Str.split "\n\n" 33 | |> List.dropIf (\s -> s == "") 34 | |> List.map Str.trim 35 | 36 | when sections is 37 | [] -> crash "parse: empty \\n\\n split" 38 | [seedsStr, .. as mapsStrs] -> 39 | seeds = 40 | seedsStr 41 | |> Str.split " " 42 | |> List.dropFirst 1 43 | |> List.mapTry Str.toNat 44 | |> crashResult "not all seeds are numbers" 45 | 46 | maps = 47 | mapsStrs 48 | |> List.map (\mapStr -> 49 | mapStr 50 | |> Str.split "\n" 51 | |> List.dropIf (\s -> s == "") 52 | |> List.map Str.trim 53 | |> List.dropFirst 1 54 | |> List.map (\mapLine -> 55 | nums = 56 | mapLine 57 | |> Str.split " " 58 | |> List.mapTry Str.toNat 59 | |> crashResult "not all map contents are numbers" 60 | 61 | when nums is 62 | [dst,src,len] -> 63 | { 64 | destRangeStart: dst, 65 | sourceRangeStart: src, 66 | length: len, 67 | } 68 | _ -> 69 | crash "Unexpected nums in map" 70 | ) 71 | ) 72 | 73 | { 74 | seeds: seeds, 75 | maps: maps, 76 | } 77 | 78 | crashResult : Result a *, Str -> a 79 | crashResult = \result,label -> 80 | when result is 81 | Ok a -> a 82 | Err _ -> crash label 83 | 84 | 85 | part1 = \input -> 86 | in = parse input 87 | 88 | in.seeds 89 | |> List.map (\seed -> follow1 in.maps seed) 90 | |> List.min 91 | |> Inspect.toStr 92 | |> Stdout.line 93 | 94 | follow1 : List (List Range),Nat -> Nat 95 | follow1 = \maps,seed -> 96 | maps 97 | |> List.walk seed (\acc,map -> 98 | map 99 | |> List.walk (NotFound acc) (\acc2,range -> 100 | when acc2 is 101 | NotFound theSeed -> 102 | if theSeed >= range.sourceRangeStart 103 | && theSeed < range.sourceRangeStart + range.length then 104 | Found (range.destRangeStart + (theSeed - range.sourceRangeStart)) 105 | else 106 | NotFound theSeed 107 | 108 | # "Short circuit" 109 | Found theSeed -> 110 | Found theSeed 111 | ) 112 | |> unwrapFound 113 | ) 114 | 115 | unwrapFound : [ Found a, NotFound a ] -> a 116 | unwrapFound = \x -> 117 | when x is 118 | Found a -> a 119 | NotFound a -> a 120 | 121 | part2 = \input -> 122 | in = parse input 123 | seedGroups = groupBy2 in.seeds 124 | seedGroupsLen = List.len seedGroups 125 | 126 | seedGroups 127 | |> List.map (\group -> walkGroup in.maps 9999999999 group) 128 | |> List.min 129 | |> Inspect.toStr 130 | |> Stdout.line 131 | 132 | walkGroup : List (List Range),Nat,(Nat,Nat) -> Nat 133 | walkGroup = \maps,minSoFar,(start,len) -> 134 | if len <= 0 then 135 | minSoFar 136 | else 137 | x = 138 | if start % 1000000 == 0 then 139 | dbg start 140 | start 141 | else 142 | start 143 | 144 | result = follow1 maps start 145 | newMin = Num.min result minSoFar 146 | walkGroup maps newMin (start + 1, len - 1) 147 | 148 | groupBy2 : List a -> List (a,a) 149 | groupBy2 = \list -> 150 | groupBy2Help list [] 151 | 152 | groupBy2Help : List a, List (a,a) -> List (a,a) 153 | groupBy2Help = \list,acc -> 154 | when list is 155 | [a,b, .. as rest] -> 156 | groupBy2Help rest (acc |> List.append (a,b)) 157 | _ -> 158 | acc 159 | 160 | 161 | testInput = 162 | "seeds: 79 14 55 13\n\nseed-to-soil map:\n50 98 2\n52 50 48\n\nsoil-to-fertilizer map:\n0 15 37\n37 52 2\n39 0 15\n\nfertilizer-to-water map:\n49 53 8\n0 11 42\n42 0 7\n57 7 4\n\nwater-to-light map:\n88 18 7\n18 25 70\n\nlight-to-temperature map:\n45 77 23\n81 45 19\n68 64 13\n\ntemperature-to-humidity map:\n0 69 1\n1 0 69\n\nhumidity-to-location map:\n60 56 37\n56 93 4" 163 | -------------------------------------------------------------------------------- /src/Year2023/Day06.roc: -------------------------------------------------------------------------------- 1 | app "hello" 2 | packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" } 3 | imports [ 4 | pf.Stdout, 5 | ] 6 | provides [main] to pf 7 | 8 | 9 | main = part1 realInput1 10 | #main = part2 realInput2 11 | #main = part1 testInput1 12 | #main = part2 testInput2 13 | 14 | part1 = \input -> 15 | input 16 | |> List.map process1 17 | |> List.product 18 | |> Inspect.toStr 19 | |> Stdout.line 20 | 21 | process1 = \(ms,record) -> 22 | List.range { start: At 1, end: Before ms } 23 | |> List.countIf (\n -> (ms - n) * n > record) 24 | 25 | part2 = \_ -> 26 | DoneByHand 27 | 28 | # Part 2: 29 | # Test input: 30 | # ----------------- 31 | 32 | # Limits 33 | # min n: (71530 - n) * n > 940200 34 | # max n: (71530 - n) * n > 940200 35 | 36 | # The inequation: 37 | # 71530n - n^2 > 940200 38 | # -n^2 + 71530n - 940200 > 0 39 | # https://www.wolframalpha.com/input?i=-n%5E2+%2B+71530n+-+940200+%3E+0 40 | 41 | # The solutions: 42 | # 14..71516 43 | # Count: 71516-14+1 = 71503 44 | 45 | # Now for the real input: 46 | # --------------------------- 47 | # inequation: 48 | # -n^2 + 44826981n - 202107611381458 > 0 49 | 50 | # https://www.wolframalpha.com/input?i=-n%5E2+%2B+44826981n+-+202107611381458+%3E+0+over+integers 51 | 52 | # solutions: 53 | # 5085567<=n<=39741414 54 | # Count: 34655848 55 | 56 | testInput1 = 57 | [ (7,9) 58 | , (15,40) 59 | , (30,200) 60 | ] 61 | 62 | realInput1 = 63 | [ (44,202) 64 | , (82,1076) 65 | , (69,1138) 66 | , (81,1458) 67 | ] 68 | 69 | testInput2 = 70 | (71530,940200) 71 | 72 | realInput2 = 73 | (44826981,202107611381458) 74 | -------------------------------------------------------------------------------- /src/Year2023/Day09.roc: -------------------------------------------------------------------------------- 1 | app "hello" 2 | packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" } 3 | imports [ 4 | pf.Stdout, 5 | "../../input202309.txt" as realInput : Str 6 | ] 7 | provides [main] to pf 8 | 9 | 10 | #main = part1 realInput 11 | main = part2 realInput 12 | #main = part1 testInput 13 | #main = part2 testInput 14 | 15 | parse : Str -> List (List I64) 16 | parse = \input -> 17 | input 18 | |> Str.split "\n" 19 | |> List.dropIf (\s -> s == "") 20 | |> List.map (\s -> 21 | Str.split s " " 22 | |> List.mapTry Str.toI64 23 | |> crashResult "not all numbers" 24 | ) 25 | 26 | part1 = \input -> 27 | input 28 | |> parse 29 | # COMPUTE 30 | |> List.map next 31 | |> List.sum 32 | # REPORT 33 | |> Inspect.toStr 34 | |> Stdout.line 35 | 36 | next : List I64 -> I64 37 | next = \ns -> 38 | diffs : List (List I64) 39 | diffs = 40 | findDiffs [] ns 41 | 42 | diffs 43 | |> List.walkBackwards 0 (\bottom,top -> 44 | when (top) is 45 | [.., topLeft] -> 46 | bottom + topLeft 47 | 48 | _ -> 49 | crash "next: wut" 50 | ) 51 | 52 | findDiffs : List (List I64), List I64 -> List (List I64) 53 | findDiffs = \acc,current -> 54 | if List.all current (\n -> n == 0) then 55 | acc 56 | 57 | else 58 | newCurrent = current |> mapPairs (\a,b -> b - a) 59 | newAcc = acc |> List.append current 60 | findDiffs newAcc newCurrent 61 | 62 | expect findDiffs [] [0,3,6,9,12,15] == [[0,3,6,9,12,15],[3,3,3,3,3]] 63 | 64 | mapPairs : List a, (a,a -> b) -> List b 65 | mapPairs = \xs,fn -> 66 | List.map2 67 | xs 68 | (xs |> List.dropFirst 1) 69 | fn 70 | 71 | expect mapPairs [1,2,3] Pair == [Pair 1 2, Pair 2 3] 72 | 73 | crashResult : Result a *, Str -> a 74 | crashResult = \result,label -> 75 | when result is 76 | Ok a -> a 77 | Err _ -> crash label 78 | 79 | ### PART 2 80 | 81 | part2 = \input -> 82 | input 83 | |> parse 84 | # COMPUTE 85 | |> List.map previous 86 | |> List.sum 87 | # REPORT 88 | |> Inspect.toStr 89 | |> Stdout.line 90 | 91 | previous : List I64 -> I64 92 | previous = \ns -> 93 | diffs : List (List I64) 94 | diffs = 95 | findDiffs [] ns 96 | 97 | diffs 98 | |> List.walkBackwards 0 (\bottom,top -> 99 | when (top) is 100 | [topRight, ..] -> 101 | topRight - bottom 102 | 103 | _ -> 104 | crash "previous: wut" 105 | ) 106 | -------------------------------------------------------------------------------- /src/Year2023/Day11.roc: -------------------------------------------------------------------------------- 1 | app "hello" 2 | packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" } 3 | imports [ 4 | pf.Stdout, 5 | "../../input202311.txt" as realInput : Str 6 | ] 7 | provides [main] to pf 8 | 9 | 10 | #main = part1 realInput 11 | main = part2 realInput 12 | #main = part1 testInput 13 | #main = part2 testInput 14 | 15 | 16 | testInput = 17 | """ 18 | ...#...... 19 | .......#.. 20 | #......... 21 | .......... 22 | ......#... 23 | .#........ 24 | .........# 25 | .......... 26 | .......#.. 27 | #...#..... 28 | """ 29 | 30 | part1 = \input -> 31 | input 32 | |> parse 33 | |> expandRows 34 | |> expandColumns 35 | |> toGalaxies 36 | |> toAllPairs 37 | |> List.map manhattanDistance 38 | |> List.sum 39 | |> Inspect.toStr 40 | |> Stdout.line 41 | 42 | part2 = \input -> 43 | parsed = parse input 44 | emptyRows = findEmpty parsed 45 | emptyColumns = findEmpty (transpose parsed) 46 | 47 | parsed 48 | |> toGalaxies 49 | |> expandSparse emptyRows emptyColumns 1000000 50 | |> toAllPairs 51 | |> List.map manhattanDistance 52 | |> List.sum 53 | |> Inspect.toStr 54 | |> Stdout.line 55 | 56 | expandSparse : List XY,List I64,List I64,I64 -> List XY 57 | expandSparse = \galaxies,emptyRows,emptyColumns,mult -> 58 | galaxies 59 | |> List.map (\(x,y) -> 60 | dx = emptyColumns |> List.countIf (\c -> c < x) |> Num.intCast |> Num.mul (mult - 1) 61 | dy = emptyRows |> List.countIf (\r -> r < y) |> Num.intCast |> Num.mul (mult - 1) 62 | (x+dx,y+dy) 63 | ) 64 | 65 | expect expandSparse [(0,0),(2,3)] [1,2] [1] 3 == [(0,0),(4,7)] 66 | 67 | parse : Str -> List (List Str) 68 | parse = \input -> 69 | input 70 | |> Str.split "\n" 71 | |> List.dropIf (\s -> s == "") 72 | |> List.map Str.graphemes 73 | 74 | XY: (I64,I64) 75 | 76 | findEmpty : List (List Str) -> List I64 77 | findEmpty = \rows -> 78 | rows 79 | |> List.mapWithIndex (\row,y -> 80 | if Set.fromList row == Set.single "." then 81 | Ok (Num.intCast y) 82 | else 83 | Err NotEmpty 84 | ) 85 | |> List.keepOks (\x -> x) 86 | 87 | 88 | 89 | expandRows : List (List Str) -> List (List Str) 90 | expandRows = \rows -> 91 | rows 92 | |> List.joinMap (\row -> 93 | if Set.fromList row == Set.single "." then 94 | [row,row] 95 | else 96 | [row] 97 | ) 98 | 99 | expandColumns : List (List Str) -> List (List Str) 100 | expandColumns = \rows -> 101 | rows 102 | |> transpose 103 | |> expandRows 104 | |> transpose 105 | 106 | transpose : List (List a) -> List (List a) 107 | transpose = \listOfLists -> 108 | listOfLists 109 | |> List.walk 110 | ([] |> List.repeat (rowsLength listOfLists)) 111 | (\acc,list -> 112 | List.map2 113 | acc 114 | list 115 | List.append 116 | ) 117 | 118 | expect transpose [[1,2,3],[4,5,6]] == [[1,4],[2,5],[3,6]] 119 | 120 | rowsLength : List (List a) -> Nat 121 | rowsLength = \lists -> 122 | when lists is 123 | [x,..] -> List.len x 124 | _ -> 0 125 | 126 | toGalaxies : List (List Str) -> List XY 127 | toGalaxies = \input -> 128 | input 129 | |> List.mapWithIndex (\row,y -> 130 | row |> List.mapWithIndex (\cell,x -> 131 | if cell == "#" then 132 | Ok (Num.intCast x, Num.intCast y) 133 | else 134 | Err NotAGalaxy 135 | ) 136 | ) 137 | |> List.join 138 | |> List.keepOks (\x -> x) 139 | 140 | toAllPairs : List a -> List (a,a) 141 | toAllPairs = \xs -> 142 | when xs is 143 | [] -> [] 144 | [x, .. as rest] -> 145 | List.concat 146 | (rest |> List.map (\y -> (x,y))) 147 | (toAllPairs rest) 148 | 149 | expect toAllPairs [1,2,3,4] == [(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)] 150 | 151 | manhattanDistance : (XY,XY) -> I64 152 | manhattanDistance = \((x1,y1),(x2,y2)) -> 153 | Num.abs (x2 - x1) + Num.abs (y2 - y1) 154 | -------------------------------------------------------------------------------- /src/Year2023/Day12.roc: -------------------------------------------------------------------------------- 1 | app "hello" 2 | packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" } 3 | imports [ 4 | pf.Stdout, 5 | "../../input202312.txt" as realInput : Str 6 | ] 7 | provides [main] to pf 8 | 9 | crashResult : Result a *, Str -> a 10 | crashResult = \result,label -> 11 | when result is 12 | Ok a -> a 13 | Err _ -> crash label 14 | 15 | testInput = 16 | """ 17 | ???.### 1,1,3 18 | .??..??...?##. 1,1,3 19 | ?#?#?#?#?#?#?#? 1,3,1,6 20 | ????.#...#... 4,1,1 21 | ????.######..#####. 1,6,5 22 | ?###???????? 3,2,1 23 | """ 24 | 25 | main = part1 realInput 26 | #main = part2 realInput 27 | #main = part1 testInput 28 | #main = part2 testInput 29 | 30 | pipeDbg = \x -> 31 | dbg x 32 | x 33 | 34 | ### PARSE ### 35 | 36 | parse : Str -> List Line 37 | parse = \input -> 38 | input 39 | |> Str.trim 40 | |> Str.split "\n" 41 | |> List.map parseLine 42 | 43 | parseLine : Str -> Line 44 | parseLine = \input -> 45 | when input |> Str.split " " is 46 | [springs,lengths] -> 47 | { 48 | springs: Str.graphemes springs, 49 | lengths: 50 | lengths 51 | |> Str.split "," 52 | |> List.mapTry Str.toNat 53 | |> crashResult "parse lengths" 54 | } 55 | 56 | _ -> crash "parseLine - weird input" 57 | 58 | Line: { 59 | springs: List Str, 60 | lengths: List Nat, 61 | } 62 | 63 | ### PART 1 ### 64 | 65 | part1 = \input -> 66 | input 67 | |> parse 68 | |> List.map countPossibilities 69 | |> List.sum 70 | |> Inspect.toStr 71 | |> Stdout.line 72 | 73 | countPossibilities = \line -> 74 | unknowns = line.springs |> List.countIf (\s -> s == "?") 75 | allPossibilities = 76 | ["#", "."] 77 | |> List.repeat unknowns 78 | |> cartesianProduct 79 | allPossibilities 80 | |> List.countIf (\possibility -> agreesWith line possibility) 81 | 82 | agreesWith : Line,List Str -> Bool 83 | agreesWith = \line,possibility -> 84 | newSprings = combine line.springs possibility 85 | countSprings newSprings == line.lengths 86 | 87 | combine : List Str,List Str -> List Str 88 | combine = \haystack,todos -> 89 | todos 90 | |> List.walk 91 | haystack 92 | (\acc,todo -> 93 | res = 94 | acc 95 | |> List.splitFirst "?" 96 | |> crashResult "combine - no more ?" 97 | 98 | List.join 99 | [ 100 | res.before, 101 | [todo], 102 | res.after, 103 | ] 104 | ) 105 | 106 | expect combine (Str.graphemes "?..#.??#?") (Str.graphemes "ABCD") 107 | == Str.graphemes "A..#.BC#D" 108 | 109 | countSprings : List Str -> List Nat 110 | countSprings = \springs -> 111 | springs 112 | |> group 113 | |> List.dropIf (\xs -> List.first xs == Ok ".") 114 | |> List.map List.len 115 | 116 | expect countSprings (Str.graphemes ".##.#.####") == [2,1,4] 117 | expect countSprings (Str.graphemes "###.#.###.") == [3,1,3] 118 | 119 | group = \items -> 120 | items 121 | |> List.walk 122 | {full:[],group:[],last:NoItem} 123 | (\acc,x -> 124 | when acc.last is 125 | NoItem -> 126 | {acc & 127 | group: acc.group |> List.append x, 128 | last: Item x 129 | } 130 | Item prev -> 131 | if prev == x then 132 | {acc & 133 | group: acc.group |> List.append x, 134 | last: Item x 135 | } 136 | else 137 | { 138 | full: acc.full |> List.append acc.group, 139 | group: [x], 140 | last: Item x 141 | } 142 | ) 143 | |> (\final -> final.full |> List.append final.group) 144 | 145 | expect group [A,A,B,B,B,C,D,A,B] == [[A,A],[B,B,B],[C],[D],[A],[B]] 146 | 147 | cartesianProduct : List (List a) -> List (List a) 148 | where a implements Inspect.Inspect 149 | cartesianProduct = \ll -> 150 | when ll is 151 | [] -> 152 | [ [] ] 153 | 154 | [.. as xss, xs] -> 155 | map2Cartesian 156 | (cartesianProduct xss) 157 | xs 158 | List.append 159 | 160 | expect cartesianProduct [[A,B],[C,D,E]] == [[A,C],[A,D],[A,E],[B,C],[B,D],[B,E]] 161 | 162 | map2Cartesian : List a,List b,(a,b -> c) -> List c 163 | map2Cartesian = \la,lb,f -> 164 | la 165 | |> List.joinMap (\a -> 166 | lb 167 | |> List.joinMap (\b -> 168 | [f a b] 169 | ) 170 | ) 171 | -------------------------------------------------------------------------------- /src/aoc.gleam: -------------------------------------------------------------------------------- 1 | import gladvent 2 | 3 | pub fn main() { 4 | gladvent.run() 5 | } 6 | -------------------------------------------------------------------------------- /src/aoc_2024/day_1.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/dict 3 | import gleam/int 4 | import gleam/list 5 | import gleam/otp/task 6 | import gleam/result 7 | import gleam/string 8 | import pocket_watch 9 | 10 | pub type Input = 11 | #(List(Int), List(Int)) 12 | 13 | pub fn parse(input: String) -> Input { 14 | use <- pocket_watch.simple("parse") 15 | input 16 | |> string.split("\n") 17 | |> extra.pmap(string.split(_, " ")) 18 | |> list.fold(from: #([], []), with: fn(acc, row) { 19 | let #(xs, ys) = acc 20 | case row { 21 | [x, y] -> { 22 | #([extra.yolo_int(x), ..xs], [extra.yolo_int(y), ..ys]) 23 | } 24 | _ -> panic as "Bad input" 25 | } 26 | }) 27 | } 28 | 29 | pub fn pt_1(input: Input) { 30 | use <- pocket_watch.simple("part 1") 31 | let sorted_xs_handle = task.async(fn() { list.sort(input.0, int.compare) }) 32 | let sorted_ys_handle = task.async(fn() { list.sort(input.1, int.compare) }) 33 | let sorted_xs = task.await_forever(sorted_xs_handle) 34 | let sorted_ys = task.await_forever(sorted_ys_handle) 35 | 36 | extra.pmap2(sorted_xs, sorted_ys, fn(x, y) { int.absolute_value(x - y) }) 37 | |> int.sum 38 | } 39 | 40 | pub fn pt_2(input: Input) { 41 | use <- pocket_watch.simple("part 2") 42 | let #(xs, ys) = input 43 | let freqs = extra.frequencies(ys) 44 | 45 | xs 46 | |> extra.pmap(fn(x) { x * { dict.get(freqs, x) |> result.unwrap(0) } }) 47 | |> int.sum 48 | } 49 | -------------------------------------------------------------------------------- /src/aoc_2024/day_10.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/int 3 | import gleam/list 4 | import gleam/set.{type Set} 5 | import grid.{type Grid, type XY} 6 | import pocket_watch 7 | 8 | pub fn parse(input: String) -> Grid(Int) { 9 | use <- pocket_watch.simple("parse") 10 | grid.from_string(input) 11 | |> grid.map(fn(_, c) { extra.yolo_int(c) }) 12 | } 13 | 14 | pub fn pt_1(input: Grid(Int)) { 15 | use <- pocket_watch.simple("part 1") 16 | let starting_positions: List(XY) = grid.find_all_exact(input, 0) 17 | starting_positions 18 | |> list.map(trailheads_count(input, _)) 19 | |> int.sum 20 | } 21 | 22 | pub fn pt_2(input: Grid(Int)) { 23 | use <- pocket_watch.simple("part 2") 24 | let starting_positions: List(XY) = grid.find_all_exact(input, 0) 25 | starting_positions 26 | |> list.map(trailheads_rating(input, _)) 27 | |> int.sum 28 | } 29 | 30 | type Trailhead { 31 | Trailhead(xy: XY, height: Int) 32 | } 33 | 34 | fn trailheads_count(input: Grid(Int), start: XY) -> Int { 35 | find_trailheads( 36 | input, 37 | todos: [Trailhead(xy: start, height: 0)], 38 | count: 0, 39 | seen: set.new(), 40 | ) 41 | |> fn(x) { set.size(x.1) } 42 | } 43 | 44 | fn trailheads_rating(input: Grid(Int), start: XY) -> Int { 45 | find_trailheads( 46 | input, 47 | todos: [Trailhead(xy: start, height: 0)], 48 | count: 0, 49 | seen: set.new(), 50 | ) 51 | |> fn(x) { x.0 } 52 | } 53 | 54 | fn find_trailheads( 55 | input: Grid(Int), 56 | todos todos: List(Trailhead), 57 | count count: Int, 58 | seen seen: Set(XY), 59 | ) -> #(Int, Set(XY)) { 60 | case todos { 61 | [] -> #(count, seen) 62 | [todo_, ..rest] -> { 63 | case todo_.height == 9 { 64 | True -> 65 | find_trailheads( 66 | input, 67 | todos: rest, 68 | count: count + 1, 69 | seen: set.insert(seen, todo_.xy), 70 | ) 71 | False -> { 72 | let added_todos = 73 | grid.neighbours(input, todo_.xy, grid.orthogonal_dirs) 74 | |> list.filter(fn(neighbour) { neighbour.1 == Ok(todo_.height + 1) }) 75 | |> list.map(fn(neighbour) { 76 | Trailhead(xy: neighbour.0, height: todo_.height + 1) 77 | }) 78 | find_trailheads( 79 | input, 80 | todos: list.append(rest, added_todos), 81 | count: count, 82 | seen: seen, 83 | ) 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/aoc_2024/day_11.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/bool 3 | import gleam/int 4 | import gleam/list 5 | import gleam/string 6 | import pocket_watch 7 | import rememo/memo 8 | 9 | pub fn parse(input: String) -> List(Int) { 10 | use <- pocket_watch.simple("parse") 11 | input 12 | |> string.split(" ") 13 | |> list.map(extra.yolo_int) 14 | } 15 | 16 | pub fn pt_1(input: List(Int)) { 17 | use <- pocket_watch.simple("part 1") 18 | use cache <- memo.create() 19 | input 20 | |> list.map(c(_, 25, cache)) 21 | |> int.sum 22 | } 23 | 24 | pub fn pt_2(input: List(Int)) { 25 | use <- pocket_watch.simple("part 2") 26 | use cache <- memo.create() 27 | input 28 | |> list.map(c(_, 75, cache)) 29 | |> int.sum 30 | } 31 | 32 | fn c(n: Int, i: Int, cache) -> Int { 33 | use <- memo.memoize(cache, #(n, i)) 34 | use <- bool.guard(when: i == 0, return: 1) 35 | case n { 36 | 0 -> c(1, i - 1, cache) 37 | _ -> { 38 | let str = int.to_string(n) 39 | let len = string.length(str) 40 | case len % 2 == 0 { 41 | True -> { 42 | let half = len / 2 43 | let left = 44 | extra.yolo_int(string.slice(str, at_index: 0, length: half)) 45 | let right = 46 | extra.yolo_int(string.slice(str, at_index: half, length: half)) 47 | c(left, i - 1, cache) + c(right, i - 1, cache) 48 | } 49 | False -> c(n * 2024, i - 1, cache) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/aoc_2024/day_13.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/bool 3 | import gleam/int 4 | import gleam/list 5 | import gleam/string 6 | import grid.{type XY} 7 | import pocket_watch 8 | 9 | pub type Machine { 10 | Machine(a: XY, b: XY, prize: XY) 11 | } 12 | 13 | pub fn parse(input: String) -> List(Machine) { 14 | use <- pocket_watch.simple("parse") 15 | input 16 | |> string.split("\n\n") 17 | |> list.map(parse_machine) 18 | } 19 | 20 | fn parse_machine(input: String) -> Machine { 21 | case string.split(input, "\n") { 22 | ["Button A: X+" <> but_a, "Button B: X+" <> but_b, "Prize: X=" <> prize] -> { 23 | case 24 | string.split(but_a, ", Y+"), 25 | string.split(but_b, ", Y+"), 26 | string.split(prize, ", Y=") 27 | { 28 | [ax, ay], [bx, by], [px, py] -> 29 | Machine( 30 | a: #(extra.yolo_int(ax), extra.yolo_int(ay)), 31 | b: #(extra.yolo_int(bx), extra.yolo_int(by)), 32 | prize: #(extra.yolo_int(px), extra.yolo_int(py)), 33 | ) 34 | _, _, _ -> panic as "Bad input 2?" 35 | } 36 | } 37 | _ -> panic as "Bad input?" 38 | } 39 | } 40 | 41 | pub fn pt_1(input: List(Machine)) { 42 | use <- pocket_watch.simple("part 1") 43 | input 44 | |> list.map(solve_machine) 45 | |> int.sum 46 | } 47 | 48 | fn solve_machine(machine: Machine) -> Int { 49 | // button A = X+i, Y+j 50 | // button B = X+l, Y+m 51 | // prize = (K,N) 52 | // --------------------- 53 | // ia + jb = k 54 | // la + mb = n 55 | // --------------------- 56 | // b = (kl-in)/(jl-im) 57 | // a = (n-mb)/l 58 | // --------------------- 59 | // cost = 3 * a + b 60 | // --------------------- 61 | // we need to check for integer divisibility and short-circuit to 0 62 | let #(i, l) = machine.a 63 | let #(j, m) = machine.b 64 | let #(k, n) = machine.prize 65 | use <- bool.guard(when: { k * l - i * n } % { j * l - i * m } != 0, return: 0) 66 | let b = { k * l - i * n } / { j * l - i * m } 67 | use <- bool.guard(when: { n - m * b } % l != 0, return: 0) 68 | let a = { n - m * b } / l 69 | 3 * a + b 70 | } 71 | 72 | pub fn pt_2(input: List(Machine)) { 73 | use <- pocket_watch.simple("part 2") 74 | input 75 | |> list.map(fn(machine) { 76 | Machine( 77 | ..machine, 78 | prize: grid.xy_add(machine.prize, #( 79 | 10_000_000_000_000, 80 | 10_000_000_000_000, 81 | )), 82 | ) 83 | }) 84 | |> list.map(solve_machine) 85 | |> int.sum 86 | } 87 | -------------------------------------------------------------------------------- /src/aoc_2024/day_14.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/bool 3 | import gleam/int 4 | import gleam/io 5 | import gleam/list 6 | import gleam/string 7 | import grid.{type XY} 8 | import pocket_watch 9 | 10 | pub type Robot { 11 | Robot(p: XY, v: XY) 12 | } 13 | 14 | pub fn parse(input: String) -> List(Robot) { 15 | use <- pocket_watch.simple("parse") 16 | input 17 | |> string.split("\n") 18 | |> list.map(parse_robot) 19 | } 20 | 21 | fn parse_robot(line: String) -> Robot { 22 | case string.split(line, " ") { 23 | [p, v] -> { 24 | case p, v { 25 | "p=" <> pp, "v=" <> vv -> { 26 | case string.split(pp, ","), string.split(vv, ",") { 27 | [px, py], [vx, vy] -> 28 | Robot(p: #(extra.yolo_int(px), extra.yolo_int(py)), v: #( 29 | extra.yolo_int(vx), 30 | extra.yolo_int(vy), 31 | )) 32 | _, _ -> panic as "Bad input? #3" 33 | } 34 | } 35 | _, _ -> panic as "Bad input? #2" 36 | } 37 | } 38 | _ -> panic as "Bad input? #1" 39 | } 40 | } 41 | 42 | pub fn pt_1(robots: List(Robot)) { 43 | use <- pocket_watch.simple("part 1") 44 | robots 45 | |> step_times(100) 46 | |> io.debug 47 | |> to_quadrants 48 | |> io.debug 49 | |> list.map(list.length) 50 | |> int.product 51 | } 52 | 53 | fn step_times(robots: List(Robot), times: Int) -> List(Robot) { 54 | use <- bool.guard(when: times <= 0, return: robots) 55 | step_times(step(robots), times - 1) 56 | } 57 | 58 | fn step(robots: List(Robot)) -> List(Robot) { 59 | list.map(robots, step_robot) 60 | } 61 | 62 | pub const room: XY = #(101, 103) 63 | 64 | pub const q_size: XY = #(50, 51) 65 | 66 | pub const q1: XY = #(0, 0) 67 | 68 | pub const q2: XY = #(51, 0) 69 | 70 | pub const q3: XY = #(0, 52) 71 | 72 | pub const q4: XY = #(51, 52) 73 | 74 | pub const q_mid: XY = #(25, 26) 75 | 76 | //pub const room: XY = #(11, 7) 77 | //pub const q_size: XY = #(5, 3) 78 | //pub const q1: XY = #(0, 0) 79 | //pub const q2: XY = #(6, 0) 80 | //pub const q3: XY = #(0, 4) 81 | //pub const q4: XY = #(6, 4) 82 | 83 | fn step_robot(robot: Robot) -> Robot { 84 | let new_p: XY = 85 | robot.p 86 | |> grid.xy_add(robot.v) 87 | |> grid.xy_mod(room) 88 | |> grid.xy_add(room) 89 | |> grid.xy_mod(room) 90 | Robot(..robot, p: new_p) 91 | } 92 | 93 | fn to_quadrants(robots: List(Robot)) -> List(List(Robot)) { 94 | [q1, q2, q3, q4] 95 | |> list.map(robots_in_quadrant(robots, _)) 96 | } 97 | 98 | fn robots_in_quadrant(robots: List(Robot), q: XY) -> List(Robot) { 99 | robots 100 | |> list.filter(fn(robot) { 101 | robot.p.0 >= q.0 102 | && robot.p.0 < { q.0 + q_size.0 } 103 | && robot.p.1 >= q.1 104 | && robot.p.1 < { q.1 + q_size.1 } 105 | }) 106 | } 107 | 108 | pub fn pt_2(robots: List(Robot)) { 109 | use <- pocket_watch.simple("part 2") 110 | step_pt2(0, robots) 111 | } 112 | 113 | fn step_pt2(seconds: Int, robots: List(Robot)) { 114 | let is_interesting = seconds % 101 == 9 115 | case is_interesting { 116 | True -> { 117 | io.println_error("------------------------") 118 | io.debug(#(seconds, "seconds")) 119 | step_pt2(seconds + 1, step(robots)) 120 | } 121 | False -> step_pt2(seconds + 1, step(robots)) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/aoc_2024/day_18.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/dict 3 | import gleam/list 4 | import gleam/result 5 | import gleam/string 6 | import grid.{type Dims, type XY, Grid} 7 | import pocket_watch 8 | 9 | pub fn parse(input: String) -> List(XY) { 10 | use <- pocket_watch.simple("parse") 11 | input 12 | |> string.split("\n") 13 | |> list.map(fn(line) { 14 | case string.split(line, ",") { 15 | [x, y] -> #(extra.yolo_int(x), extra.yolo_int(y)) 16 | _ -> panic as "bad input?" 17 | } 18 | }) 19 | } 20 | 21 | // example: size 7, take 12 22 | const size = 71 23 | 24 | pub fn pt_1(input: List(XY)) -> Int { 25 | use <- pocket_watch.simple("part 1") 26 | let board = grid.dims_of_size(width: size, height: size) 27 | let taken = 1024 28 | let assert Ok(cost) = attempt(input, board, taken) 29 | cost 30 | } 31 | 32 | pub fn pt_2(input: List(XY)) { 33 | use <- pocket_watch.simple("part 2") 34 | let board = grid.dims_of_size(width: size, height: size) 35 | pt_2_loop(input, board, 1025) 36 | } 37 | 38 | fn pt_2_loop(input: List(XY), board: Dims, taken: Int) -> XY { 39 | case attempt(input, board, taken) { 40 | Ok(_) -> pt_2_loop(input, board, taken + 1) 41 | Error(last) -> last 42 | } 43 | } 44 | 45 | fn attempt(input: List(XY), board: Dims, taken: Int) -> Result(Int, XY) { 46 | let first_kb = 47 | input 48 | |> list.take(taken) 49 | |> list.filter(grid.in_dims(board, _)) 50 | |> list.map(fn(xy) { #(xy, Nil) }) 51 | let grid = Grid(dims: board, data: dict.new()) |> grid.set_all(first_kb) 52 | let found_cost = 53 | grid.astar( 54 | from: #(0, 0), 55 | to: #(size - 1, size - 1), 56 | neighbours: fn(xy) { 57 | grid.neighbours(grid, xy, grid.orthogonal_dirs) 58 | |> list.filter(fn(kv) { kv.1 |> result.is_error }) 59 | |> list.map(fn(kv) { kv.0 }) 60 | }, 61 | cost: grid.manhattan_distance, 62 | print_progress: fn(_, _, _) { Nil }, 63 | ) 64 | case found_cost { 65 | Error(Nil) -> { 66 | let assert Ok(last) = input |> list.drop(taken - 1) |> list.first 67 | Error(last) 68 | } 69 | Ok(cost) -> Ok(cost) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/aoc_2024/day_19.gleam: -------------------------------------------------------------------------------- 1 | import gleam/bool 2 | import gleam/int 3 | import gleam/list 4 | import gleam/string 5 | import pocket_watch 6 | import rememo/memo 7 | 8 | pub type Input { 9 | Input(towels: List(String), patterns: List(String)) 10 | } 11 | 12 | pub fn parse(input: String) -> Input { 13 | use <- pocket_watch.simple("parse") 14 | case string.split(input, "\n\n") { 15 | [towels_str, patterns_str] -> { 16 | Input( 17 | towels: string.split(towels_str, ", "), 18 | patterns: string.split(patterns_str, "\n"), 19 | ) 20 | } 21 | _ -> panic as "Bad input" 22 | } 23 | } 24 | 25 | pub fn pt_1(input: Input) { 26 | use <- pocket_watch.simple("part 1") 27 | input.patterns 28 | |> list.count(pt_1_possible(input.towels, _)) 29 | } 30 | 31 | fn pt_1_possible(towels: List(String), pattern: String) -> Bool { 32 | pt_1_possible_aux(towels, [pattern]) 33 | } 34 | 35 | fn pt_1_possible_aux(towels: List(String), todos: List(String)) -> Bool { 36 | case todos { 37 | [] -> False 38 | [todo_, ..rest] -> { 39 | use <- bool.guard( 40 | when: list.any(towels, fn(towel) { towel == todo_ }), 41 | return: True, 42 | ) 43 | 44 | let next_towels: List(String) = 45 | towels 46 | |> list.filter(fn(towel) { string.starts_with(todo_, towel) }) 47 | let added_todos: List(String) = 48 | next_towels 49 | |> list.map(fn(towel) { string.drop_start(todo_, string.length(towel)) }) 50 | pt_1_possible_aux(towels, list.append(added_todos, rest)) 51 | } 52 | } 53 | } 54 | 55 | pub fn pt_2(input: Input) { 56 | use <- pocket_watch.simple("part 2") 57 | use cache <- memo.create() 58 | input.patterns 59 | |> list.map(pt_2_all(input.towels, _, cache)) 60 | |> int.sum 61 | } 62 | 63 | fn pt_2_all(towels: List(String), pattern: String, cache) -> Int { 64 | use <- memo.memoize(cache, #(towels, pattern)) 65 | let #(done, not_done) = list.partition(towels, fn(towel) { towel == pattern }) 66 | let done_towels = list.length(done) 67 | let rec = 68 | not_done 69 | |> list.filter(fn(towel) { string.starts_with(pattern, towel) }) 70 | |> list.map(fn(towel) { string.drop_start(pattern, string.length(towel)) }) 71 | |> list.map(fn(rest) { pt_2_all(towels, rest, cache) }) 72 | |> int.sum 73 | done_towels + rec 74 | } 75 | -------------------------------------------------------------------------------- /src/aoc_2024/day_2.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/int 3 | import gleam/list 4 | import gleam/string 5 | import pocket_watch 6 | 7 | pub fn parse(input: String) -> List(List(Int)) { 8 | use <- pocket_watch.simple("parse") 9 | input 10 | |> string.split("\n") 11 | |> list.map(fn(line) { 12 | line 13 | |> string.split(" ") 14 | |> list.map(extra.yolo_int) 15 | }) 16 | } 17 | 18 | fn is_ascending(line: List(Int)) { 19 | line == list.sort(line, by: int.compare) 20 | } 21 | 22 | fn is_descending(line: List(Int)) { 23 | list.reverse(line) == list.sort(line, by: int.compare) 24 | } 25 | 26 | fn has_good_differences(line: List(Int)) { 27 | list.window_by_2(line) 28 | |> list.map(fn(pair) { int.absolute_value(pair.0 - pair.1) }) 29 | |> list.all(fn(n) { n >= 1 && n <= 3 }) 30 | } 31 | 32 | fn is_ok(line: List(Int)) { 33 | { is_ascending(line) || is_descending(line) } && has_good_differences(line) 34 | } 35 | 36 | pub fn pt_1(input: List(List(Int))) { 37 | use <- pocket_watch.simple("part 1") 38 | list.count(input, is_ok) 39 | } 40 | 41 | pub fn pt_2(input: List(List(Int))) { 42 | use <- pocket_watch.simple("part 2") 43 | input 44 | |> list.count(fn(line) { 45 | line 46 | |> list.combinations(list.length(line) - 1) 47 | |> list.any(is_ok) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /src/aoc_2024/day_20.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/dict 3 | import gleam/int 4 | import gleam/io 5 | import gleam/list 6 | import gleam/result 7 | import gleam/yielder.{type Yielder} 8 | import grid.{type Dir, type Grid, type XY} 9 | import pocket_watch 10 | 11 | pub type Input { 12 | Input(grid: Grid(Nil), start: XY, end: XY) 13 | } 14 | 15 | pub fn parse(input: String) -> Input { 16 | use <- pocket_watch.simple("parse") 17 | let input_grid = grid.from_string(input) 18 | let assert Ok(start) = grid.find_exact(input_grid, "S") 19 | let assert Ok(end) = grid.find_exact(input_grid, "E") 20 | let grid = 21 | input_grid 22 | |> grid.filter_map(fn(_, c) { 23 | case c { 24 | "#" -> Error(Nil) 25 | "." -> Ok(Nil) 26 | "S" -> Ok(Nil) 27 | "E" -> Ok(Nil) 28 | _ -> panic as "Bad input?" 29 | } 30 | }) 31 | Input(grid, start, end) 32 | } 33 | 34 | pub fn pt_1(input: Input) { 35 | use <- pocket_watch.simple("part 1") 36 | find(input, 2, 100) 37 | } 38 | 39 | pub fn pt_2(input: Input) { 40 | use <- pocket_watch.simple("part 2") 41 | find(input, 20, 100) 42 | } 43 | 44 | pub fn find(input: Input, teleport: Int, threshold: Int) { 45 | let track: List(XY) = grid.find_all_exact(input.grid, Nil) 46 | let assert Ok(#(_, astar_bests)) = 47 | grid.astar_with_bests( 48 | from: input.start, 49 | to: input.end, 50 | cost: grid.manhattan_distance, 51 | neighbours: fn(xy) { 52 | grid.neighbours(input.grid, xy, grid.orthogonal_dirs) 53 | |> list.filter(fn(kv) { result.is_ok(kv.1) }) 54 | |> list.map(fn(kv) { kv.0 }) 55 | }, 56 | print_progress: fn(_, _, _) { Nil }, 57 | ) 58 | 59 | let possibilities: Yielder(#(XY, XY)) = 60 | track 61 | |> yielder.from_list 62 | |> extra.yield_combination_pairs 63 | |> yielder.filter(fn(pair) { 64 | grid.manhattan_distance(pair.0, pair.1) <= teleport 65 | }) 66 | 67 | possibilities 68 | |> yielder.filter(fn(tp) { 69 | let assert Ok(old_cost) = dict.get(astar_bests, tp.1) 70 | let assert Ok(start_cost) = dict.get(astar_bests, tp.0) 71 | let savings = 72 | int.absolute_value(old_cost - start_cost) 73 | - grid.manhattan_distance(tp.0, tp.1) 74 | savings >= threshold 75 | }) 76 | |> yielder.length 77 | } 78 | -------------------------------------------------------------------------------- /src/aoc_2024/day_3.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/int 3 | import gleam/list 4 | import gleam/regexp 5 | import gleam/string 6 | import pocket_watch 7 | 8 | pub type Op { 9 | Mul(Int, Int) 10 | Do 11 | Dont 12 | } 13 | 14 | fn from_match(match: regexp.Match) -> Op { 15 | case match.content { 16 | "mul(" <> rest -> { 17 | let assert Ok(strs) = 18 | rest 19 | |> string.drop_end(1) 20 | |> string.split_once(",") 21 | Mul(extra.yolo_int(strs.0), extra.yolo_int(strs.1)) 22 | } 23 | "do()" -> Do 24 | "don't()" -> Dont 25 | _ -> panic as "Bad input" 26 | } 27 | } 28 | 29 | pub fn parse(input: String) -> List(Op) { 30 | use <- pocket_watch.simple("parse") 31 | let assert Ok(regex) = 32 | regexp.from_string("mul\\(\\d+,\\d+\\)|do\\(\\)|don't\\(\\)") 33 | 34 | regex 35 | |> regexp.scan(input) 36 | |> list.map(from_match) 37 | } 38 | 39 | pub fn pt_1(ops: List(Op)) { 40 | use <- pocket_watch.simple("part 1") 41 | ops 42 | |> list.map(fn(op) { 43 | case op { 44 | Mul(x, y) -> x * y 45 | Do -> 0 46 | Dont -> 0 47 | } 48 | }) 49 | |> int.sum 50 | } 51 | 52 | type State { 53 | State(sum: Int, can_mult: Bool) 54 | } 55 | 56 | pub fn pt_2(ops: List(Op)) { 57 | use <- pocket_watch.simple("part 2") 58 | let result = 59 | ops 60 | |> list.fold(State(0, True), fn(acc, op) { 61 | case op { 62 | Mul(x, y) -> 63 | case acc.can_mult { 64 | True -> State(..acc, sum: acc.sum + x * y) 65 | False -> acc 66 | } 67 | Do -> State(..acc, can_mult: True) 68 | Dont -> State(..acc, can_mult: False) 69 | } 70 | }) 71 | 72 | result.sum 73 | } 74 | -------------------------------------------------------------------------------- /src/aoc_2024/day_4.gleam: -------------------------------------------------------------------------------- 1 | import gleam/int 2 | import gleam/list 3 | import gleam/result 4 | import gleam/string 5 | import grid.{type Grid, type XY} 6 | import pocket_watch 7 | 8 | pub type XGrid = 9 | Grid(String) 10 | 11 | pub fn parse(input: String) -> XGrid { 12 | use <- pocket_watch.simple("parse") 13 | grid.from_string(input) 14 | } 15 | 16 | pub fn pt_1(grid: XGrid) { 17 | use <- pocket_watch.simple("part 1") 18 | grid 19 | |> grid.map(fn(xy, _) { xmas_count(grid, xy) }) 20 | |> grid.values 21 | |> int.sum 22 | } 23 | 24 | pub fn pt_2(grid: XGrid) { 25 | use <- pocket_watch.simple("part 2") 26 | grid 27 | |> grid.map(fn(xy, _) { x_mas_count(grid, xy) }) 28 | |> grid.values 29 | |> int.sum 30 | } 31 | 32 | fn xmas_count(grid_: XGrid, xy: XY) -> Int { 33 | grid.all_dirs 34 | |> list.count(fn(dir) { 35 | grid.in_dir(grid_, xy, dir, 4) 36 | |> result.map(fn(xys) { 37 | grid.get_all(grid_, xys) 38 | |> result.map(fn(all) { string.join(all, "") == "XMAS" }) 39 | |> result.unwrap(False) 40 | }) 41 | |> result.unwrap(False) 42 | }) 43 | } 44 | 45 | fn x_mas_count(grid_: XGrid, xy: XY) -> Int { 46 | case grid.get(grid_, xy) { 47 | Ok("A") -> { 48 | let x_deltas = list.map(grid.diagonal_dirs, grid.delta) 49 | let xys = x_deltas |> list.map(fn(d) { grid.xy_add(xy, d) }) 50 | case grid.get_all(grid_, xys) { 51 | Error(Nil) -> 0 52 | // TR BR BL TL 53 | // Theoretically we could do "if the list is a palindrome"? 54 | Ok(["M", "M", "S", "S"]) 55 | | Ok(["M", "S", "S", "M"]) 56 | | Ok(["S", "M", "M", "S"]) 57 | | Ok(["S", "S", "M", "M"]) -> 1 58 | Ok(_) -> 0 59 | } 60 | } 61 | _ -> 0 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/aoc_2024/day_5.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/bool 3 | import gleam/dict.{type Dict} 4 | import gleam/int 5 | import gleam/list 6 | import gleam/order 7 | import gleam/result 8 | import gleam/set.{type Set} 9 | import gleam/string 10 | import pocket_watch 11 | 12 | pub type Rule { 13 | Rule(first: Int, then: Int) 14 | } 15 | 16 | pub type Input { 17 | Input(rules: List(Rule), manuals: List(List(Int))) 18 | } 19 | 20 | pub fn parse(input: String) -> Input { 21 | use <- pocket_watch.simple("parse") 22 | case string.split(input, "\n\n") { 23 | [rules_str, manuals_str] -> { 24 | let rules = 25 | rules_str 26 | |> string.split("\n") 27 | |> list.map(fn(line) { 28 | case string.split(line, "|") { 29 | [first, then] -> 30 | Rule(first: extra.yolo_int(first), then: extra.yolo_int(then)) 31 | _ -> panic as "Bad input 2!" 32 | } 33 | }) 34 | let manuals = 35 | manuals_str 36 | |> string.split("\n") 37 | |> list.map(fn(manual) { 38 | manual 39 | |> string.split(",") 40 | |> list.map(extra.yolo_int) 41 | }) 42 | Input(rules, manuals) 43 | } 44 | _ -> panic as "Bad input 1!" 45 | } 46 | } 47 | 48 | fn is_ok(manual: List(Int), dependents: Dict(Int, Set(Int))) { 49 | list.fold_until(manual, #(True, set.new()), fn(acc, page) { 50 | let #(ok, seen) = acc 51 | let dependents_ = dict.get(dependents, page) |> result.unwrap(set.new()) 52 | case set.is_empty(set.intersection(dependents_, seen)) { 53 | True -> list.Continue(#(ok, set.insert(seen, page))) 54 | False -> list.Stop(#(False, seen)) 55 | } 56 | }) 57 | |> fn(acc) { acc.0 } 58 | } 59 | 60 | fn get_dependents(input: Input) -> Dict(Int, Set(Int)) { 61 | list.fold(input.rules, dict.new(), fn(acc, rule) { 62 | let acc_rule = dict.get(acc, rule.first) |> result.unwrap(set.new()) 63 | let new_rule = set.insert(acc_rule, rule.then) 64 | dict.insert(acc, rule.first, new_rule) 65 | }) 66 | } 67 | 68 | fn middle_page(manual: List(Int)) -> Result(Int, Nil) { 69 | let len = list.length(manual) 70 | let drop = len / 2 71 | 72 | manual 73 | |> list.drop(drop) 74 | |> list.first 75 | } 76 | 77 | pub fn pt_1(input: Input) { 78 | use <- pocket_watch.simple("part 1") 79 | let dependents = get_dependents(input) 80 | input.manuals 81 | |> list.filter_map(fn(manual) { 82 | use <- bool.guard(when: !is_ok(manual, dependents), return: Error(Nil)) 83 | middle_page(manual) 84 | }) 85 | |> int.sum 86 | } 87 | 88 | fn fix_order(manual: List(Int), dependents: Dict(Int, Set(Int))) -> List(Int) { 89 | list.sort(manual, by: fn(a, b) { 90 | let a_dependents = dict.get(dependents, a) |> result.unwrap(set.new()) 91 | case set.contains(a_dependents, b) { 92 | True -> order.Lt 93 | False -> { 94 | let b_dependents = dict.get(dependents, b) |> result.unwrap(set.new()) 95 | case set.contains(b_dependents, a) { 96 | True -> order.Gt 97 | False -> order.Eq 98 | } 99 | } 100 | } 101 | }) 102 | } 103 | 104 | pub fn pt_2(input: Input) { 105 | use <- pocket_watch.simple("part 2") 106 | let dependents = get_dependents(input) 107 | input.manuals 108 | |> list.filter_map(fn(manual) { 109 | use <- bool.guard(when: is_ok(manual, dependents), return: Error(Nil)) 110 | middle_page(fix_order(manual, dependents)) 111 | }) 112 | |> int.sum 113 | } 114 | -------------------------------------------------------------------------------- /src/aoc_2024/day_6.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/list 3 | import gleam/set.{type Set} 4 | import grid.{type Dir, type Grid, type XY} 5 | import pocket_watch 6 | 7 | pub type Input { 8 | Input(grid: Grid(Nil), guard_xy: XY) 9 | } 10 | 11 | pub fn parse(input: String) -> Input { 12 | use <- pocket_watch.simple("parse") 13 | let g = grid.from_string(input) 14 | let assert Ok(guard_xy) = grid.find_exact(g, "^") 15 | Input( 16 | grid: grid.filter_map(g, fn(_, c) { 17 | case c { 18 | "#" -> Ok(Nil) 19 | _ -> Error(Nil) 20 | } 21 | }), 22 | guard_xy: guard_xy, 23 | ) 24 | } 25 | 26 | pub fn pt_1(input: Input) { 27 | use <- pocket_watch.simple("part 1") 28 | pt_1_step( 29 | input.grid, 30 | set.from_list([input.guard_xy]), 31 | input.guard_xy, 32 | grid.Top, 33 | ) 34 | |> set.size() 35 | } 36 | 37 | fn pt_1_step(g: Grid(Nil), seen: Set(XY), current_xy: XY, dir: Dir) -> Set(XY) { 38 | let next_xy = grid.step(current_xy, dir, 1) 39 | case grid.get(g, next_xy) { 40 | Ok(_wall) -> pt_1_step(g, seen, current_xy, grid.turn_90deg_right(dir)) 41 | Error(_) -> 42 | case grid.in_grid(g, next_xy) { 43 | True -> pt_1_step(g, set.insert(seen, next_xy), next_xy, dir) 44 | False -> seen 45 | } 46 | } 47 | } 48 | 49 | pub fn pt_2(input: Input) { 50 | use <- pocket_watch.simple("part 2") 51 | let orig_path = 52 | pt_1_step( 53 | input.grid, 54 | set.from_list([input.guard_xy]), 55 | input.guard_xy, 56 | grid.Top, 57 | ) 58 | orig_path 59 | |> set.to_list 60 | |> extra.pmap(fn(step: XY) { 61 | pt_2_has_loop( 62 | grid.insert(input.grid, step, Nil), 63 | set.new(), 64 | input.guard_xy, 65 | grid.Top, 66 | ) 67 | }) 68 | |> list.count(fn(bool) { bool }) 69 | } 70 | 71 | fn pt_2_has_loop( 72 | g: Grid(Nil), 73 | seen: Set(#(XY, Dir)), 74 | current_xy: XY, 75 | dir: Dir, 76 | ) -> Bool { 77 | case set.contains(seen, #(current_xy, dir)) { 78 | True -> True 79 | False -> { 80 | let new_seen = set.insert(seen, #(current_xy, dir)) 81 | let next_xy = grid.step(current_xy, dir, 1) 82 | case grid.get(g, next_xy) { 83 | Ok(_wall) -> { 84 | let next_dir = grid.turn_90deg_right(dir) 85 | pt_2_has_loop(g, new_seen, current_xy, next_dir) 86 | } 87 | Error(_) -> 88 | case grid.in_grid(g, next_xy) { 89 | True -> pt_2_has_loop(g, new_seen, next_xy, dir) 90 | False -> False 91 | } 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/aoc_2024/day_7.gleam: -------------------------------------------------------------------------------- 1 | import extra 2 | import gleam/int 3 | import gleam/list 4 | import gleam/string 5 | import pocket_watch 6 | 7 | pub type Row { 8 | Row(result: Int, operands: List(Int)) 9 | } 10 | 11 | pub fn parse(input: String) -> List(Row) { 12 | use <- pocket_watch.simple("parse") 13 | string.split(input, "\n") 14 | |> list.map(fn(line) { 15 | case string.split(line, ": ") { 16 | [result_str, operands_str] -> { 17 | Row( 18 | result: extra.yolo_int(result_str), 19 | operands: string.split(operands_str, " ") 20 | |> list.map(extra.yolo_int), 21 | ) 22 | } 23 | _ -> panic as "Bad input 1" 24 | } 25 | }) 26 | } 27 | 28 | pub fn pt_1(input: List(Row)) { 29 | use <- pocket_watch.simple("part 1") 30 | input 31 | |> list.filter(fn(row) { can_be_combined(row, [int.add, int.multiply]) }) 32 | |> list.map(fn(row) { row.result }) 33 | |> int.sum 34 | } 35 | 36 | pub fn pt_2(input: List(Row)) { 37 | use <- pocket_watch.simple("part 2") 38 | input 39 | |> list.filter(fn(row) { 40 | can_be_combined(row, [int.add, int.multiply, concat_digits]) 41 | }) 42 | |> list.map(fn(row) { row.result }) 43 | |> int.sum 44 | } 45 | 46 | fn can_be_combined(row: Row, ops: List(fn(Int, Int) -> Int)) -> Bool { 47 | case row.operands { 48 | [first, ..rest] -> search(row.result, [#(first, rest)], ops) 49 | _ -> panic as "No operands?!" 50 | } 51 | } 52 | 53 | fn concat_digits(a: Int, b: Int) -> Int { 54 | // TODO could be faster if we do math (a * log10(b) + b) 55 | extra.yolo_int(int.to_string(a) <> int.to_string(b)) 56 | } 57 | 58 | fn search( 59 | result: Int, 60 | todos: List(#(Int, List(Int))), 61 | ops: List(fn(Int, Int) -> Int), 62 | ) -> Bool { 63 | case todos { 64 | [] -> False 65 | [#(acc_sum, operands), ..rest_of_todos] -> { 66 | case operands { 67 | [] -> 68 | case acc_sum == result { 69 | True -> True 70 | False -> search(result, rest_of_todos, ops) 71 | } 72 | [operand, ..rest_of_operands] -> { 73 | case acc_sum > result { 74 | True -> search(result, rest_of_todos, ops) 75 | False -> 76 | search( 77 | result, 78 | list.append( 79 | list.map(ops, fn(op) { 80 | #(op(acc_sum, operand), rest_of_operands) 81 | }), 82 | rest_of_todos, 83 | ), 84 | ops, 85 | ) 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/aoc_2024/day_8.gleam: -------------------------------------------------------------------------------- 1 | import gleam/dict 2 | import gleam/list 3 | import gleam/set 4 | import grid.{type Dims, type Grid, type XY} 5 | import pocket_watch 6 | 7 | pub fn parse(input: String) -> Grid(String) { 8 | use <- pocket_watch.simple("parse") 9 | grid.from_string(input) 10 | |> grid.filter(fn(_, c) { c != "." }) 11 | } 12 | 13 | pub fn pt_1(input: Grid(String)) { 14 | use <- pocket_watch.simple("part 1") 15 | input 16 | |> grid.to_list 17 | |> list.group(by: fn(cell) { cell.1 }) 18 | |> dict.values 19 | |> list.map(fn(nodes) { list.map(nodes, fn(node) { node.0 }) }) 20 | |> list.fold(from: set.new(), with: fn(acc, nodes) { 21 | nodes 22 | |> list.combination_pairs 23 | |> list.flat_map(antinodes_xy_pt1) 24 | |> list.filter(fn(xy) { grid.in_grid(input, xy) }) 25 | |> set.from_list 26 | |> set.union(acc) 27 | }) 28 | |> set.size 29 | } 30 | 31 | fn antinodes_xy_pt1(pair: #(XY, XY)) -> List(XY) { 32 | let #(a, b) = pair 33 | let diff = grid.xy_sub(a, b) 34 | [grid.xy_add(a, diff), grid.xy_sub(b, diff)] 35 | } 36 | 37 | pub fn pt_2(input: Grid(String)) { 38 | use <- pocket_watch.simple("part 2") 39 | input 40 | |> grid.to_list 41 | |> list.group(by: fn(cell) { cell.1 }) 42 | |> dict.values 43 | |> list.map(fn(nodes) { list.map(nodes, fn(node) { node.0 }) }) 44 | |> list.fold(from: set.new(), with: fn(acc, nodes) { 45 | nodes 46 | |> list.combination_pairs 47 | |> list.flat_map(antinodes_xy_pt2(input.dims, _)) 48 | |> set.from_list 49 | |> set.union(acc) 50 | }) 51 | |> set.size 52 | } 53 | 54 | fn antinodes_xy_pt2(dims: Dims, pair: #(XY, XY)) -> List(XY) { 55 | let #(a, b) = pair 56 | list.append( 57 | cast_ray(dims, a, grid.xy_sub(a, b), [a]), 58 | cast_ray(dims, b, grid.xy_sub(b, a), [b]), 59 | ) 60 | } 61 | 62 | fn cast_ray(dims: Dims, start: XY, d: XY, acc: List(XY)) -> List(XY) { 63 | let next = grid.xy_add(start, d) 64 | case grid.in_dims(dims, next) { 65 | False -> acc 66 | True -> cast_ray(dims, next, d, [next, ..acc]) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/extra.gleam: -------------------------------------------------------------------------------- 1 | import gleam/dict.{type Dict} 2 | import gleam/float 3 | import gleam/int 4 | import gleam/io 5 | import gleam/list 6 | import gleam/option 7 | import gleam/otp/task 8 | import gleam/string 9 | import gleam/yielder.{type Yielder} 10 | 11 | /// Needs the operation to be associative and there to be a zero (monoid). 12 | /// Uses parallelism under the hood. 13 | pub fn smush(xs: List(x), zero: x, fun: fn(x, x) -> x) -> x { 14 | smush_aux(xs, list.length(xs), zero, fun) 15 | } 16 | 17 | fn smush_aux(xs: List(x), length: Int, zero: x, fun: fn(x, x) -> x) -> x { 18 | case xs { 19 | [] -> zero 20 | [x] -> x 21 | _ -> { 22 | let half = length / 2 23 | let #(left, right) = list.split(xs, at: half) 24 | let left_handle = task.async(fn() { smush_aux(left, half, zero, fun) }) 25 | let right_result = smush_aux(right, length - half, zero, fun) 26 | let left_result = task.await_forever(left_handle) 27 | fun(left_result, right_result) 28 | } 29 | } 30 | } 31 | 32 | pub fn pmap(xs: List(x), with fun: fn(x) -> y) -> List(y) { 33 | xs 34 | |> list.map(fn(x) { task.async(fn() { fun(x) }) }) 35 | |> list.map(task.await_forever) 36 | } 37 | 38 | pub fn pmap2(xs: List(x), ys: List(y), with fun: fn(x, y) -> z) -> List(z) { 39 | list.map2(xs, ys, fn(x, y) { task.async(fn() { fun(x, y) }) }) 40 | |> list.map(task.await_forever) 41 | } 42 | 43 | /// frequencies([5,10,2,5,10,5]) -> dict.from_list([ #(2,1), #(5,3), #(10,2) ]) 44 | pub fn frequencies(xs: List(a)) -> Dict(a, Int) { 45 | xs 46 | |> list.fold(from: dict.new(), with: fn(counter, x) { 47 | counter 48 | |> dict.upsert(x, fn(old) { option.unwrap(old, 0) + 1 }) 49 | }) 50 | } 51 | 52 | /// remove_at([5,10,2],1) -> [5,2] 53 | pub fn remove_at(xs: List(a), at i: Int) -> List(a) { 54 | list.append(list.take(xs, i), list.drop(xs, i + 1)) 55 | } 56 | 57 | pub fn yolo_int(x: String) -> Int { 58 | let assert Ok(n) = int.parse(x) 59 | n 60 | } 61 | 62 | /// strip_left("Hello World", "Hello ") -> "World" 63 | pub fn strip_left(from input: String, remove prefix: String) -> String { 64 | case string.starts_with(input, prefix) { 65 | True -> input |> string.drop_start(string.length(prefix)) 66 | False -> input 67 | } 68 | } 69 | 70 | pub fn log(label: String, value: a) -> a { 71 | io.print_error(label <> ": ") 72 | let _ = io.debug(value) 73 | io.println_error("") 74 | value 75 | } 76 | 77 | pub fn do_if(subject: a, pred pred: Bool, fun fun: fn(a) -> a) -> a { 78 | case pred { 79 | True -> fun(subject) 80 | False -> subject 81 | } 82 | } 83 | 84 | @external(erlang, "timer", "sleep") 85 | pub fn sleep(ms: Int) -> Nil 86 | 87 | pub fn add_line_numbers(str: String) -> String { 88 | str 89 | |> string.split("\n") 90 | |> list.index_map(fn(s, i) { 91 | { 92 | int.to_string(i) 93 | |> string.pad_start(3, with: " ") 94 | } 95 | <> " " 96 | <> s 97 | }) 98 | |> string.join("\n") 99 | } 100 | 101 | pub fn string_set(str: String, at: Int, value: String) -> String { 102 | str 103 | |> string.to_graphemes 104 | // PERF: fold_until 105 | |> list.index_map(fn(s, i) { 106 | case i == at { 107 | True -> value 108 | False -> s 109 | } 110 | }) 111 | |> string.join("") 112 | } 113 | 114 | pub fn pow(b: Int, e: Int) -> Int { 115 | let assert Ok(x) = int.power(b, int.to_float(e)) 116 | float.truncate(x) 117 | } 118 | 119 | pub fn yield_combination_pairs(items: Yielder(a)) -> Yielder(#(a, a)) { 120 | case yielder.step(items) { 121 | yielder.Done -> yielder.empty() 122 | yielder.Next(x, acc) -> 123 | yielder.append( 124 | yielder.map(acc, fn(y) { #(x, y) }), 125 | yield_combination_pairs(acc), 126 | ) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /start-roc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | YEAR="${1}" 4 | DAY="${2}" 5 | DAY_NO_ZEROS="$(echo $DAY | sed 's/^0*//')" 6 | INPUT="Template.roc" 7 | OUTPUT="src/Year${YEAR}/Day${DAY}.roc" 8 | PUZZLE_URL="https://adventofcode.com/${YEAR}/day/${DAY_NO_ZEROS}/input" 9 | PUZZLE_FILE="input${YEAR}${DAY}.txt" 10 | 11 | curl "${PUZZLE_URL}" -H "cookie: session=${AOC_SESSION_COOKIE}" -o "${PUZZLE_FILE}" 2>/dev/null 12 | cat "${PUZZLE_FILE}" 13 | mkdir -p "$(dirname ${OUTPUT})" 14 | cp "${INPUT}" "${OUTPUT}" 15 | gsed -i "s/inputXXX.txt/${PUZZLE_FILE}/g" "${OUTPUT}" 16 | 17 | ./watch-roc.sh "${YEAR}" "${DAY}" 18 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | YEAR="${1}" 4 | DAY="${2}" 5 | DAY_NO_ZEROS="$(echo $DAY | sed 's/^0*//')" 6 | INPUT="Template.elm" 7 | OUTPUT="src/Year${YEAR}/Day${DAY}.elm" 8 | PUZZLE_URL="https://adventofcode.com/${YEAR}/day/${DAY_NO_ZEROS}/input" 9 | PUZZLE_FILE="input${YEAR}${DAY}.txt" 10 | 11 | curl "${PUZZLE_URL}" -H "cookie: session=${AOC_SESSION_COOKIE}" -o "${PUZZLE_FILE}" 2>/dev/null 12 | mkdir -p "$(dirname ${OUTPUT})" 13 | cp "${INPUT}" "${OUTPUT}" 14 | sed -i "s/YearXXX/Year${YEAR}/g" "${OUTPUT}" 15 | sed -i "s/DayXXX/Day${DAY}/g" "${OUTPUT}" 16 | sed -i -e "/InputXXX/r ${PUZZLE_FILE}" -e "/InputXXX/d" "${OUTPUT}" 17 | rm "${PUZZLE_FILE}" 18 | 19 | ./watch.sh "${YEAR}" "${DAY}" 20 | -------------------------------------------------------------------------------- /tests/Year2018Day15Tests.elm: -------------------------------------------------------------------------------- 1 | module Year2018Day15Tests exposing (..) 2 | 3 | import Advent 4 | import Expect 5 | import Grid 6 | import Test exposing (Test) 7 | import Year2018.Day15 as Problem exposing (Entity(..)) 8 | 9 | 10 | readingOrder : Test 11 | readingOrder = 12 | Test.describe "reading order" 13 | [ Test.test "given a list of positions it sorts them" <| 14 | \() -> 15 | """ 16 | ####### 17 | #.G.E.# 18 | #E.G.E# 19 | #.G.E.# 20 | ####### 21 | """ 22 | |> Advent.removeNewlinesAtEnds 23 | |> Problem.parse1 24 | |> Problem.getNPCs 25 | |> List.map Tuple.first 26 | |> Problem.sortByReadingOrder 27 | {- 28 | ####### 29 | #.1.2.# 30 | #3.4.5# 31 | #.6.7.# 32 | ####### 33 | -} 34 | |> Expect.equal 35 | [ ( 2, 1 ) 36 | , ( 4, 1 ) 37 | , ( 1, 2 ) 38 | , ( 3, 2 ) 39 | , ( 5, 2 ) 40 | , ( 2, 3 ) 41 | , ( 4, 3 ) 42 | ] 43 | ] 44 | 45 | 46 | outcome : Test 47 | outcome = 48 | let 49 | cases : List ( String, ( Int, List Int ), Int ) 50 | cases = 51 | [ ( "main example", ( 47, [ 200, 131, 59, 200 ] ), 27730 ) 52 | , ( "summarized example 1", ( 37, [ 200, 197, 185, 200, 200 ] ), 36334 ) 53 | , ( "summarized example 2", ( 46, [ 164, 197, 200, 98, 200 ] ), 39514 ) 54 | , ( "summarized example 3", ( 35, [ 200, 98, 200, 95, 200 ] ), 27755 ) 55 | , ( "summarized example 4", ( 54, [ 200, 98, 38, 200 ] ), 28944 ) 56 | , ( "summarized example 5", ( 20, [ 137, 200, 200, 200, 200 ] ), 18740 ) 57 | ] 58 | 59 | testCase : ( String, ( Int, List Int ), Int ) -> Test 60 | testCase ( label, ( fullRounds, hitpoints ), expectedOutcome ) = 61 | Test.test label <| 62 | \() -> 63 | Problem.outcomeScore 64 | { fullRounds = fullRounds 65 | , hitpoints = hitpoints 66 | } 67 | |> Expect.equal expectedOutcome 68 | in 69 | Test.describe "outcome" <| 70 | List.map testCase cases 71 | 72 | 73 | isEnd : Test 74 | isEnd = 75 | let 76 | cases : List ( String, List Entity, Bool ) 77 | cases = 78 | [ ( "single goblin", [ Goblin 200 ], True ) 79 | , ( "single elf", [ Elf 1 ], True ) 80 | , ( "two goblins", [ Goblin 10, Goblin 20 ], True ) 81 | , ( "two elves", [ Elf 10, Elf 20 ], True ) 82 | , ( "mixed", [ Goblin 10, Elf 20 ], False ) 83 | , ( "empty", [], True ) 84 | ] 85 | 86 | testCase : ( String, List Entity, Bool ) -> Test 87 | testCase ( label, entities, expectedOutput ) = 88 | Test.test label <| 89 | \() -> 90 | Problem.isEnd entities 91 | |> Expect.equal expectedOutput 92 | in 93 | Test.describe "isEnd" <| 94 | List.map testCase cases 95 | -------------------------------------------------------------------------------- /watch-roc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | YEAR="${1}" 4 | DAY="${2}" 5 | INPUT="src/Year${YEAR}/Day${DAY}.roc" 6 | 7 | chokidar '**/*.roc' | while read WHATEVER; do 8 | clear && tput reset && echo -en "\033c\033[3J" 9 | roc test "${INPUT}" && RUST_BACKTRACE=full roc dev "${INPUT}" 10 | #RUST_BACKTRACE=full roc dev "${INPUT}" 11 | done; 12 | -------------------------------------------------------------------------------- /watch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | YEAR="${1}" 4 | DAY="${2}" 5 | INPUT="src/Year${YEAR}/Day${DAY}.elm" 6 | OUTPUT="js/${YEAR}-${DAY}.js" 7 | 8 | ./run.sh "${1}" "${2}"; 9 | chokidar '**/*.elm' | while read WHATEVER; do 10 | ./run.sh "${1}" "${2}"; 11 | done; 12 | --------------------------------------------------------------------------------