├── .github ├── dependabot.yml └── workflows │ ├── get-inputs.yml │ ├── hs-bench.yml │ ├── hs-docs.yml │ ├── hs.yml │ ├── hyperfine.yml │ ├── kt-bench.yml │ ├── kt-docs.yml │ ├── kt.yml │ ├── py-bench.yml │ ├── py.yml │ ├── rs-bench.yml │ └── rs.yml ├── .gitignore ├── README.md ├── get-inputs.main.kts ├── hs ├── .gitignore ├── ChangeLog.md ├── LICENSE ├── README.md ├── Setup.hs ├── app │ ├── Main.hs │ └── cbits │ │ └── main.c ├── bench │ ├── Main.hs │ └── cbits │ │ └── main.c ├── package.yaml ├── src │ ├── Common.hs │ ├── Day1.hs │ ├── Day10.hs │ ├── Day11.hs │ ├── Day12.hs │ ├── Day13.hs │ ├── Day14.hs │ ├── Day15.hs │ ├── Day16.hs │ ├── Day17.hs │ ├── Day18.hs │ ├── Day19.hs │ ├── Day2.hs │ ├── Day20.hs │ ├── Day21.hs │ ├── Day22.hs │ ├── Day23.hs │ ├── Day24.hs │ ├── Day25.hs │ ├── Day3.hs │ ├── Day4.hs │ ├── Day5.hs │ ├── Day6.hs │ ├── Day6Meta.hs │ ├── Day7.hs │ ├── Day8.hs │ └── Day9.hs ├── stack.yaml ├── stack.yaml.lock └── test │ ├── CommonSpec.hs │ ├── Day10Spec.hs │ ├── Day11Spec.hs │ ├── Day12Spec.hs │ ├── Day13Spec.hs │ ├── Day14Spec.hs │ ├── Day15Spec.hs │ ├── Day16Spec.hs │ ├── Day17Spec.hs │ ├── Day18Spec.hs │ ├── Day19Spec.hs │ ├── Day1Spec.hs │ ├── Day20Spec.hs │ ├── Day21Spec.hs │ ├── Day22Spec.hs │ ├── Day23Spec.hs │ ├── Day24Spec.hs │ ├── Day25Spec.hs │ ├── Day2Spec.hs │ ├── Day3Spec.hs │ ├── Day4Spec.hs │ ├── Day5Spec.hs │ ├── Day6Spec.hs │ ├── Day7Spec.hs │ ├── Day8Spec.hs │ ├── Day9Spec.hs │ └── Main.hs ├── kt ├── .gitignore ├── README.md ├── build.gradle.kts ├── detekt.yml ├── graalvm │ └── build.gradle.kts ├── gradle.properties ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kotlin-js-store │ └── yarn.lock ├── settings.gradle.kts └── src │ ├── commonBench │ └── kotlin │ │ └── com │ │ └── github │ │ └── ephemient │ │ └── aoc2021 │ │ ├── Day10Bench.kt │ │ ├── Day11Bench.kt │ │ ├── Day12Bench.kt │ │ ├── Day13Bench.kt │ │ ├── Day14Bench.kt │ │ ├── Day15Bench.kt │ │ ├── Day16Bench.kt │ │ ├── Day17Bench.kt │ │ ├── Day18Bench.kt │ │ ├── Day19Bench.kt │ │ ├── Day1Bench.kt │ │ ├── Day20Bench.kt │ │ ├── Day21Bench.kt │ │ ├── Day22Bench.kt │ │ ├── Day23Bench.kt │ │ ├── Day24Bench.kt │ │ ├── Day25Bench.kt │ │ ├── Day2Bench.kt │ │ ├── Day3Bench.kt │ │ ├── Day4Bench.kt │ │ ├── Day5Bench.kt │ │ ├── Day6Bench.kt │ │ ├── Day7Bench.kt │ │ ├── Day8Bench.kt │ │ └── Day9Bench.kt │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── github │ │ └── ephemient │ │ └── aoc2021 │ │ ├── CacheSet.kt │ │ ├── CardinalDirection.kt │ │ ├── Day1.kt │ │ ├── Day10.kt │ │ ├── Day11.kt │ │ ├── Day12.kt │ │ ├── Day13.kt │ │ ├── Day14.kt │ │ ├── Day15.kt │ │ ├── Day16.kt │ │ ├── Day17.kt │ │ ├── Day18.kt │ │ ├── Day19.kt │ │ ├── Day2.kt │ │ ├── Day20.kt │ │ ├── Day21.kt │ │ ├── Day22.kt │ │ ├── Day23.kt │ │ ├── Day24.kt │ │ ├── Day24Common.kt │ │ ├── Day24Impl.kt │ │ ├── Day24Range.kt │ │ ├── Day25.kt │ │ ├── Day3.kt │ │ ├── Day4.kt │ │ ├── Day5.kt │ │ ├── Day6.kt │ │ ├── Day7.kt │ │ ├── Day8.kt │ │ ├── Day9.kt │ │ ├── IntPair.kt │ │ ├── LongPair.kt │ │ ├── PriorityQueue.kt │ │ ├── common.kt │ │ └── main.kt │ ├── commonTest │ └── kotlin │ │ └── com │ │ └── github │ │ └── ephemient │ │ └── aoc2021 │ │ ├── Day10Test.kt │ │ ├── Day11Test.kt │ │ ├── Day12Test.kt │ │ ├── Day13Test.kt │ │ ├── Day14Test.kt │ │ ├── Day15Test.kt │ │ ├── Day16Test.kt │ │ ├── Day17Test.kt │ │ ├── Day18Test.kt │ │ ├── Day19Test.kt │ │ ├── Day1Test.kt │ │ ├── Day20Test.kt │ │ ├── Day21Test.kt │ │ ├── Day22Test.kt │ │ ├── Day23Test.kt │ │ ├── Day24Test.kt │ │ ├── Day25Test.kt │ │ ├── Day2Test.kt │ │ ├── Day3Test.kt │ │ ├── Day4Test.kt │ │ ├── Day5Test.kt │ │ ├── Day6Test.kt │ │ ├── Day7Test.kt │ │ ├── Day8Test.kt │ │ ├── Day9Test.kt │ │ ├── JsIgnore.kt │ │ ├── JvmIgnore.kt │ │ ├── PermutationsTest.kt │ │ └── PowTest.kt │ ├── jsMain │ └── kotlin │ │ └── com │ │ └── github │ │ └── ephemient │ │ └── aoc2021 │ │ └── Config.kt │ ├── jsTest │ └── kotlin │ │ └── com │ │ └── github │ │ └── ephemient │ │ └── aoc2021 │ │ └── JsIgnore.kt │ ├── jvmMain │ ├── kotlin │ │ └── com │ │ │ └── github │ │ │ └── ephemient │ │ │ └── aoc2021 │ │ │ ├── Day24.kt │ │ │ ├── Day24Jvm.kt │ │ │ ├── PriorityQueue.kt │ │ │ └── Resources.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.github.ephemient.aoc2021.Day24Impl$Provider │ ├── jvmTest │ └── kotlin │ │ └── com │ │ └── github │ │ └── ephemient │ │ └── aoc2021 │ │ ├── Day24JvmTest.kt │ │ └── JvmIgnore.kt │ ├── nonJsMain │ └── kotlin │ │ └── com │ │ └── github │ │ └── ephemient │ │ └── aoc2021 │ │ └── Config.kt │ └── nonJvmMain │ └── kotlin │ └── com │ └── github │ └── ephemient │ └── aoc2021 │ ├── Day24.kt │ └── PriorityQueue.kt ├── py ├── .flake8 ├── .gitignore ├── LICENSE ├── README.md ├── aoc2021 │ ├── __init__.py │ ├── day1.py │ ├── day10.py │ ├── day11.py │ ├── day12.py │ ├── day13.py │ ├── day14.py │ ├── day15.py │ ├── day16.py │ ├── day17.py │ ├── day18.py │ ├── day19.py │ ├── day2.py │ ├── day20.py │ ├── day21.py │ ├── day22.py │ ├── day23.py │ ├── day24.py │ ├── day25.py │ ├── day3.py │ ├── day4.py │ ├── day5.py │ ├── day6.py │ ├── day7.py │ ├── day8.py │ ├── day9.py │ └── main.py ├── poetry.lock ├── pyproject.toml └── tests │ └── test_benchmark.py └── rs ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── benches └── criterion.rs ├── build.rs └── src ├── day1.rs ├── day10.rs ├── day11.rs ├── day12.rs ├── day13.rs ├── day14.rs ├── day15.rs ├── day16.rs ├── day17.rs ├── day18.rs ├── day19.rs ├── day2.rs ├── day20.rs ├── day21.rs ├── day22.rs ├── day23.rs ├── day24.rs ├── day25.rs ├── day3.rs ├── day4.rs ├── day5.rs ├── day6.rs ├── day7.rs ├── day8.rs ├── day9.rs ├── lib.rs ├── main.rs └── util.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: /kt 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: pip 8 | directory: /py 9 | schedule: 10 | interval: daily 11 | - package-ecosystem: cargo 12 | directory: /rs 13 | schedule: 14 | interval: daily 15 | - package-ecosystem: bundler 16 | directory: / 17 | target-branch: gh-docs 18 | schedule: 19 | interval: daily 20 | -------------------------------------------------------------------------------- /.github/workflows/get-inputs.yml: -------------------------------------------------------------------------------- 1 | name: Get inputs 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | year: 7 | required: false 8 | type: number 9 | secrets: 10 | SESSION: 11 | required: true 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Get Day 19 | id: get-day 20 | run: | 21 | from datetime import datetime, timedelta, tzinfo 22 | class TZ(tzinfo): 23 | def utcoffset(self, dt): 24 | return timedelta(hours=-5) + self.dst(dt) 25 | def dst(self, dt): 26 | return timedelta(0) 27 | year = ${{ inputs.year }} or 2021 28 | day = max(0, min(25, (datetime.now(TZ()) - datetime(year, 12, 1, 0, 0, 0, 0, TZ())).days + 1)) 29 | print("::set-output name=year::" + str(year)) 30 | print("::set-output name=day::" + str(min(25, max(0, day)))) 31 | print("::set-output name=days::" + ' '.join(map(str, range(1, day + 1)))) 32 | shell: python 33 | - id: cache 34 | uses: actions/cache@v2 35 | with: 36 | key: inputs-${{ steps.get-day.outputs.day }} 37 | restore-keys: inputs- 38 | path: day*.txt 39 | - name: Download inputs 40 | if: steps.cache.outputs.cache-hit != 'true' 41 | run: | 42 | for day in ${{ steps.get-day.outputs.days }}; do 43 | [[ -e day$day.txt ]] || curl -b session=$SESSION -o day$day.txt -f https://adventofcode.com/${{ steps.get-day.outputs.year }}/day/$day/input 44 | done 45 | shell: bash --noprofile --norc -euxo pipefail {0} 46 | env: 47 | SESSION: ${{ secrets.SESSION }} 48 | - uses: actions/upload-artifact@v2 49 | with: 50 | name: inputs 51 | path: day*.txt 52 | -------------------------------------------------------------------------------- /.github/workflows/hs-bench.yml: -------------------------------------------------------------------------------- 1 | name: Haskell benchmarks 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | get-inputs: 8 | uses: ephemient/aoc2021/.github/workflows/get-inputs.yml@main 9 | secrets: 10 | SESSION: ${{ secrets.SESSION }} 11 | 12 | build: 13 | needs: [ get-inputs ] 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/checkout@v2 19 | with: 20 | ref: gh-docs 21 | path: gh-docs 22 | - uses: actions/download-artifact@v2 23 | with: 24 | name: inputs 25 | path: hs 26 | - uses: haskell/actions/setup@v1 27 | with: 28 | enable-stack: true 29 | - uses: actions/cache@v2 30 | with: 31 | key: ${{ runner.os }}-criterion-${{ hashFiles('**/stack.*') }}-${{ hashFiles('**/package.yaml') }} 32 | restore-keys: ${{ runner.os }}-criterion-${{ hashFiles('**/stack.*') }}- 33 | path: ~/.stack 34 | - run: stack bench --ba '-o ${{ github.workspace }}/gh-docs/aoc2021-bench.html' 35 | working-directory: hs 36 | - uses: EndBug/add-and-commit@v7 37 | with: 38 | cwd: gh-docs 39 | branch: gh-docs 40 | add: aoc2021-bench.html 41 | message: 'Haskell Criterion ${{ github.sha }}' 42 | -------------------------------------------------------------------------------- /.github/workflows/hs-docs.yml: -------------------------------------------------------------------------------- 1 | name: Haskell docs 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | get-inputs: 8 | uses: ephemient/aoc2021/.github/workflows/get-inputs.yml@main 9 | secrets: 10 | SESSION: ${{ secrets.SESSION }} 11 | 12 | build: 13 | needs: [ get-inputs ] 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/checkout@v2 19 | with: 20 | ref: gh-docs 21 | path: gh-docs 22 | - uses: actions/download-artifact@v2 23 | with: 24 | name: inputs 25 | path: hs 26 | - uses: haskell/actions/setup@v1 27 | with: 28 | enable-stack: true 29 | - uses: actions/cache@v2 30 | with: 31 | key: ${{ runner.os }}-haddock-${{ hashFiles('**/stack.*') }}-${{ hashFiles('**/package.yaml') }} 32 | restore-keys: ${{ runner.os }}-haddock-${{ hashFiles('**/stack.*') }}- 33 | path: ~/.stack 34 | - run: stack haddock --only-dependencies 35 | working-directory: hs 36 | - run: stack haddock --only-locals --haddock-internal --haddock-arguments --ignore-all-exports 37 | working-directory: hs 38 | - run: rsync --archive --delete --verbose --whole-file hs/.stack-work/dist/*/*/doc/html/aoc2021/* gh-docs/haddock/ 39 | - name: Rewrite links to Hackage 40 | run: find gh-docs/haddock -name '*.html' -print0 | xargs -0r sed -i -e 's!"\.\./\([^"/]\+\)/!"https://hackage.haskell.org/package/\1/docs/!g' 41 | - uses: EndBug/add-and-commit@v7 42 | with: 43 | cwd: gh-docs 44 | branch: gh-docs 45 | add: haddock 46 | message: 'Haddock ${{ github.sha }}' 47 | -------------------------------------------------------------------------------- /.github/workflows/hs.yml: -------------------------------------------------------------------------------- 1 | name: Haskell CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: [ hs/** ] 7 | pull_request: 8 | branches: [ main ] 9 | paths: [ hs/** ] 10 | 11 | workflow_dispatch: 12 | 13 | jobs: 14 | get-inputs: 15 | uses: ephemient/aoc2021/.github/workflows/get-inputs.yml@main 16 | secrets: 17 | SESSION: ${{ secrets.SESSION }} 18 | 19 | build: 20 | needs: [ get-inputs ] 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions/download-artifact@v2 26 | with: 27 | name: inputs 28 | path: hs 29 | - uses: haskell/actions/setup@v1 30 | with: 31 | enable-stack: true 32 | - uses: actions/cache@v2 33 | with: 34 | key: ${{ runner.os }}-${{ hashFiles('**/stack.*') }}-${{ hashFiles('**/package.yaml') }} 35 | restore-keys: ${{ runner.os }}-${{ hashFiles('**/stack.*') }}- 36 | path: ~/.stack 37 | - run: stack build hlint --exec 'hlint .' 38 | working-directory: hs 39 | - run: stack build --test --bench --no-run-benchmarks 40 | working-directory: hs 41 | -------------------------------------------------------------------------------- /.github/workflows/kt-docs.yml: -------------------------------------------------------------------------------- 1 | name: Kotlin docs 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | get-inputs: 8 | uses: ephemient/aoc2021/.github/workflows/get-inputs.yml@main 9 | secrets: 10 | SESSION: ${{ secrets.SESSION }} 11 | 12 | build: 13 | needs: [ get-inputs ] 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/checkout@v2 19 | with: 20 | ref: gh-docs 21 | path: gh-docs 22 | - uses: actions/download-artifact@v2 23 | with: 24 | name: inputs 25 | - uses: actions/setup-java@v2 26 | with: 27 | distribution: temurin 28 | java-version: 17 29 | cache: gradle 30 | - run: ./gradlew --no-daemon --info dokkaHtml 31 | working-directory: kt 32 | - run: rsync --archive --delete --verbose --whole-file kt/build/dokka/html/ gh-docs/dokka/ 33 | - uses: EndBug/add-and-commit@v7 34 | with: 35 | cwd: gh-docs 36 | branch: gh-docs 37 | add: dokka 38 | message: 'Dokka ${{ github.sha }}' 39 | -------------------------------------------------------------------------------- /.github/workflows/kt.yml: -------------------------------------------------------------------------------- 1 | name: Kotlin CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: [ kt/** ] 7 | pull_request: 8 | branches: [ main ] 9 | paths: [ kt/** ] 10 | 11 | workflow_dispatch: 12 | 13 | jobs: 14 | get-inputs: 15 | uses: ephemient/aoc2021/.github/workflows/get-inputs.yml@main 16 | secrets: 17 | SESSION: ${{ secrets.SESSION }} 18 | 19 | build: 20 | needs: [ get-inputs ] 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions/download-artifact@v2 26 | with: 27 | name: inputs 28 | - uses: actions/setup-java@v2 29 | with: 30 | distribution: temurin 31 | java-version: 17 32 | cache: gradle 33 | - uses: actions/cache@v2 34 | with: 35 | key: ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle*') }} 36 | restore-keys: ${{ runner.os }}-konan- 37 | path: ~/.konan 38 | - run: ./gradlew --no-daemon --info build assembleBenchmarks 39 | working-directory: kt 40 | 41 | gradle-wrapper-validation: 42 | runs-on: ubuntu-latest 43 | 44 | steps: 45 | - uses: actions/checkout@v2 46 | - uses: gradle/wrapper-validation-action@v1 47 | -------------------------------------------------------------------------------- /.github/workflows/py-bench.yml: -------------------------------------------------------------------------------- 1 | name: Python benchmarks 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | get-inputs: 8 | uses: ephemient/aoc2021/.github/workflows/get-inputs.yml@main 9 | secrets: 10 | SESSION: ${{ secrets.SESSION }} 11 | 12 | build: 13 | needs: [ get-inputs ] 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/checkout@v2 19 | with: 20 | ref: gh-docs 21 | path: gh-docs 22 | - uses: actions/download-artifact@v2 23 | with: 24 | name: inputs 25 | path: py/aoc2021 26 | - uses: actions/setup-python@v2 27 | with: 28 | python-version: '3.10' 29 | - uses: snok/install-poetry@v1 30 | - uses: actions/cache@v2 31 | with: 32 | key: ${{ runner.os }}-${{ hashFiles('**/pyproject.toml') }}-${{ hashFiles('**/poetry.lock') }} 33 | path: ~/.cache/pypoetry 34 | - run: poetry install --no-interaction --no-root 35 | working-directory: py 36 | - run: poetry run pytest --benchmark-enable --benchmark-only --benchmark-histogram=../gh-docs/benchmark 37 | working-directory: py 38 | - uses: EndBug/add-and-commit@v7 39 | with: 40 | cwd: gh-docs 41 | branch: gh-docs 42 | add: benchmark.svg 43 | message: 'pytest-benchmark ${{ github.sha }}' 44 | -------------------------------------------------------------------------------- /.github/workflows/py.yml: -------------------------------------------------------------------------------- 1 | name: Python CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: [ py/** ] 7 | pull_request: 8 | branches: [ main ] 9 | paths: [ py/** ] 10 | 11 | workflow_dispatch: 12 | 13 | jobs: 14 | get-inputs: 15 | uses: ephemient/aoc2021/.github/workflows/get-inputs.yml@main 16 | secrets: 17 | SESSION: ${{ secrets.SESSION }} 18 | 19 | build: 20 | needs: [ get-inputs ] 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions/download-artifact@v2 26 | with: 27 | name: inputs 28 | path: py/aoc2021 29 | - uses: actions/setup-python@v2 30 | with: 31 | python-version: '3.10' 32 | - uses: snok/install-poetry@v1 33 | - uses: actions/cache@v2 34 | with: 35 | key: ${{ runner.os }}-${{ hashFiles('**/pyproject.toml') }}-${{ hashFiles('**/poetry.lock') }} 36 | path: ~/.cache/pypoetry 37 | - run: poetry install --no-interaction --no-root 38 | working-directory: py 39 | - run: poetry run flake8 . 40 | working-directory: py 41 | - run: poetry run black . --check 42 | working-directory: py 43 | - run: poetry run isort . --check 44 | working-directory: py 45 | - run: poetry run pytest 46 | working-directory: py 47 | -------------------------------------------------------------------------------- /.github/workflows/rs.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: [ rs/** ] 7 | pull_request: 8 | branches: [ main ] 9 | paths: [ rs/** ] 10 | 11 | workflow_dispatch: 12 | 13 | jobs: 14 | get-inputs: 15 | uses: ephemient/aoc2021/.github/workflows/get-inputs.yml@main 16 | secrets: 17 | SESSION: ${{ secrets.SESSION }} 18 | 19 | build: 20 | needs: [ get-inputs ] 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions/download-artifact@v2 26 | with: 27 | name: inputs 28 | - id: rust-toolchain 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | toolchain: stable 32 | profile: minimal 33 | components: clippy, rustfmt 34 | default: true 35 | - uses: actions/cache@v2 36 | with: 37 | key: ${{ runner.os }}-${{ steps.rust-toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.*') }} 38 | restore-keys: ${{ runner.os }}-${{ steps.rust-toolchain.outputs.rustc_hash }}- 39 | path: ~/.cargo 40 | - run: cargo fmt -- --check 41 | working-directory: rs 42 | - run: cargo clippy -- --deny warnings 43 | working-directory: rs 44 | - run: cargo test 45 | working-directory: rs 46 | - run: cargo build --all-features --all-targets --release 47 | working-directory: rs 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /day*.txt 2 | -------------------------------------------------------------------------------- /hs/.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | aoc2021.cabal 3 | day*.txt 4 | *~ -------------------------------------------------------------------------------- /hs/ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Changelog for aoc2021 2 | 3 | ## Unreleased changes 4 | -------------------------------------------------------------------------------- /hs/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Daniel Lin (c) 2021 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Daniel Lin nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /hs/README.md: -------------------------------------------------------------------------------- 1 | # [Advent of Code 2021](https://adventofcode.com/2021) 2 | ### my answers in [Haskell](https://www.haskell.org/) ![Haskell CI](https://github.com/ephemient/aoc2021/workflows/Haskell%20CI/badge.svg) 3 | 4 | This project builds with [The Haskell Tool Stack](https://haskellstack.org/). 5 | 6 | Setup: 7 | 8 | ```sh 9 | curl -sSL https://get.haskellstack.org/ | sh -s - 10 | stack setup 11 | ``` 12 | 13 | Run the [Hspec](https://hspec.github.io/) test suite: 14 | 15 | ```sh 16 | stack test aoc2021:test:aoc2021-test 17 | ``` 18 | 19 | Run [criterion](http://www.serpentine.com/criterion/) benchmarks ([results online](https://ephemient.github.io/aoc2021/aoc2021-bench.html)): 20 | 21 | ```sh 22 | stack bench aoc2021:bench:aoc2021-bench 23 | ``` 24 | 25 | Print solutions for the inputs provided in local data files: 26 | 27 | ```sh 28 | stack build aoc2021:exe:aoc2021-exe --exec aoc2021-exe 29 | ``` 30 | 31 | Generate [Haddock](https://www.haskell.org/haddock/) API documentation: 32 | 33 | ```sh 34 | stack haddock aoc2021:lib 35 | ``` 36 | 37 | Run [hlint](https://github.com/ndmitchell/hlint) source code suggestions: 38 | 39 | ```sh 40 | stack build hlint --exec 'hlint src test bench' 41 | ``` 42 | -------------------------------------------------------------------------------- /hs/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /hs/app/cbits/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "HsFFI.h" 5 | #include "Rts.h" 6 | 7 | extern StgClosure ZCMain_main_closure; 8 | 9 | static void envRtsMsgFunction(const char *s, va_list ap) { 10 | const char *trace = getenv("TRACE"); 11 | if (trace == NULL || trace[0] != '0') { 12 | rtsDebugMsgFn(s, ap); 13 | } 14 | } 15 | 16 | int main(int argc, char *argv[]) { 17 | RtsConfig rtsConfig = defaultRtsConfig; 18 | rtsConfig.rts_opts = "-N -qg"; 19 | rtsConfig.rts_hs_main = true; 20 | debugMsgFn = envRtsMsgFunction; 21 | return hs_main(argc, argv, &ZCMain_main_closure, rtsConfig); 22 | } 23 | -------------------------------------------------------------------------------- /hs/bench/cbits/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "HsFFI.h" 5 | #include "Rts.h" 6 | 7 | extern StgClosure ZCMain_main_closure; 8 | 9 | static void envRtsMsgFunction(const char *s, va_list ap) { 10 | const char *trace = getenv("TRACE"); 11 | if (trace == NULL || trace[0] != '0') { 12 | rtsDebugMsgFn(s, ap); 13 | } 14 | } 15 | 16 | int main(int argc, char *argv[]) { 17 | RtsConfig rtsConfig = defaultRtsConfig; 18 | rtsConfig.rts_opts = "-N -qg"; 19 | rtsConfig.rts_hs_main = true; 20 | debugMsgFn = envRtsMsgFunction; 21 | return hs_main(argc, argv, &ZCMain_main_closure, rtsConfig); 22 | } 23 | -------------------------------------------------------------------------------- /hs/package.yaml: -------------------------------------------------------------------------------- 1 | name: aoc2021 2 | version: 0.1.0.0 3 | github: "ephemient/aoc2021" 4 | license: BSD3 5 | author: "Daniel Lin" 6 | maintainer: "ephemient@gmail.com" 7 | copyright: "2021 Daniel Lin" 8 | 9 | extra-source-files: 10 | - README.md 11 | - ChangeLog.md 12 | 13 | # Metadata used when publishing your package 14 | # synopsis: Short description of your package 15 | # category: Web 16 | 17 | # To avoid duplicated efforts in documentation and dealing with the 18 | # complications of embedding Haddock markup inside cabal files, it is 19 | # common to point users to the README.md file. 20 | description: Please see the README on GitHub at 21 | 22 | dependencies: 23 | - base >= 4.7 && < 5 24 | 25 | library: 26 | source-dirs: src 27 | dependencies: 28 | - array 29 | - containers 30 | - fgl 31 | - heap 32 | - megaparsec 33 | - mtl 34 | - split 35 | - template-haskell 36 | - text 37 | - vector 38 | other-modules: 39 | - Day6Meta 40 | 41 | executables: 42 | aoc2021-exe: 43 | main: Main.hs 44 | other-modules: 45 | - Paths_aoc2021 46 | source-dirs: app 47 | c-sources: app/cbits/main.c 48 | ghc-options: 49 | - -no-hs-main 50 | - -threaded 51 | dependencies: 52 | - aoc2021 53 | - megaparsec 54 | - text 55 | 56 | data-files: 57 | - day*.txt 58 | 59 | tests: 60 | aoc2021-test: 61 | main: Main.hs 62 | source-dirs: test 63 | ghc-options: 64 | - -threaded 65 | - -rtsopts 66 | - '"-with-rtsopts=-N -qg"' 67 | dependencies: 68 | - QuickCheck 69 | - aoc2021 70 | - hspec 71 | - text 72 | 73 | benchmarks: 74 | aoc2021-bench: 75 | main: Main.hs 76 | source-dirs: bench 77 | c-sources: bench/cbits/main.c 78 | ghc-options: 79 | - -no-hs-main 80 | - -threaded 81 | dependencies: 82 | - aoc2021 83 | - criterion 84 | - text 85 | -------------------------------------------------------------------------------- /hs/src/Common.hs: -------------------------------------------------------------------------------- 1 | module Common (crt, egcd, readEntire) where 2 | 3 | import Data.Text (Text) 4 | import qualified Data.Text as T (null) 5 | import Data.Text.Read (Reader) 6 | 7 | -- |Chinese remainder theorem. 8 | -- 9 | -- prop> crt (r1, q1) (r2, q2) == (r3, q3) ==> 10 | -- r3 `mod` q1 == r1 && q3 `mod` q1 == 0 && 11 | -- r3 `mod` q2 == r2 && q3 `mod` q2 == 0 12 | crt :: (Integral a) => (a, a) -> (a, a) -> (a, a) 13 | crt (r1, q1) (r2, q2) = (r3 `mod` q3, q3) where 14 | q3 = lcm q1 q2 15 | -- r3 * q2 == r1 * q2 (mod q3) 16 | -- r3 * q3 == r2 * q1 (mod q3) 17 | -- r3 * (q1 + q2) = r1 * q2 + r2 * q1 (mod q3) 18 | (t, _, g) = egcd (q1 + q2) q3 19 | -- t * (q1 + q2) == g (mod q3) 20 | -- r3 = (r1 * q2 + r2 * q1) * t / g (mod q3) 21 | (r3, 0) = ((r1 * q2 + r2 * q1) * t) `divMod` g 22 | 23 | -- |Extended GCD. 24 | -- 25 | -- prop> gcd a b == (s, t, g) ==> a * s + b * t == g 26 | egcd :: (Integral a) => a -> a -> (a, a, a) 27 | egcd a 0 = (1, 0, a) 28 | egcd a b = (t, s - q * t, g) where 29 | (q, r) = a `quotRem` b 30 | (s, t, g) = egcd b r 31 | 32 | readEntire :: Reader a -> Text -> Either String a 33 | readEntire reader input = do 34 | (a, t) <- reader input 35 | if T.null t then Right a else Left "incomplete read" 36 | -------------------------------------------------------------------------------- /hs/src/Day1.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day1 3 | Description: 4 | -} 5 | {-# LANGUAGE NondecreasingIndentation, ParallelListComp, TypeApplications #-} 6 | module Day1 (day1a, day1b) where 7 | 8 | import Common (readEntire) 9 | import Data.Text (Text) 10 | import qualified Data.Text as T (lines) 11 | import qualified Data.Text.Read as T (decimal) 12 | 13 | day1a :: Text -> Either String Int 14 | day1a input = do 15 | nums <- mapM (readEntire @Int T.decimal) (T.lines input) 16 | pure $ length $ filter id $ zipWith (<) nums $ drop 1 nums 17 | 18 | day1b :: Text -> Either String Int 19 | day1b input = do 20 | nums <- mapM (readEntire @Int T.decimal) (T.lines input) 21 | let sums = [x + y + z | x <- nums | y <- drop 1 nums | z <- drop 2 nums] 22 | pure $ length $ filter id $ zipWith (<) sums $ drop 1 sums 23 | -------------------------------------------------------------------------------- /hs/src/Day10.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day10 3 | Description: 4 | -} 5 | module Day10 (day10a, day10b) where 6 | 7 | import Control.Monad ((<=<), foldM) 8 | import Data.Either (lefts) 9 | import Data.List (sort) 10 | import Data.Maybe (mapMaybe) 11 | import Data.Text (Text) 12 | import qualified Data.Text as T (lines, unpack) 13 | 14 | day10a :: Text -> Int 15 | day10a input = sum $ lefts $ points . T.unpack <$> T.lines input where 16 | points ('(':xs) = points xs >>= score ')' 17 | points ('[':xs) = points xs >>= score ']' 18 | points ('{':xs) = points xs >>= score '}' 19 | points ('<':xs) = points xs >>= score '>' 20 | points xs = Right xs 21 | score c (x:xs) | c == x = points xs 22 | score _ (')':_) = Left 3 23 | score _ (']':_) = Left 57 24 | score _ ('}':_) = Left 1197 25 | score _ ('>':_) = Left 25137 26 | score _ xs = points xs 27 | 28 | day10b :: Text -> Maybe Int 29 | day10b input = median $ mapMaybe (foldM score 0 <=< points "") $ T.unpack <$> T.lines input where 30 | points cs ('(':xs) = points (')':cs) xs 31 | points cs ('[':xs) = points (']':cs) xs 32 | points cs ('{':xs) = points ('}':cs) xs 33 | points cs ('<':xs) = points ('>':cs) xs 34 | points (c:cs) (x:xs) | c == x = points cs xs 35 | points cs [] = Just cs 36 | points _ _ = Nothing 37 | score x ')' = Just $ 5 * x + 1 38 | score x ']' = Just $ 5 * x + 2 39 | score x '}' = Just $ 5 * x + 3 40 | score x '>' = Just $ 5 * x + 4 41 | score _ _ = Nothing 42 | median [] = Nothing 43 | median xs = Just $ sort xs !! (length xs `div` 2) 44 | -------------------------------------------------------------------------------- /hs/src/Day11.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day11 3 | Description: 4 | -} 5 | module Day11 (day11) where 6 | 7 | import Control.Monad (guard) 8 | import Data.Char (digitToInt, isDigit) 9 | import Data.Ix (inRange) 10 | import Data.List (unfoldr) 11 | import Data.List.NonEmpty (NonEmpty((:|)), nonEmpty) 12 | import Data.Text (Text) 13 | import qualified Data.Text as T (all, length, lines, unpack) 14 | import Data.Vector.Unboxed (Unbox, Vector) 15 | import qualified Data.Vector.Unboxed as V (accum, all, findIndex, length, map, fromList) 16 | 17 | step :: (Num a, Ord a, Unbox a) => Int -> Vector a -> Maybe (Int, Vector a) 18 | step width v 19 | | V.all (== 0) v = Nothing 20 | | otherwise = step' 0 $ V.map (+ 1) v 21 | where 22 | step' n v' 23 | | Just i <- V.findIndex (> 9) v' = step' (n + 1) $ V.accum f v' 24 | [ (x' + y' * width, x == x' && y == y') 25 | | let (y, x) = i `divMod` width 26 | , x' <- filter (inRange (0, width - 1)) [x - 1..x + 1] 27 | , y' <- filter (inRange (0, V.length v' `div` width - 1)) [y - 1..y + 1] 28 | ] 29 | | otherwise = Just (n, V.map (max 0) v') 30 | f _ True = -1 31 | f a _ = if a < 0 then a else a + 1 32 | 33 | day11 :: Text -> Maybe (Int, Int) 34 | day11 input = do 35 | input'@(input0 :| inputs) <- nonEmpty $ T.lines input 36 | guard $ all (T.all isDigit) input' 37 | let width = T.length input0 38 | guard $ all ((== width) . T.length) inputs 39 | let flashes = unfoldr (step width) $ V.fromList $ digitToInt <$> concatMap T.unpack input' 40 | pure (sum $ take 100 flashes, length flashes) 41 | -------------------------------------------------------------------------------- /hs/src/Day12.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day12 3 | Description: 4 | -} 5 | {-# LANGUAGE FlexibleContexts, MultiWayIf, OverloadedStrings, TypeApplications #-} 6 | module Day12 (day12a, day12b) where 7 | 8 | import Control.Monad (forM, guard, when) 9 | import Control.Monad.Writer (execWriter, tell) 10 | import Data.Bits (finiteBitSize, setBit, testBit) 11 | import Data.Char (isUpper) 12 | import Data.Containers.ListUtils (nubOrd) 13 | import Data.Graph.Inductive (Graph, Gr, lsuc, mkGraph, nodes) 14 | import qualified Data.Map as Map ((!), (!?), fromList, toList) 15 | import Data.Monoid (Sum(..)) 16 | import Data.Text (Text) 17 | import qualified Data.Text as T (all, breakOn, lines, stripPrefix) 18 | import Data.Tuple (swap) 19 | 20 | parse :: (Graph gr) => Text -> Maybe (Int, Int, gr Text Bool) 21 | parse input = do 22 | conn <- forM (T.lines input) $ \line -> 23 | let (a, b) = T.breakOn "-" line in (,) a <$> T.stripPrefix "-" b 24 | let names = Map.fromList $ flip zip [0..] $ nubOrd $ concat [[a, b] | (a, b) <- conn] 25 | start <- names Map.!? "start" 26 | end <- names Map.!? "end" 27 | pure 28 | ( start 29 | , end 30 | , mkGraph (swap <$> Map.toList names) $ do 31 | (a, b) <- conn 32 | let a' = names Map.! a 33 | b' = names Map.! b 34 | [(a', b', T.all isUpper b), (b', a', T.all isUpper a)] 35 | ) 36 | 37 | walk :: (Monad m, Traversable t) => (a -> m (t a)) -> a -> m () 38 | walk f = walk' where walk' a = f a >>= mapM_ walk' 39 | 40 | day12 :: Bool -> Text -> Maybe Int 41 | day12 bonus input = do 42 | (start, end, g) <- parse @Gr input 43 | when (any (>= bonusBit) $ nodes g) $ error "input too large" 44 | let step (state, i) 45 | | i == end = mempty <$ tell (Sum 1) 46 | | otherwise = pure $ do 47 | (j, big) <- lsuc g i 48 | guard $ j /= start 49 | if 50 | | big -> pure (state, j) 51 | | not $ testBit state j -> pure (setBit state j, j) 52 | | not $ testBit state bonusBit -> pure (setBit state bonusBit, j) 53 | | otherwise -> mempty 54 | pure $ getSum $ execWriter $ walk step (if bonus then 0 else setBit 0 bonusBit :: Int, start) 55 | where bonusBit = finiteBitSize (0 :: Int) - 1 56 | 57 | day12a :: Text -> Maybe Int 58 | day12a = day12 False 59 | 60 | day12b :: Text -> Maybe Int 61 | day12b = day12 True 62 | -------------------------------------------------------------------------------- /hs/src/Day13.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day13 3 | Description: 4 | -} 5 | {-# LANGUAGE FlexibleContexts, OverloadedStrings, TupleSections, TypeApplications, TypeFamilies #-} 6 | module Day13 (day13a, day13b) where 7 | 8 | import Control.Arrow ((&&&), (***)) 9 | import Data.Array.Unboxed (UArray, accumArray, elems) 10 | import Data.List.Split (chunksOf) 11 | import Data.Semigroup (Max(..), Min(..)) 12 | import qualified Data.Set as Set (fromList, size) 13 | import Data.String (IsString) 14 | import Data.Text (Text) 15 | import Data.Void (Void) 16 | import Text.Megaparsec (MonadParsec, ParseErrorBundle, Token, Tokens, (<|>), chunk, eof, parse, sepEndBy, sepEndBy1, single) 17 | import Text.Megaparsec.Char (newline) 18 | import Text.Megaparsec.Char.Lexer (decimal) 19 | 20 | parsePair :: (Num a, MonadParsec e s m, Token s ~ Char) => m (a, a) 21 | parsePair = flip (,) <$> decimal <* single ',' <*> decimal 22 | 23 | parseFold :: (Num a, Ord a, MonadParsec e s m, Token s ~ Char, IsString (Tokens s)) => m ((a, a) -> (a, a)) 24 | parseFold = chunk "fold along " *> (foldX <$ single 'x' <|> foldY <$ single 'y') <*> (single '=' *> decimal) 25 | 26 | foldX, foldY :: (Num a, Ord a) => a -> (a, a) -> (a, a) 27 | foldX x' (y, x) = (y, x' - abs (x - x')) 28 | foldY y' (y, x) = (y' - abs (y - y'), x) 29 | 30 | day13a :: Text -> Either (ParseErrorBundle Text Void) Int 31 | day13a input = do 32 | (points, fold) <- parse parser "" input 33 | pure $ Set.size $ Set.fromList $ fold <$> points 34 | where parser = (,) <$> parsePair @Int `sepEndBy` newline <* newline <*> parseFold 35 | 36 | day13b :: Text -> Either (ParseErrorBundle Text Void) [String] 37 | day13b input = do 38 | (points, folds) <- parse parser "" input 39 | let points' = foldr (flip (.)) id folds <$> points 40 | ((Min y0, Min x0), (Max y1, Max x1)) = mconcat $ ((Min *** Min) &&& (Max *** Max)) <$> points' 41 | bitmap = accumArray @UArray (const id) '\x2591' ((y0, x0), (y1, x1)) $ (, '\x2593') <$> points' 42 | pure $ chunksOf (x1 - x0 + 1) $ elems bitmap 43 | where parser = (,) <$> parsePair @Int `sepEndBy1` newline <* newline <*> parseFold `sepEndBy` newline <* eof 44 | -------------------------------------------------------------------------------- /hs/src/Day14.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day14 3 | Description: 4 | -} 5 | {-# LANGUAGE OverloadedStrings, TupleSections #-} 6 | module Day14 (day14a, day14b) where 7 | 8 | import Control.Arrow ((&&&)) 9 | import Control.Monad (guard) 10 | import qualified Data.Map as Map ((!), elems, fromList, fromListWith, size, toList) 11 | import Data.Semigroup (Max(..), Min(..)) 12 | import qualified Data.Set as Set (fromList, size) 13 | import Data.Text (Text) 14 | import qualified Data.Text as T (head, last, length, lines, null, stripPrefix, tail, uncons, unpack, zip) 15 | 16 | day14 :: Text -> Maybe [Int] 17 | day14 input = do 18 | initial : e : rest <- pure $ T.lines input 19 | guard $ T.null e && T.length initial >= 2 20 | rules <- Map.fromList <$> mapM parseRule rest 21 | let nChars = Set.size $ Set.fromList $ T.unpack initial ++ do 22 | ((x, y), z) <- Map.toList rules 23 | x : y : concatMap (\(a, b) -> [a, b]) z 24 | guard $ Map.size rules == nChars * nChars 25 | let state0 = Map.fromListWith (+) $ (, 1) <$> T.zip initial (T.tail initial) 26 | step state = Map.fromListWith (+) 27 | [ (dst, n) 28 | | (src, n) <- Map.toList state 29 | , dst <- rules Map.! src 30 | ] 31 | extract state = (hi - lo) `div` 2 where 32 | (Min lo, Max hi) = mconcat $ map (Min &&& Max) $ Map.elems $ Map.fromListWith (+) $ 33 | (T.head initial, 1) : (T.last initial, 1) : 34 | [(c, n) | ((a, b), n) <- Map.toList state, c <- [a, b]] 35 | pure $ extract <$> iterate step state0 36 | where 37 | parseRule line = do 38 | (x, line') <- T.uncons line 39 | (y, line'') <- T.uncons line' 40 | (z, line''') <- T.uncons =<< T.stripPrefix " -> " line'' 41 | guard $ T.null line''' 42 | pure ((x, y), [(x, z), (z, y)]) 43 | 44 | day14a :: Text -> Maybe Int 45 | day14a = fmap (!! 10) . day14 46 | 47 | day14b :: Text -> Maybe Int 48 | day14b = fmap (!! 40) . day14 49 | -------------------------------------------------------------------------------- /hs/src/Day17.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day17 3 | Description: 4 | -} 5 | {-# LANGUAGE FlexibleContexts, OverloadedStrings, TypeFamilies #-} 6 | module Day17 (day17) where 7 | 8 | import Control.Monad (guard) 9 | import Data.Ix (inRange) 10 | import qualified Data.IntMap as IntMap ((!?), empty, insertWith) 11 | import qualified Data.IntSet as IntSet (findMax, null, singleton, size, unions) 12 | import Data.List (foldl', scanl') 13 | import Data.List.NonEmpty (NonEmpty((:|))) 14 | import Data.Maybe (catMaybes) 15 | import Data.Semigroup (Max(..), Sum(..), sconcat) 16 | import Data.String (IsString) 17 | import Data.Text (Text) 18 | import Data.Void (Void) 19 | import Text.Megaparsec (MonadParsec, ParseErrorBundle, Token, Tokens, chunk, parse) 20 | import Text.Megaparsec.Char.Lexer (decimal) 21 | 22 | parser :: (MonadParsec e s m, IsString (Tokens s), Token s ~ Char, Num a, Ord a) => m ((a, a), (a, a)) 23 | parser = do 24 | x0 <- chunk "target area: x=" *> decimal 25 | x1 <- chunk ".." *> decimal 26 | y0 <- chunk ", y=-" *> (negate <$> decimal) 27 | y1 <- chunk "..-" *> (negate <$> decimal) 28 | pure ((x0, y0), (x1, y1)) 29 | 30 | day17 :: Text -> Either (ParseErrorBundle Text Void) (Int, Int) 31 | day17 input = do 32 | ((x0, y0), (x1, y1)) <- parse parser "" input 33 | let (maxT, dyHits) = foldl' f (0, IntMap.empty) [y0 .. -y0] 34 | f k dy = foldl' (g dy) k $ zip [0..] $ takeWhile (>= y0) $ scanl' (+) 0 [dy, dy - 1..] 35 | g dy k@(maxT', m) (t, y) 36 | | inRange (y0, y1) y = (max maxT' t, IntMap.insertWith (<>) t (IntSet.singleton dy) m) 37 | | otherwise = k 38 | (Max maxDy, Sum count) = sconcat $ (Max 0, Sum 0) :| do 39 | dx <- [ceiling (sqrt (2 * fromIntegral x0 + 0.25 :: Double) - 0.5)..x1] 40 | let dys = IntSet.unions $ catMaybes 41 | [ dyHits IntMap.!? t 42 | | (t, x) <- zip [0..maxT] $ takeWhile (<= x1) $ scanl' (+) 0 $ [dx, dx - 1..1] ++ repeat 0 43 | , inRange (x0, x1) x 44 | ] 45 | guard $ not $ IntSet.null dys 46 | pure (Max $ IntSet.findMax dys, Sum $ IntSet.size dys) 47 | pure (maxDy * (maxDy + 1) `div` 2, count) 48 | -------------------------------------------------------------------------------- /hs/src/Day2.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day2 3 | Description: 4 | -} 5 | {-# LANGUAGE OverloadedStrings, ViewPatterns #-} 6 | module Day2 (day2a, day2b) where 7 | 8 | import Common (readEntire) 9 | import Control.Arrow (first) 10 | import Data.List (foldl') 11 | import Data.Text (Text) 12 | import qualified Data.Text as T (lines, stripPrefix) 13 | import qualified Data.Text.Read as T (decimal) 14 | import Data.Text.Read (Reader) 15 | 16 | data Move a = Horizontal a | Vertical a 17 | 18 | move :: (Integral a) => Reader (Move a) 19 | move (T.stripPrefix "forward " -> Just f) = first Horizontal <$> T.decimal f 20 | move (T.stripPrefix "down " -> Just d) = first Vertical <$> T.decimal d 21 | move (T.stripPrefix "up " -> Just u) = first (Vertical . negate) <$> T.decimal u 22 | move _ = Left "no parse" 23 | 24 | day2a :: Text -> Either String Int 25 | day2a input = do 26 | moves <- mapM (readEntire move) $ T.lines input 27 | let x = sum [d | Horizontal d <- moves] 28 | y = sum [d | Vertical d <- moves] 29 | pure $ x * y 30 | 31 | day2b :: Text -> Either String Int 32 | day2b input = do 33 | moves <- mapM (readEntire move) $ T.lines input 34 | let (x, y, _) = foldl' step (0, 0, 0) moves 35 | step (x0, y0, z) (Horizontal d) = (x0 + d, y0 + d * z, z) 36 | step (x0, y0, z) (Vertical d) = (x0, y0, z + d) 37 | pure $ x * y 38 | -------------------------------------------------------------------------------- /hs/src/Day20.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day20 3 | Description: 4 | -} 5 | {-# LANGUAGE OverloadedStrings, ViewPatterns #-} 6 | module Day20 (day20a, day20b) where 7 | 8 | import Control.Monad ((>=>)) 9 | import Data.Bits ((.&.)) 10 | import Data.Char (ord) 11 | import Data.List (tails) 12 | import Data.Text (Text) 13 | import qualified Data.Text as T (compareLength, concat, cons, count, foldl', index, length, lines, pack, null, replicate, singleton, snoc, tails, take) 14 | 15 | day20 :: Text -> Maybe [Maybe Int] 16 | day20 (T.lines -> alg:blank:image0) 17 | | T.length alg == 512 && T.null blank 18 | = Just $ count <$> iterate enhance (image0, '.') 19 | where 20 | get s = T.index alg $ T.foldl' (\x y -> 2 * x + ord y .&. 1) 0 s 21 | enhance (image, fill) = (image', get $ T.replicate 9 $ T.singleton fill) where 22 | line4 = T.replicate (T.length $ head image) $ T.singleton fill 23 | win3 = map (T.take 3) . takeWhile ((> LT) . flip T.compareLength 3) . T.tails . 24 | T.cons fill . T.cons fill . flip T.snoc fill . flip T.snoc fill 25 | image' = if null image then image else 26 | [ T.pack [get $ T.concat [a, b, c] | (a, b, c) <- zip3 (win3 line1) (win3 line2) (win3 line3)] 27 | | line1:line2:line3:_ <- tails $ line4 : line4 : image ++ [line4, line4] 28 | ] 29 | count (image, '.') = Just $ sum $ T.count "#" <$> image 30 | count _ = Nothing 31 | day20 _ = Nothing 32 | 33 | day20a :: Text -> Maybe Int 34 | day20a = day20 >=> (!! 2) 35 | 36 | day20b :: Text -> Maybe Int 37 | day20b = day20 >=> (!! 50) 38 | -------------------------------------------------------------------------------- /hs/src/Day21.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day21 3 | Description: 4 | -} 5 | {-# LANGUAGE FlexibleContexts, OverloadedStrings, TypeFamilies #-} 6 | module Day21 (day21a, day21b) where 7 | 8 | import Control.Monad (guard, join, replicateM) 9 | import Data.Array ((!), listArray, range) 10 | import qualified Data.IntMap as IntMap (assocs, fromListWith) 11 | import Data.String (IsString) 12 | import Data.Text (Text) 13 | import Data.Void (Void) 14 | import Text.Megaparsec (MonadParsec, ParseErrorBundle, Token, Tokens, between, chunk, parse) 15 | import Text.Megaparsec.Char (newline) 16 | import Text.Megaparsec.Char.Lexer (decimal) 17 | 18 | parser :: (MonadParsec e s m, IsString (Tokens s), Token s ~ Char) => m (Int, Int) 19 | parser = (,) <$> 20 | between (chunk "Player 1 starting position: ") newline decimal <*> 21 | between (chunk "Player 2 starting position: ") newline decimal 22 | 23 | day21a :: Text -> Either (ParseErrorBundle Text Void) Int 24 | day21a input = do 25 | (p1, p2) <- parse parser "" input 26 | pure $ head $ do 27 | ((_, _, s1, s2), n) <- join (zip . scanl f (p1, p2, 0, 0)) [0, 3..] 28 | n * s1 <$ guard (s2 >= 1000) 29 | where 30 | f (p1, p2, s1, s2) n = (p2, k, s2, s1 + k) where 31 | k = (p1 + n `mod` 100 + (n + 1) `mod` 100 + (n + 2) `mod` 100 + 2) `mod` 10 + 1 32 | 33 | day21b :: Text -> Either (ParseErrorBundle Text Void) Int 34 | day21b input = do 35 | (p1, p2) <- parse parser "" input 36 | pure $ uncurry max $ scores ! (p1, p2, 0 :: Int, 0 :: Int) 37 | where 38 | scores = listArray ((1, 1, 0, 0), (10, 10, 20, 20)) 39 | [ foldr add2 (0, 0) 40 | [ if s1 + k >= 21 then (n, 0) else (y * n, x * n) 41 | | (d, n) <- IntMap.assocs $ IntMap.fromListWith (+) 42 | [(d, 1) | d <- sum <$> replicateM 3 [1..3]] 43 | , let k = (p1 + d - 1) `mod` 10 + 1 44 | (x, y) = scores ! (p2, k, s2, s1 + k) 45 | ] 46 | | (p1, p2, s1, s2) <- range ((1, 1, 0, 0), (10, 10, 20, 20)) 47 | ] 48 | add2 (a, b) (c, d) = (a + c, b + d) 49 | -------------------------------------------------------------------------------- /hs/src/Day25.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day25 3 | Description: 4 | -} 5 | {-# LANGUAGE FlexibleContexts, TypeApplications #-} 6 | module Day25 (day25) where 7 | 8 | import Control.Arrow (first, second) 9 | import Control.Monad (guard) 10 | import Data.Array.Unboxed (UArray, (!), (//), accumArray, assocs) 11 | import Data.Maybe (isJust) 12 | import Data.Text (Text) 13 | import qualified Data.Text as T (length, lines, unpack) 14 | 15 | day25 :: Text -> Maybe Int 16 | day25 input = do 17 | let inputs = T.lines input 18 | height = length inputs 19 | width <- maximum $ Nothing : map (Just . T.length) inputs 20 | let state0 = accumArray @UArray (const id) '.' ((0, 0), (height - 1, width - 1)) 21 | [((y, x), c) | (y, line) <- zip [0..] inputs, (x, c) <- zip [0..] $ T.unpack line] 22 | step state = state'' <$ guard (a || b) where 23 | (a, state') = step' (second $ \x -> succ x `mod` width) '>' state 24 | (b, state'') = step' (first $ \y -> succ y `mod` height) 'v' state' 25 | step' f d state = (not $ null acc, state // acc) where 26 | acc = do 27 | (i, c) <- assocs state 28 | guard $ c == d && state ! f i == '.' 29 | [(i, '.'), (f i, d)] 30 | pure $ length $ takeWhile isJust $ iterate (>>= step) $ Just state0 31 | -------------------------------------------------------------------------------- /hs/src/Day3.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day3 3 | Description: 4 | -} 5 | module Day3 (day3a, day3b) where 6 | 7 | import Data.Bits (xor) 8 | import Data.Char (chr, ord) 9 | import Data.List (group, maximumBy, minimumBy, sort, transpose) 10 | import Data.Ord (comparing) 11 | import Data.Text (Text) 12 | import qualified Data.Text as T (lines, unpack) 13 | import Data.Void (Void) 14 | import Text.Megaparsec (ParseErrorBundle, eof, parse) 15 | import Text.Megaparsec.Char.Lexer (binary) 16 | 17 | common, uncommon :: (Ord a) => [a] -> a 18 | common xs = head $ maximumBy (comparing length) $ group $ sort xs 19 | uncommon xs = head $ minimumBy (comparing length) $ group $ sort xs 20 | 21 | day3a :: Text -> Either (ParseErrorBundle String Void) Int 22 | day3a input = (*) <$> parse (binary <* eof) "" gamma <*> parse (binary <* eof) "" epsilon where 23 | nums = T.unpack <$> T.lines input 24 | gamma = common <$> transpose nums 25 | epsilon = chr . xor 1 . ord <$> gamma 26 | 27 | day3b :: Text -> Either (ParseErrorBundle String Void) Int 28 | day3b input = (*) <$> parse (binary <* eof) "" o2 <*> parse (binary <* eof) "" co2 where 29 | nums = T.unpack <$> T.lines input 30 | scrub _ xs | null xs || any null xs = [] 31 | scrub f xs = x : scrub f [ys | y:ys <- xs, y == x] where x = f $ head <$> xs 32 | o2 = scrub common nums 33 | co2 = scrub uncommon nums 34 | -------------------------------------------------------------------------------- /hs/src/Day4.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day4 3 | Description: 4 | -} 5 | {-# LANGUAGE TypeFamilies #-} 6 | module Day4 (day4) where 7 | 8 | import Control.Arrow ((&&&)) 9 | import qualified Data.IntMap as IntMap ((!?), fromListWith) 10 | import Data.List (transpose) 11 | import Data.List.NonEmpty (nonEmpty) 12 | import Data.Maybe (mapMaybe) 13 | import Data.Semigroup (Max(Max), Min(Min), sconcat) 14 | import Data.Text (Text) 15 | import Data.Void (Void) 16 | import Text.Megaparsec (MonadParsec, ParseErrorBundle, Token, count, eof, parse, sepBy, sepBy1, sepEndBy, skipSome) 17 | import Text.Megaparsec.Char (char, hspace, hspace1, newline) 18 | import Text.Megaparsec.Char.Lexer (decimal) 19 | 20 | parser :: (MonadParsec e s m, Token s ~ Char) => m ([Int], [[[Int]]]) 21 | parser = do 22 | draws <- decimal `sepBy` char ',' <* skipSome newline 23 | boards <- board `sepEndBy` newline 24 | (draws, boards) <$ eof 25 | where 26 | board = do 27 | first <- hspace *> decimal `sepBy1` hspace1 <* newline 28 | let width = length first 29 | rest <- line width `sepEndBy` newline 30 | pure $ first:rest 31 | line n = (:) <$> (hspace *> decimal) <*> count (n - 1) (hspace1 *> decimal) 32 | 33 | day4 :: Text -> Either (ParseErrorBundle Text Void) (Maybe (Int, Int)) 34 | day4 input = do 35 | (draws, boards) <- parse parser "" input 36 | let drawTurns = IntMap.fromListWith const $ zip draws [0 :: Int ..] 37 | scoreBoard board = do 38 | let turns = fmap (drawTurns IntMap.!?) <$> board 39 | rows = mapMaybe (fmap maximum . sequence) turns 40 | cols = mapMaybe (fmap maximum . sequence) $ transpose turns 41 | turn <- minimum <$> nonEmpty (rows ++ cols) 42 | let remaining = sum 43 | [ value 44 | | row <- zip board turns 45 | , (value, maybeTurn) <- uncurry zip row 46 | , maybe True (> turn) maybeTurn 47 | ] 48 | pure (turn, draws !! turn * remaining) 49 | pure $ do 50 | scores <- nonEmpty $ mapMaybe scoreBoard boards 51 | let (Min (_, minScore), Max (_, maxScore)) = sconcat $ (Min &&& Max) <$> scores 52 | pure (minScore, maxScore) 53 | -------------------------------------------------------------------------------- /hs/src/Day6.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day6 3 | Description: 4 | -} 5 | {-# LANGUAGE OverloadedStrings, QuasiQuotes #-} 6 | module Day6 (day6a, day6b) where 7 | 8 | import Common (readEntire) 9 | import Data.Array.Unboxed (UArray, (!)) 10 | import Data.Text (Text) 11 | import qualified Data.Text as T (lines, splitOn) 12 | import qualified Data.Text.Read as T (decimal) 13 | import Day6Meta (mkLUT) 14 | 15 | day6 :: UArray Int Int -> Text -> Either String Int 16 | day6 lut input = do 17 | nums <- mapM (readEntire T.decimal) $ T.splitOn "," =<< T.lines input 18 | pure $ sum [lut ! num | num <- nums] 19 | 20 | day6a :: Text -> Either String Int 21 | day6a = day6 [mkLUT| 80 |] 22 | 23 | day6b :: Text -> Either String Int 24 | day6b = day6 [mkLUT| 256 |] 25 | -------------------------------------------------------------------------------- /hs/src/Day6Meta.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day6 3 | Description: Generates lookup tables for "Day6" 4 | -} 5 | {-# LANGUAGE TemplateHaskell, TypeApplications #-} 6 | module Day6Meta (mkLUT) where 7 | 8 | import Data.Array (Array) 9 | import Data.Array.IArray (IArray, (!), accumArray, bounds, elems, listArray, range) 10 | import Data.Semigroup (stimes) 11 | import Language.Haskell.TH.Syntax (lift) 12 | import Language.Haskell.TH.Quote (QuasiQuoter(..)) 13 | 14 | newtype Matrix a e = Matrix (a (Int, Int) e) 15 | 16 | instance (IArray a e, Num e) => Semigroup (Matrix a e) where 17 | Matrix x <> Matrix y = Matrix $ listArray ((i0, j0), (i1, j1)) 18 | [ sum $ zipWith (*) [x ! (i, k0) | k0 <- range (k00, k01)] [y ! (k1, j) | k1 <- range (k10, k11)] 19 | | (i, j) <- range ((i0, j0), (i1, j1)) 20 | ] where 21 | ((i0, k00), (i1, k01)) = bounds x 22 | ((k10, j0), (k11, j1)) = bounds y 23 | 24 | flatten :: (IArray a e, Num e) => Matrix a e -> a Int e 25 | flatten (Matrix arr) = listArray (j0, j1) 26 | [sum [arr ! (i, j) | i <- range (i0, i1)] | j <- range (j0, j1)] where 27 | ((i0, j0), (i1, j1)) = bounds arr 28 | 29 | step :: Matrix Array Integer 30 | step = Matrix $ accumArray (const id) 0 ((0, 0), (8, 8)) $ 31 | ((6, 0), 1) : ((8, 0), 1) : [((i, i + 1), 1) | i <- [0..7]] 32 | 33 | mkLUT :: QuasiQuoter 34 | mkLUT = QuasiQuoter 35 | { quoteExp = \times -> 36 | let lut = flatten $ stimes (read @Int times) step 37 | in [| listArray $(lift $ bounds lut) $(lift $ elems lut) |] 38 | , quotePat = error "unsupported" 39 | , quoteType = error "unsupported" 40 | , quoteDec = error "unsupported" 41 | } 42 | -------------------------------------------------------------------------------- /hs/src/Day7.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day7 3 | Description: 4 | -} 5 | {-# LANGUAGE OverloadedStrings #-} 6 | module Day7 (day7a, day7b) where 7 | 8 | import Common (readEntire) 9 | import Data.List (sort) 10 | import Data.Ratio ((%)) 11 | import Data.Text (Text) 12 | import qualified Data.Text as T (lines, splitOn) 13 | import qualified Data.Text.Read as T (decimal) 14 | 15 | day7a :: Text -> Either String Int 16 | day7a input = do 17 | nums <- mapM (readEntire T.decimal) $ T.splitOn "," =<< T.lines input 18 | let median = sort nums !! (length nums `div` 2) 19 | pure $ sum [abs $ num - median | num <- nums] 20 | 21 | day7b :: Text -> Either String Int 22 | day7b input = do 23 | nums <- mapM (readEntire T.decimal) $ T.splitOn "," =<< T.lines input 24 | let mean = sum nums % length nums 25 | weight mid = sum [dx * (dx + 1) `div` 2 | num <- nums, let dx = abs $ num - mid] 26 | pure $ min (weight $ floor mean) (weight $ ceiling mean) 27 | -------------------------------------------------------------------------------- /hs/src/Day8.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day8 3 | Description: 4 | -} 5 | {-# LANGUAGE OverloadedStrings #-} 6 | module Day8 (day8a, day8b) where 7 | 8 | import Control.Arrow (first) 9 | import Control.Monad (guard) 10 | import Data.Bits ((.&.), (.|.), complement, popCount, setBit) 11 | import Data.Char (ord) 12 | import qualified Data.IntMap as IntMap ((!?), fromListWith) 13 | import Data.List (elemIndex, foldl', partition) 14 | import Data.Text (Text) 15 | import qualified Data.Text as T (breakOnEnd, foldl', length, lines, stripSuffix, words) 16 | 17 | day8a :: Text -> Int 18 | day8a input = length $ do 19 | word <- T.lines input >>= T.words . snd . T.breakOnEnd " | " 20 | guard $ T.length word `elem` [2, 4, 3, 7] 21 | 22 | day8b :: Text -> Maybe Int 23 | day8b input = sum <$> mapM handle (T.lines input) 24 | 25 | handle :: Text -> Maybe Int 26 | handle line 27 | | (Just lhs, rhs) <- first (T.stripSuffix " | ") $ T.breakOnEnd " | " line = do 28 | let signals = bits <$> T.words lhs 29 | outputs = bits <$> T.words rhs 30 | counts = IntMap.fromListWith (<>) [(popCount s, [s]) | s <- signals] 31 | [one] <- counts IntMap.!? 2 32 | [seven] <- counts IntMap.!? 3 33 | [four] <- counts IntMap.!? 4 34 | ([two], threeFive) <- partition ((== 2) . popCountWithout (four .|. seven)) <$> counts IntMap.!? 5 35 | ([three], [five]) <- pure $ partition ((== 1) . popCountWithout two) threeFive 36 | ([six], zeroNine) <- partition ((/= 0) . (one .&.) . complement) <$> counts IntMap.!? 6 37 | ([zero], [nine]) <- pure $ partition ((== 2) . popCountWithout three) zeroNine 38 | [eight] <- counts IntMap.!? 7 39 | let digits = [zero, one, two, three, four, five, six, seven, eight, nine] 40 | foldl' (\x y -> 10 * x + y) 0 <$> mapM (`elemIndex` digits) outputs 41 | | otherwise = Nothing 42 | where 43 | bits = T.foldl' (\acc c -> acc `setBit` (ord c .&. 31)) (0 :: Int) 44 | popCountWithout antimask = popCount . (.&. complement antimask) 45 | -------------------------------------------------------------------------------- /hs/src/Day9.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module: Day9 3 | Description: 4 | -} 5 | {-# LANGUAGE TypeApplications #-} 6 | module Day9 (day9a, day9b) where 7 | 8 | import Data.Char (digitToInt, isDigit) 9 | import Data.Graph.Inductive (Gr, buildGr, components) 10 | import Data.List (mapAccumL, mapAccumR, sortOn, zipWith5) 11 | import Data.Maybe (catMaybes) 12 | import Data.Ord (Down(Down)) 13 | import Data.Text (Text) 14 | import qualified Data.Text as T (lines, unpack) 15 | 16 | day9a :: Text -> Int 17 | day9a input = sum risks where 18 | heights = T.unpack <$> T.lines input 19 | risks = concat $ zipWith3 basins heights 20 | (repeat maxBound : heights) (drop 1 heights ++ [repeat maxBound]) 21 | basins row above below = catMaybes $ zipWith5 basin row above below 22 | (maxBound : row) (drop 1 row ++ repeat maxBound) 23 | basin x above below left right 24 | | isDigit x, x < above, x < below, x < left, x < right = Just $ digitToInt x + 1 25 | | otherwise = Nothing 26 | 27 | day9b :: Text -> Int 28 | day9b input = product $ take 3 $ sortOn Down $ map length $ components basins where 29 | basins = buildGr @Gr $ concat $ snd $ mapAccumR mkRow (0, []) $ T.unpack <$> T.lines input 30 | mkRow (n, prev) line = (next, contexts) where 31 | next@(_, ns) = mapAccumL f n line 32 | f n' c | isDigit c, digitToInt c < 9 = (n' + 1, Just n') 33 | f n' _ = (n', Nothing) 34 | contexts = catMaybes $ zipWith3 g ns (prev ++ repeat Nothing) (drop 1 ns ++ repeat Nothing) 35 | g (Just n') above' right = 36 | Just ([((), m) | m <- catMaybes [above', right]], n', (), []) 37 | g _ _ _ = Nothing 38 | -------------------------------------------------------------------------------- /hs/stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # https://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # 15 | # The location of a snapshot can be provided as a file or url. Stack assumes 16 | # a snapshot provided as a file might change, whereas a url resource does not. 17 | # 18 | # resolver: ./custom-snapshot.yaml 19 | # resolver: https://example.com/snapshots/2018-01-01.yaml 20 | resolver: 21 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/20.yaml 22 | 23 | # User packages to be built. 24 | # Various formats can be used as shown in the example below. 25 | # 26 | # packages: 27 | # - some-directory 28 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 29 | # subdirs: 30 | # - auto-update 31 | # - wai 32 | packages: 33 | - . 34 | # Dependency packages to be pulled from upstream that are not in the resolver. 35 | # These entries can reference officially published versions as well as 36 | # forks / in-progress versions pinned to a git hash. For example: 37 | # 38 | # extra-deps: 39 | # - acme-missiles-0.3 40 | # - git: https://github.com/commercialhaskell/stack.git 41 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 42 | # 43 | # extra-deps: [] 44 | 45 | # Override default flag values for local packages and extra-deps 46 | # flags: {} 47 | 48 | # Extra package databases containing global packages 49 | # extra-package-dbs: [] 50 | 51 | # Control whether we use the GHC we find on the path 52 | # system-ghc: true 53 | # 54 | # Require a specific version of stack, using version ranges 55 | # require-stack-version: -any # Default 56 | # require-stack-version: ">=2.7" 57 | # 58 | # Override the architecture used by stack, especially useful on Windows 59 | # arch: i386 60 | # arch: x86_64 61 | # 62 | # Extra directories used by stack for building 63 | # extra-include-dirs: [/path/to/dir] 64 | # extra-lib-dirs: [/path/to/dir] 65 | # 66 | # Allow a newer minor version of GHC than the snapshot specifies 67 | # compiler-check: newer-minor 68 | 69 | ghc-options: 70 | "$locals": -Wall 71 | "$everything": -O2 72 | -------------------------------------------------------------------------------- /hs/stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: [] 7 | snapshots: 8 | - completed: 9 | size: 586106 10 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/20.yaml 11 | sha256: 8699812d2b2c1f83d6ad1261de9cf628ed36a1cfc14f19d67188e005e7a3a39d 12 | original: 13 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/20.yaml 14 | -------------------------------------------------------------------------------- /hs/test/CommonSpec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TypeApplications #-} 2 | module CommonSpec (spec) where 3 | 4 | import Common (crt, egcd) 5 | import Test.Hspec (Spec, describe) 6 | import Test.Hspec.QuickCheck (prop) 7 | import Test.QuickCheck (Large(Large), (===), arbitrarySizedIntegral, conjoin, counterexample, cover, property, suchThat) 8 | 9 | spec :: Spec 10 | spec = do 11 | describe "crt" $ do 12 | prop "is correct" $ do 13 | q1 <- arbitrarySizedIntegral `suchThat` (/= 0) 14 | q2 <- arbitrarySizedIntegral `suchThat` (/= 0) 15 | let q = gcd q1 q2 16 | r1 <- arbitrarySizedIntegral `suchThat` ((== 0) . (`mod` q)) 17 | r2 <- arbitrarySizedIntegral `suchThat` ((== 0) . (`mod` q)) 18 | let (r3, q3) = crt @Int (r1, q1) (r2, q2) 19 | pure . cover 90 (abs q1 /= 1 && abs q1 /= 1) "non-trivial" . 20 | counterexample ("(r1,q1) = " ++ show (r1, q1)) . 21 | counterexample ("(r2,q2) = " ++ show (r2, q2)) . 22 | counterexample ("(r3,q3) = " ++ show (r3, q3)) $ conjoin 23 | [ counterexample "r1 == r3 (mod q1)" $ r1 `mod` q1 === r3 `mod` q1 24 | , counterexample "q3 == 0 (mod q1)" $ q3 `mod` q1 === 0 25 | , counterexample "r2 == r3 (mod q2)" $ r2 `mod` q2 === r3 `mod` q2 26 | , counterexample "q3 == 0 (mod q2)" $ q3 `mod` q2 === 0 27 | , counterexample "|r3| < |q3|" $ property $ abs r3 < abs q3 28 | , counterexample "|gcd(r1 - r2, q1, q2)| == |q1 * q2 / q3|" $ 29 | abs (gcd (r1 - r2) $ gcd q1 q2) === abs (q1 * q2 `div` q3) 30 | ] 31 | describe "egcd" $ do 32 | prop "is correct" $ \(Large a) (Large b) -> 33 | cover 90 (abs a > 1 && abs b > 1) "non-trivial" $ 34 | let (s, t, g) = egcd @Int a b 35 | in a * s + b * t === g 36 | -------------------------------------------------------------------------------- /hs/test/Day10Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day10Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day10 (day10a, day10b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "[({(<(())[]>[[{[]{<()<>>" 12 | , "[(()[<>])]({[<{<<[]>>(" 13 | , "{([(<{}[<>[]}>{[]{[(<()>" 14 | , "(((({<>}<{<{<>}{[]{[]{}" 15 | , "[[<[([]))<([[{}[[()]]]" 16 | , "[{[{({}]{}}([{[{{{}}([]" 17 | , "{<[[]]>}<{[{[{[]{()[[[]" 18 | , "[<(<(<(<{}))><([]([]()" 19 | , "<{([([[(<>()){}]>(<<{{" 20 | , "<{([{{}}[<[[[<>{}]]]>[]]" 21 | ] 22 | 23 | spec :: Spec 24 | spec = do 25 | describe "part 1" $ do 26 | it "examples" $ do 27 | day10a example `shouldBe` 26397 28 | describe "part 2" $ do 29 | it "examples" $ do 30 | day10b example `shouldBe` Just 288957 31 | -------------------------------------------------------------------------------- /hs/test/Day11Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day11Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day11 (day11) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "5483143223" 12 | , "2745854711" 13 | , "5264556173" 14 | , "6141336146" 15 | , "6357385478" 16 | , "4167524645" 17 | , "2176841721" 18 | , "6882881134" 19 | , "4846848554" 20 | , "5283751526" 21 | ] 22 | 23 | spec :: Spec 24 | spec = do 25 | describe "both" $ do 26 | it "examples" $ do 27 | day11 example `shouldBe` Just (1656, 195) 28 | -------------------------------------------------------------------------------- /hs/test/Day12Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day12Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day12 (day12a, day12b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example1, example2, example3 :: Text 10 | example1 = T.unlines 11 | [ "start-A" 12 | , "start-b" 13 | , "A-c" 14 | , "A-b" 15 | , "b-d" 16 | , "A-end" 17 | , "b-end" 18 | ] 19 | example2 = T.unlines 20 | [ "dc-end" 21 | , "HN-start" 22 | , "start-kj" 23 | , "dc-start" 24 | , "dc-HN" 25 | , "LN-dc" 26 | , "HN-end" 27 | , "kj-sa" 28 | , "kj-HN" 29 | , "kj-dc" 30 | ] 31 | example3 = T.unlines 32 | [ "fs-end" 33 | , "he-DX" 34 | , "fs-he" 35 | , "start-DX" 36 | , "pj-DX" 37 | , "end-zg" 38 | , "zg-sl" 39 | , "zg-pj" 40 | , "pj-he" 41 | , "RW-he" 42 | , "fs-DX" 43 | , "pj-RW" 44 | , "zg-RW" 45 | , "start-pj" 46 | , "he-WI" 47 | , "zg-he" 48 | , "pj-fs" 49 | , "start-RW" 50 | ] 51 | 52 | spec :: Spec 53 | spec = do 54 | describe "part 1" $ do 55 | it "examples" $ do 56 | day12a example1 `shouldBe` Just 10 57 | day12a example2 `shouldBe` Just 19 58 | day12a example3 `shouldBe` Just 226 59 | describe "part 2" $ do 60 | it "examples" $ do 61 | day12b example1 `shouldBe` Just 36 62 | day12b example2 `shouldBe` Just 103 63 | day12b example3 `shouldBe` Just 3509 64 | -------------------------------------------------------------------------------- /hs/test/Day13Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day13Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day13 (day13a, day13b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "6,10" 12 | , "0,14" 13 | , "9,10" 14 | , "0,3" 15 | , "10,4" 16 | , "4,11" 17 | , "6,0" 18 | , "6,12" 19 | , "4,1" 20 | , "0,13" 21 | , "10,12" 22 | , "3,4" 23 | , "3,0" 24 | , "8,4" 25 | , "1,10" 26 | , "2,14" 27 | , "8,10" 28 | , "9,0" 29 | , "" 30 | , "fold along y=7" 31 | , "fold along x=5" 32 | ] 33 | 34 | spec :: Spec 35 | spec = do 36 | describe "part 1" $ do 37 | it "examples" $ do 38 | day13a example `shouldBe` Right 17 39 | describe "part 2" $ do 40 | it "examples" $ do 41 | day13b example `shouldBe` Right ["▓▓▓▓▓", "▓░░░▓", "▓░░░▓", "▓░░░▓", "▓▓▓▓▓"] 42 | -------------------------------------------------------------------------------- /hs/test/Day14Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day14Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day14 (day14a, day14b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "NNCB" 12 | , "" 13 | , "CH -> B" 14 | , "HH -> N" 15 | , "CB -> H" 16 | , "NH -> C" 17 | , "HB -> C" 18 | , "HC -> B" 19 | , "HN -> C" 20 | , "NN -> C" 21 | , "BH -> H" 22 | , "NC -> B" 23 | , "NB -> B" 24 | , "BN -> B" 25 | , "BB -> N" 26 | , "BC -> B" 27 | , "CC -> N" 28 | , "CN -> C" 29 | ] 30 | 31 | spec :: Spec 32 | spec = do 33 | describe "part 1" $ do 34 | it "examples" $ do 35 | day14a example `shouldBe` Just 1588 36 | describe "part 2" $ do 37 | it "examples" $ do 38 | day14b example `shouldBe` Just 2188189693529 39 | -------------------------------------------------------------------------------- /hs/test/Day15Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day15Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day15 (day15a, day15b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "1163751742" 12 | , "1381373672" 13 | , "2136511328" 14 | , "3694931569" 15 | , "7463417111" 16 | , "1319128137" 17 | , "1359912421" 18 | , "3125421639" 19 | , "1293138521" 20 | , "2311944581" 21 | ] 22 | 23 | spec :: Spec 24 | spec = do 25 | describe "part 1" $ do 26 | it "examples" $ do 27 | day15a example `shouldBe` Just 40 28 | describe "part 2" $ do 29 | it "examples" $ do 30 | day15b example `shouldBe` Just 315 31 | -------------------------------------------------------------------------------- /hs/test/Day16Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day16Spec (spec) where 3 | 4 | import Day16 (day16a, day16b) 5 | import Test.Hspec (Spec, describe, it, shouldBe) 6 | 7 | spec :: Spec 8 | spec = do 9 | describe "part 1" $ do 10 | it "examples" $ do 11 | day16a "8A004A801A8002F478" `shouldBe` Right 16 12 | day16a "620080001611562C8802118E34" `shouldBe` Right 12 13 | day16a "C0015000016115A2E0802F182340" `shouldBe` Right 23 14 | day16a "A0016C880162017C3686B18A3D4780" `shouldBe` Right 31 15 | describe "part 2" $ do 16 | it "examples" $ do 17 | day16b "C200B40A82" `shouldBe` Right (Just 3) 18 | day16b "04005AC33890" `shouldBe` Right (Just 54) 19 | day16b "880086C3E88112" `shouldBe` Right (Just 7) 20 | day16b "CE00C43D881120" `shouldBe` Right (Just 9) 21 | day16b "D8005AC2A8F0" `shouldBe` Right (Just 1) 22 | day16b "F600BC2D8F" `shouldBe` Right (Just 0) 23 | day16b "9C005AC2F8F0" `shouldBe` Right (Just 0) 24 | day16b "9C0141080250320F1802104A08" `shouldBe` Right (Just 1) 25 | -------------------------------------------------------------------------------- /hs/test/Day17Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day17Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day17 (day17) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines ["target area: x=20..30, y=-10..-5"] 11 | 12 | spec :: Spec 13 | spec = do 14 | describe "both" $ do 15 | it "examples" $ do 16 | day17 example `shouldBe` Right (45, 112) 17 | -------------------------------------------------------------------------------- /hs/test/Day18Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day18Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day18 (day18a, day18b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]" 12 | , "[[[5,[2,8]],4],[5,[[9,9],0]]]" 13 | , "[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]" 14 | , "[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]" 15 | , "[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]" 16 | , "[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]" 17 | , "[[[[5,4],[7,7]],8],[[8,3],8]]" 18 | , "[[9,3],[[9,9],[6,[4,9]]]]" 19 | , "[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]" 20 | , "[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]" 21 | ] 22 | 23 | spec :: Spec 24 | spec = do 25 | describe "part 1" $ do 26 | it "examples" $ do 27 | day18a example `shouldBe` Right (Just 4140) 28 | describe "part 2" $ do 29 | it "examples" $ do 30 | day18b example `shouldBe` Right (Just 3993) 31 | -------------------------------------------------------------------------------- /hs/test/Day1Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day1Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day1 (day1a, day1b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines ["199", "200", "208", "210", "200", "207", "240", "269", "260", "263"] 11 | 12 | spec :: Spec 13 | spec = do 14 | describe "part 1" $ do 15 | it "examples" $ do 16 | day1a example `shouldBe` Right 7 17 | describe "part 2" $ do 18 | it "examples" $ do 19 | day1b example `shouldBe` Right 5 20 | -------------------------------------------------------------------------------- /hs/test/Day20Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day20Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day20 (day20a, day20b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..###..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#..#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.....####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#.......##..####..#...#.#.#...##..#.#..###..#####........#..####......#..#" 12 | , "" 13 | , "#..#." 14 | , "#...." 15 | , "##..#" 16 | , "..#.." 17 | , "..###" 18 | ] 19 | 20 | spec :: Spec 21 | spec = do 22 | describe "part 1" $ do 23 | it "examples" $ do 24 | day20a example `shouldBe` Just 35 25 | describe "part 2" $ do 26 | it "examples" $ do 27 | day20b example `shouldBe` Just 3351 28 | -------------------------------------------------------------------------------- /hs/test/Day21Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day21Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day21 (day21a, day21b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "Player 1 starting position: 4" 12 | , "Player 2 starting position: 8" 13 | ] 14 | 15 | spec :: Spec 16 | spec = do 17 | describe "part 1" $ do 18 | it "examples" $ do 19 | day21a example `shouldBe` Right 739785 20 | describe "part 2" $ do 21 | it "examples" $ do 22 | day21b example `shouldBe` Right 444356092776315 23 | -------------------------------------------------------------------------------- /hs/test/Day23Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day23Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day23 (day23a, day23b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "#############" 12 | , "#...........#" 13 | , "###B#C#B#D###" 14 | , " #A#D#C#A#" 15 | , " #########" 16 | ] 17 | 18 | spec :: Spec 19 | spec = do 20 | describe "part 1" $ do 21 | it "examples" $ do 22 | day23a example `shouldBe` Right (Just 12521) 23 | describe "part 2" $ do 24 | it "examples" $ do 25 | day23b example `shouldBe` Right (Just 44169) 26 | -------------------------------------------------------------------------------- /hs/test/Day24Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day24Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day24 (day24) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "inp w" 12 | , "mul z 31" 13 | , "add z w" 14 | , "inp w" 15 | , "mul z 31" 16 | , "add z w" 17 | , "inp w" 18 | , "mul z 31" 19 | , "add z w" 20 | , "inp w" 21 | , "mul z 31" 22 | , "add z w" 23 | , "inp w" 24 | , "mul z 31" 25 | , "add z w" 26 | , "inp w" 27 | , "mul z 31" 28 | , "add z w" 29 | , "inp w" 30 | , "mul z 31" 31 | , "add z w" 32 | , "inp w" 33 | , "mul z 31" 34 | , "add z w" 35 | , "inp w" 36 | , "mul z 31" 37 | , "add z w" 38 | , "inp w" 39 | , "mul z 31" 40 | , "add z w" 41 | , "inp w" 42 | , "mul z 31" 43 | , "add z w" 44 | , "inp w" 45 | , "mul z 31" 46 | , "add z w" 47 | , "inp w" 48 | , "mul z 31" 49 | , "add z w" 50 | , "inp w" 51 | , "mul z 31" 52 | , "add z w" 53 | , "mod z 16777216" 54 | ] 55 | 56 | spec :: Spec 57 | spec = do 58 | describe "both" $ do 59 | it "examples" $ do 60 | day24 example `shouldBe` Right (Just (99999993811817, 11111128657365)) 61 | -------------------------------------------------------------------------------- /hs/test/Day25Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day25Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day25 (day25) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "v...>>.vv>" 12 | , ".vv>>.vv.." 13 | , ">>.>v>...v" 14 | , ">>v>>.>.v." 15 | , "v>v.vv.v.." 16 | , ">.>>..v..." 17 | , ".vv..>.>v." 18 | , "v.v..>>v.v" 19 | , "....v..v.>" 20 | ] 21 | 22 | spec :: Spec 23 | spec = do 24 | describe "part 1" $ do 25 | it "examples" $ do 26 | day25 example `shouldBe` Just 58 27 | -------------------------------------------------------------------------------- /hs/test/Day2Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day2Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day2 (day2a, day2b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines ["forward 5", "down 5", "forward 8", "up 3", "down 8", "forward 2"] 11 | 12 | spec :: Spec 13 | spec = do 14 | describe "part 1" $ do 15 | it "examples" $ do 16 | day2a example `shouldBe` Right 150 17 | describe "part 2" $ do 18 | it "examples" $ do 19 | day2b example `shouldBe` Right 900 20 | -------------------------------------------------------------------------------- /hs/test/Day3Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day3Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day3 (day3a, day3b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines ["00100", "11110", "10110", "10111", "10101", "01111", "00111", "11100", "10000", "11001", "00010", "01010"] 11 | 12 | spec :: Spec 13 | spec = do 14 | describe "part 1" $ do 15 | it "examples" $ do 16 | day3a example `shouldBe` Right 198 17 | describe "part 2" $ do 18 | it "examples" $ do 19 | day3b example `shouldBe` Right 230 20 | -------------------------------------------------------------------------------- /hs/test/Day4Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day4Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day4 (day4) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1" 12 | , "" 13 | , "22 13 17 11 0" 14 | , " 8 2 23 4 24" 15 | , "21 9 14 16 7" 16 | , " 6 10 3 18 5" 17 | , " 1 12 20 15 19" 18 | , "" 19 | , " 3 15 0 2 22" 20 | , " 9 18 13 17 5" 21 | , "19 8 7 25 23" 22 | , "20 11 10 24 4" 23 | , "14 21 16 12 6" 24 | , "" 25 | , "14 21 17 24 4" 26 | , "10 16 15 9 19" 27 | , "18 8 23 26 20" 28 | , "22 11 13 6 5" 29 | , " 2 0 12 3 7" 30 | ] 31 | 32 | spec :: Spec 33 | spec = do 34 | describe "both" $ do 35 | it "examples" $ do 36 | day4 example `shouldBe` Right (Just (4512, 1924)) 37 | -------------------------------------------------------------------------------- /hs/test/Day5Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day5Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day5 (day5a, day5b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "0,9 -> 5,9" 12 | , "8,0 -> 0,8" 13 | , "9,4 -> 3,4" 14 | , "2,2 -> 2,1" 15 | , "7,0 -> 7,4" 16 | , "6,4 -> 2,0" 17 | , "0,9 -> 2,9" 18 | , "3,4 -> 1,4" 19 | , "0,0 -> 8,8" 20 | , "5,5 -> 8,2" 21 | ] 22 | 23 | spec :: Spec 24 | spec = do 25 | describe "part 1" $ do 26 | it "examples" $ do 27 | day5a example `shouldBe` Right 5 28 | describe "part 2" $ do 29 | it "examples" $ do 30 | day5b example `shouldBe` Right 12 31 | -------------------------------------------------------------------------------- /hs/test/Day6Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day6Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day6 (day6a, day6b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines ["3,4,3,1,2"] 11 | 12 | spec :: Spec 13 | spec = do 14 | describe "part 1" $ do 15 | it "examples" $ do 16 | day6a example `shouldBe` Right 5934 17 | describe "part 2" $ do 18 | it "examples" $ do 19 | day6b example `shouldBe` Right 26984457539 20 | -------------------------------------------------------------------------------- /hs/test/Day7Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day7Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day7 (day7a, day7b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines ["16,1,2,0,4,2,7,1,2,14"] 11 | 12 | spec :: Spec 13 | spec = do 14 | describe "part 1" $ do 15 | it "examples" $ do 16 | day7a example `shouldBe` Right 37 17 | describe "part 2" $ do 18 | it "examples" $ do 19 | day7b example `shouldBe` Right 168 20 | -------------------------------------------------------------------------------- /hs/test/Day8Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day8Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day8 (day8a, day8b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example1, example2 :: Text 10 | example1 = "acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb fcadb cdfeb cdbaf" 11 | example2 = T.unlines 12 | [ "be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe" 13 | , "edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc" 14 | , "fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg" 15 | , "fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb" 16 | , "aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea" 17 | , "fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb" 18 | , "dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe" 19 | , "bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef" 20 | , "egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb" 21 | , "gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce" 22 | ] 23 | 24 | spec :: Spec 25 | spec = do 26 | describe "part 1" $ do 27 | it "examples" $ do 28 | day8a example2 `shouldBe` 26 29 | describe "part 2" $ do 30 | it "examples" $ do 31 | day8b example1 `shouldBe` Just 5353 32 | day8b example2 `shouldBe` Just 61229 33 | -------------------------------------------------------------------------------- /hs/test/Day9Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Day9Spec (spec) where 3 | 4 | import Data.Text (Text) 5 | import qualified Data.Text as T (unlines) 6 | import Day9 (day9a, day9b) 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | example :: Text 10 | example = T.unlines 11 | [ "2199943210" 12 | , "3987894921" 13 | , "9856789892" 14 | , "8767896789" 15 | , "9899965678" 16 | ] 17 | 18 | spec :: Spec 19 | spec = do 20 | describe "part 1" $ do 21 | it "examples" $ do 22 | day9a example `shouldBe` 15 23 | describe "part 2" $ do 24 | it "examples" $ do 25 | day9b example `shouldBe` 1134 26 | -------------------------------------------------------------------------------- /hs/test/Main.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF hspec-discover #-} 2 | -------------------------------------------------------------------------------- /kt/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | build/ 4 | local.properties 5 | *~ 6 | -------------------------------------------------------------------------------- /kt/README.md: -------------------------------------------------------------------------------- 1 | # [Advent of Code 2021](https://adventofcode.com/2021) 2 | ### my answers in [Kotlin](https://www.kotlinlang.org/) ![Kotlin CI](https://github.com/ephemient/aoc2021/workflows/Kotlin%20CI/badge.svg) 3 | 4 | This project builds with [Gradle](https://gradle.org/). 5 | 6 | Run the test suite: 7 | 8 | ```sh 9 | ./gradlew allTests 10 | ``` 11 | 12 | Run [kotlinx.benchmark](https://github.com/Kotlin/kotlinx-benchmark) ([JMH](https://openjdk.java.net/projects/code-tools/jmh/)) benchmarks: 13 | 14 | ```sh 15 | ./gradlew benchmark 16 | ``` 17 | 18 | Print solutions for the inputs provided in local data files: 19 | 20 | ```sh 21 | ./gradlew jvmRun 22 | ./gradlew jsIrRun 23 | ./gradlew run{Debug,Release}Executable{Linux{X64,Arm64},MingwX86,Macos{X64,Arm64}} 24 | ``` 25 | 26 | Generate [Dokka](https://github.com/Kotlin/dokka) API documentation: 27 | 28 | ```sh 29 | ./gradlew dokkaHtml 30 | ``` 31 | 32 | Run all checks, including [Detekt](https://detekt.github.io/) static code analysis and [ktlint](https://ktlint.github.io/) formatter: 33 | 34 | ```sh 35 | ./gradlew check 36 | ``` 37 | 38 | Check for newer versions of dependencies: 39 | 40 | ```sh 41 | ./gradlew dependencyUpdates 42 | ``` 43 | -------------------------------------------------------------------------------- /kt/detekt.yml: -------------------------------------------------------------------------------- 1 | style: 2 | MagicNumber: 3 | active: false 4 | -------------------------------------------------------------------------------- /kt/graalvm/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | alias(libs.plugins.native.image) 4 | } 5 | 6 | application { 7 | mainClass.set("com.github.ephemient.aoc2021.MainKt") 8 | } 9 | 10 | graalvmNative.binaries.named("main") { 11 | imageName.set(rootProject.name) 12 | buildArgs.add("-H:IncludeResources=day.*\\.txt") 13 | agent { 14 | enabled.set(true) 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation(rootProject) 20 | } 21 | -------------------------------------------------------------------------------- /kt/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin.mpp.enableGranularSourceSetsMetadata=true 3 | org.gradle.caching=true 4 | org.gradle.jvmargs=-Xmx1024m -XX:MaxMetaspaceSize=384m -XX:+UseParallelGC 5 | org.gradle.parallel=true 6 | -------------------------------------------------------------------------------- /kt/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | asm = "9.2" 3 | dependency-updates = "0.39.0" 4 | detekt = "1.19.0" 5 | dokka = "1.6.0" 6 | graal-sdk = "21.3.0" 7 | junit-jupiter = "5.8.2" 8 | kotlin = "1.6.10" 9 | kotlinx-benchmark = "0.4.1" 10 | native-image-plugin = "0.9.9" 11 | 12 | [plugins] 13 | dependency-updates = { id = "com.github.ben-manes.versions", version.ref = "dependency-updates" } 14 | detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } 15 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } 16 | kotlinx-benchmark = { id = "org.jetbrains.kotlinx.benchmark", version.ref = "kotlinx-benchmark" } 17 | native-image = { id = "org.graalvm.buildtools.native", version.ref = "native-image-plugin" } 18 | 19 | [libraries] 20 | asm = { module = "org.ow2.asm:asm", version.ref = "asm" } 21 | asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" } 22 | detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } 23 | graal-sdk = { module = "org.graalvm.sdk:graal-sdk", version.ref = "graal-sdk" } 24 | junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit-jupiter" } 25 | junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter" } 26 | junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit-jupiter" } 27 | kotlinx-benchmark-runtime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "kotlinx-benchmark" } 28 | -------------------------------------------------------------------------------- /kt/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ephemient/aoc2021/aa98c082c539e4a13c112d3b8bc81be2d54d2d21/kt/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /kt/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /kt/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("VERSION_CATALOGS") 2 | 3 | pluginManagement { 4 | repositories { 5 | mavenCentral() 6 | gradlePluginPortal() 7 | } 8 | } 9 | 10 | dependencyResolutionManagement { 11 | repositories { 12 | mavenCentral() 13 | } 14 | } 15 | 16 | rootProject.name = "aoc2021" 17 | include("graalvm") 18 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day10Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day10Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(10) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day10(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Long = Day10(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day11Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day11Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(11) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day11(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int = Day11(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day12Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day12Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(12) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day12(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int = Day12(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day13Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day13Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(13) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day13(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): String = Day13(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day14Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day14Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(14) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Long = Day14(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Long = Day14(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day15Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day15Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(15) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day15(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int = Day15(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day16Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day16Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(16) 15 | } 16 | 17 | @Benchmark 18 | @ExperimentalStdlibApi 19 | fun part1(): Int = Day16(lines).part1() 20 | 21 | @Benchmark 22 | @ExperimentalStdlibApi 23 | fun part2(): Long = Day16(lines).part2() 24 | } 25 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day17Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day17Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(17) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): IntPair = Day17(lines).solve() 19 | } 20 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day18Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day18Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(18) 15 | } 16 | 17 | @Benchmark 18 | @ExperimentalStdlibApi 19 | fun part1(): Int = Day18(lines).part1() 20 | 21 | @Benchmark 22 | @ExperimentalStdlibApi 23 | fun part2(): Int = Day18(lines).part2() 24 | } 25 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day19Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day19Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(19) 15 | } 16 | 17 | @Benchmark 18 | @ExperimentalStdlibApi 19 | fun solve(): IntPair? = Day19(lines).solve() 20 | } 21 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day1Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day1Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(1) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day1(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int = Day1(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day20Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day20Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(20) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int? = Day20(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int? = Day20(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day21Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day21Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(21) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day21(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Long = Day21(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day22Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day22Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(22) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Long = Day22(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Long = Day22(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day23Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day23Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(23) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day23(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int = Day23(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day24Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day24Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(24) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Long? = Day24(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Long? = Day24(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day25Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day25Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(25) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day25(lines).part1() 19 | } 20 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day2Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day2Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(2) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day2(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int = Day2(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day3Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day3Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(3) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day3(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int = Day3(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day4Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day4Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(4) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int? = Day4(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int? = Day4(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day5Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day5Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(5) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day5(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int = Day5(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day6Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day6Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(6) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Long = Day6(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Long = Day6(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day7Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day7Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(7) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day7(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int = Day7(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day8Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day8Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(8) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day8(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int = Day8(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonBench/kotlin/com/github/ephemient/aoc2021/Day9Bench.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlinx.benchmark.Benchmark 4 | import kotlinx.benchmark.Scope 5 | import kotlinx.benchmark.Setup 6 | import kotlinx.benchmark.State 7 | 8 | @State(Scope.Benchmark) 9 | class Day9Bench { 10 | private lateinit var lines: List 11 | 12 | @Setup 13 | fun prepare() { 14 | lines = getInput(9) 15 | } 16 | 17 | @Benchmark 18 | fun part1(): Int = Day9(lines).part1() 19 | 20 | @Benchmark 21 | fun part2(): Int = Day9(lines).part2() 22 | } 23 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/CacheSet.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | internal class CacheSet(capacity: Int) : AbstractMutableSet() { 4 | init { 5 | require(capacity > 0) 6 | } 7 | 8 | private val data = arrayOfNulls(capacity) 9 | override var size: Int = 0 10 | private set 11 | 12 | override fun contains(element: T): Boolean = data[element.hashCode().mod(data.size)] == element 13 | 14 | override fun add(element: T): Boolean { 15 | val index = element.hashCode().mod(data.size) 16 | val previous = data[index] 17 | data[index] = element 18 | return element != previous 19 | } 20 | 21 | override fun remove(element: T): Boolean { 22 | val index = element.hashCode().mod(data.size) 23 | val previous = data[index] 24 | return if (element == previous) { 25 | data[index] = null 26 | true 27 | } else false 28 | } 29 | 30 | override fun iterator(): MutableIterator = object : MutableIterator { 31 | private var index = 0 32 | 33 | override fun hasNext(): Boolean { 34 | while (index < data.size) { 35 | if (data[index] != null) return true 36 | index++ 37 | } 38 | return false 39 | } 40 | 41 | override fun next(): T { 42 | while (index < data.size) { 43 | @Suppress("UNCHECKED_CAST") 44 | return (data[index++] ?: continue) as T 45 | } 46 | throw NoSuchElementException() 47 | } 48 | 49 | override fun remove() { 50 | for (i in index - 1 downTo 0) { 51 | if (data[i] != null) { 52 | data[i] = null 53 | return 54 | } 55 | } 56 | throw NoSuchElementException() 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/CardinalDirection.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.contracts.ExperimentalContracts 4 | import kotlin.contracts.InvocationKind 5 | import kotlin.contracts.contract 6 | 7 | enum class CardinalDirection { 8 | NORTH, WEST, EAST, SOUTH; 9 | 10 | companion object 11 | } 12 | 13 | operator fun CardinalDirection.invoke(x: Int, y: Int): IntPair = when (this) { 14 | CardinalDirection.NORTH -> x to y - 1 15 | CardinalDirection.WEST -> x - 1 to y 16 | CardinalDirection.EAST -> x + 1 to y 17 | CardinalDirection.SOUTH -> x to y + 1 18 | } 19 | 20 | operator fun CardinalDirection.invoke(x: Long, y: Long): LongPair = when (this) { 21 | CardinalDirection.NORTH -> x to y - 1 22 | CardinalDirection.WEST -> x - 1 to y 23 | CardinalDirection.EAST -> x + 1 to y 24 | CardinalDirection.SOUTH -> x to y + 1 25 | } 26 | 27 | @OptIn(ExperimentalContracts::class) 28 | inline fun CardinalDirection.Companion.forEach(x: Int, y: Int, block: (x: Int, y: Int) -> Unit) { 29 | contract { 30 | callsInPlace(block, InvocationKind.AT_LEAST_ONCE) 31 | } 32 | 33 | block(x, y - 1) 34 | block(x - 1, y) 35 | block(x + 1, y) 36 | block(x, y + 1) 37 | } 38 | 39 | @OptIn(ExperimentalContracts::class) 40 | inline fun CardinalDirection.Companion.forEach(x: Long, y: Long, block: (x: Long, y: Long) -> Unit) { 41 | contract { 42 | callsInPlace(block, InvocationKind.AT_LEAST_ONCE) 43 | } 44 | 45 | block(x, y - 1) 46 | block(x - 1, y) 47 | block(x + 1, y) 48 | block(x, y + 1) 49 | } 50 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day1.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 1](https://adventofcode.com/2021/day/1): Sonar Sweep */ 4 | class Day1(lines: List) { 5 | private val nums = lines.map { it.toInt() } 6 | 7 | fun part1(): Int = (0 until nums.lastIndex).count { nums[it] < nums[it + 1] } 8 | 9 | fun part2(): Int { 10 | val sums = nums.windowed(3) { it.sum() } 11 | return (0 until sums.lastIndex).count { sums[it] < sums[it + 1] } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day10.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 10](https://adventofcode.com/2021/day/10): Syntax Scoring */ 4 | class Day10(private val lines: List) { 5 | fun part1(): Int = lines.sumOf { line -> 6 | val expected = mutableListOf() 7 | for (char in line) { 8 | pairs[char]?.also { expected += it } 9 | ?: expected.removeLastOrNull()?.takeIf { it == char } 10 | ?: return@sumOf part1Points.getValue(char) 11 | } 12 | 0 13 | } 14 | 15 | fun part2(): Long = lines.mapNotNullTo(mutableListOf()) { line -> 16 | val expected = mutableListOf() 17 | for (char in line) { 18 | pairs[char]?.also { expected += it } 19 | ?: expected.removeLastOrNull()?.takeIf { it == char } 20 | ?: return@mapNotNullTo null 21 | } 22 | expected.asReversed().fold(0L) { acc, char -> 5 * acc + part2Points.getValue(char) } 23 | }.apply { sort() }.let { it[it.size / 2] } 24 | 25 | companion object { 26 | private val pairs = "([{<".zip(")]}>").toMap() 27 | private val part1Points = mapOf(')' to 3, ']' to 57, '}' to 1197, '>' to 25137) 28 | private val part2Points = mapOf(')' to 1, ']' to 2, '}' to 3, '>' to 4) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day11.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 11](https://adventofcode.com/2021/day/11): Dumbo Octopus */ 4 | class Day11(lines: List) { 5 | private val flashes = buildList { 6 | val width = lines.first().length 7 | val arr = buildList { 8 | for (line in lines) { 9 | check(line.length == width) 10 | line.mapTo(this) { it.digitToInt() } 11 | } 12 | }.toIntArray() 13 | while (arr.any { it != 0 }) { 14 | arr.forEachIndexed { i, x -> arr[i] = x + 1 } 15 | var flashes = 0 16 | while (true) { 17 | val i = arr.indexOfFirst { it > 9 } 18 | if (i < 0) break 19 | flashes++ 20 | val x0 = i % width 21 | val y0 = i / width 22 | for (x1 in (x0 - 1).coerceAtLeast(0)..(x0 + 1).coerceAtMost(width - 1)) { 23 | for (y1 in (y0 - 1).coerceAtLeast(0)..(y0 + 1).coerceAtMost(lines.lastIndex)) { 24 | val j = x1 + y1 * width 25 | if (i == j) { 26 | arr[j] = -1 27 | } else { 28 | val n = arr[j] 29 | if (n >= 0) arr[j] = n + 1 30 | } 31 | } 32 | } 33 | } 34 | add(flashes) 35 | arr.forEachIndexed { i, x -> if (x < 0) arr[i] = 0 } 36 | } 37 | } 38 | 39 | fun part1(): Int = flashes.take(100).sum() 40 | 41 | fun part2(): Int = flashes.size 42 | } 43 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day12.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 12](https://adventofcode.com/2021/day/12): Passage Pathing */ 4 | class Day12(lines: List) { 5 | private val start: Int 6 | private val end: Int 7 | private val edges: Map>> 8 | 9 | init { 10 | val names = mutableMapOf() 11 | val edges = mutableMapOf>>() 12 | for (line in lines) { 13 | val (lhs, rhs) = line.split('-', limit = 2) 14 | val src = names.getOrPut(lhs) { names.size } 15 | val dst = names.getOrPut(rhs) { names.size } 16 | edges.getOrPut(src) { mutableListOf() }.add(dst to rhs.all { it.isUpperCase() }) 17 | edges.getOrPut(dst) { mutableListOf() }.add(src to lhs.all { it.isUpperCase() }) 18 | } 19 | check(names.size < Int.SIZE_BITS) 20 | start = names.getValue("start") 21 | end = names.getValue("end") 22 | this.edges = edges 23 | } 24 | 25 | private fun solve(bonus: Boolean): Int { 26 | var count = 0 27 | walk((if (bonus) 0 else Int.MIN_VALUE) to start) { (state, element) -> 28 | edges[element]?.mapNotNull { (next, big) -> 29 | when { 30 | next == start -> null 31 | next == end -> null.also { count++ } 32 | big -> state to next 33 | state.and(1 shl next) == 0 -> state.or(1 shl next) to next 34 | state >= 0 -> state or Int.MIN_VALUE to next 35 | else -> null 36 | } 37 | }.orEmpty() 38 | } 39 | return count 40 | } 41 | 42 | fun part1(): Int = solve(false) 43 | 44 | fun part2(): Int = solve(true) 45 | 46 | companion object { 47 | private fun walk(start: T, step: (T) -> Iterable) { 48 | val stack = mutableListOf(step(start).iterator()) 49 | while (stack.isNotEmpty()) { 50 | val iterator = stack.last() 51 | if (!iterator.hasNext()) { 52 | stack.removeLast() 53 | continue 54 | } 55 | stack.add(step(iterator.next()).iterator()) 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day13.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.math.abs 4 | 5 | /** [Day 13](https://adventofcode.com/2021/day/13): Transparent Origami */ 6 | class Day13(lines: List) { 7 | private val points: Set 8 | private val folds: List 9 | init { 10 | val iterator = lines.iterator() 11 | points = buildSet { 12 | for (line in iterator) { 13 | if (line.isEmpty()) break 14 | val (x, y) = line.split(',') 15 | add(x.toInt() to y.toInt()) 16 | } 17 | } 18 | folds = buildList { 19 | for (line in iterator) { 20 | when { 21 | line.startsWith("fold along x=") -> add(Fold.X(line.substring(13).toInt())) 22 | line.startsWith("fold along y=") -> add(Fold.Y(line.substring(13).toInt())) 23 | else -> throw IllegalStateException("bad input: $line") 24 | } 25 | } 26 | } 27 | } 28 | 29 | fun part1(): Int = points.mapTo(mutableSetOf(), folds.first()::invoke).size 30 | 31 | fun part2(): String { 32 | val points = points.mapTo(mutableSetOf()) { folds.fold(it) { point, fold -> fold(point) } } 33 | val x0 = points.minOf { it.first } 34 | val y0 = points.minOf { it.second } 35 | val x1 = points.maxOf { it.first } 36 | val y1 = points.maxOf { it.second } 37 | return (y0..y1).joinToString("\n") { y -> 38 | (x0..x1).joinToString("") { x -> 39 | if (x to y in points) "\u2593" else "\u2591" 40 | } 41 | } 42 | } 43 | 44 | private sealed interface Fold { 45 | operator fun invoke(point: IntPair): IntPair 46 | 47 | class X(private val x: Int) : Fold { 48 | override fun invoke(point: IntPair): IntPair = point.copy(first = x - abs(point.first - x)) 49 | } 50 | 51 | class Y(private val y: Int) : Fold { 52 | override fun invoke(point: IntPair): IntPair = point.copy(second = y - abs(point.second - y)) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day14.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 14](https://adventofcode.com/2021/day/14): Extended Polymerization */ 4 | class Day14(lines: List) { 5 | private val n: Int 6 | private val x: LongArray 7 | private val m: IntArray 8 | init { 9 | check(lines.size > 1 && lines[0].length > 1 && lines[1].isEmpty()) 10 | val chars = StringBuilder() 11 | for (char in lines[0]) if (char !in chars) chars.append(char) 12 | for (line in lines.drop(2)) { 13 | check(line.length == 7 && line.regionMatches(2, " -> ", 0, 4)) 14 | if (line[0] !in chars) chars.append(line[0]) 15 | if (line[1] !in chars) chars.append(line[1]) 16 | if (line.last() !in chars) chars.append(line.last()) 17 | } 18 | n = chars.length 19 | x = LongArray(n * n) 20 | for (i in 0 until lines[0].lastIndex) x[chars.indexOf(lines[0][i]) * n + chars.indexOf(lines[0][i + 1])]++ 21 | m = IntArray(n * n) { -1 } 22 | for (line in lines.drop(2)) m[chars.indexOf(line[0]) * n + chars.indexOf(line[1])] = chars.indexOf(line.last()) 23 | } 24 | 25 | fun part1(): Long = score(10) 26 | 27 | fun part2(): Long = score(40) 28 | 29 | private fun score(steps: Int): Long { 30 | var x = x 31 | repeat(steps) { 32 | val y = LongArray(n * n) 33 | x.forEachIndexed { i, j -> 34 | val z = m[i] 35 | check(z >= 0) 36 | y[i / n * n + z] += j 37 | y[z * n + i % n] += j 38 | } 39 | x = y 40 | } 41 | val counts = LongArray(n) 42 | counts[0] = 1 43 | x.forEachIndexed { i, j -> counts[i % n] += j } 44 | return counts.maxOf { it } - counts.minOf { it } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day15.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 15](https://adventofcode.com/2021/day/15): Chiton */ 4 | class Day15(lines: List) { 5 | private val width: Int = lines.first().length 6 | private val height: Int = lines.size 7 | private val risks: IntArray 8 | init { 9 | check(lines.all { it.length == width && it.all { it.isDigit() } }) 10 | risks = IntArray(width * lines.size) { lines[it / width][it % width].digitToInt() } 11 | } 12 | 13 | fun part1(): Int = solve(width, height, risks) 14 | 15 | fun part2(): Int = solve( 16 | 5 * width, 17 | 5 * height, 18 | IntArray(25 * risks.size) { 19 | val x = it % (5 * width) 20 | val y = it / (5 * width) 21 | (risks[y % (risks.size / width) * width + x % width] - 1 + x / width + y / height) % 9 + 1 22 | } 23 | ) 24 | 25 | private fun solve(width: Int, height: Int, risks: IntArray): Int { 26 | val bests = IntArray(risks.size) { Int.MAX_VALUE } 27 | bests[0] = 0 28 | val queue = PriorityQueue(compareBy(IntPair::first)) 29 | queue.add(0 to 0) 30 | while (true) { 31 | val i = queue.remove().second 32 | val c0 = bests[i] 33 | if (i == risks.lastIndex) return c0 34 | val x0 = i % width 35 | val y0 = i / width 36 | for ((x1, y1) in arrayOf(x0 - 1 to y0, x0 to y0 - 1, x0 to y0 + 1, x0 + 1 to y0)) { 37 | if (x1 !in 0 until width || y1 !in 0 until height) continue 38 | val j = y1 * width + x1 39 | val c1 = c0 + risks[j] 40 | if (c1 < bests[j]) { 41 | bests[j] = c1 42 | queue.add(c1 to j) 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day17.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.math.ceil 4 | import kotlin.math.roundToInt 5 | import kotlin.math.sqrt 6 | 7 | /** [Day 17](https://adventofcode.com/2021/day/17): Trick Shot */ 8 | class Day17(lines: List) { 9 | private val xRange: IntRange 10 | private val yRange: IntRange 11 | init { 12 | val (xRange, yRange) = PATTERN.matchEntire(lines.single())!!.destructured.let { (x0, x1, y0, y1) -> 13 | (x0.toInt()..x1.toInt()) to (y0.toInt()..y1.toInt()) 14 | } 15 | this.xRange = xRange 16 | this.yRange = yRange 17 | } 18 | 19 | @Suppress("NestedBlockDepth") 20 | fun solve(): IntPair { 21 | var maxT = 0 22 | val dyHits = mutableMapOf>() 23 | for (dy in yRange.first..-yRange.first) { 24 | for ((t, y) in generateSequence(dy) { it - 1 }.runningFold(0, Int::plus).withIndex()) { 25 | if (y < yRange.first) break 26 | if (y in yRange) { 27 | maxT = maxOf(maxT, t) 28 | dyHits.getOrPut(t) { mutableListOf() }.add(dy) 29 | } 30 | } 31 | } 32 | var maxDy = 0 33 | var count = 0 34 | for (dx in ceil(sqrt(2.0 * xRange.first + 0.25) - 0.5).roundToInt()..xRange.last) { 35 | val dys = mutableSetOf() 36 | for ((t, x) in generateSequence(dx) { (it - 1).coerceAtLeast(0) }.runningFold(0, Int::plus).withIndex()) { 37 | if (t > maxT || x > xRange.last) break 38 | if (x in xRange) dyHits[t]?.let { dys.addAll(it) } 39 | } 40 | maxDy = dys.fold(maxDy, ::maxOf) 41 | count += dys.size 42 | } 43 | return maxDy * (maxDy + 1) / 2 to count 44 | } 45 | 46 | companion object { 47 | private val PATTERN = """target area: x=(\d+)\.\.(\d+), y=(-\d+)\.\.(-\d+)""".toRegex() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day2.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 2](https://adventofcode.com/2021/day/2): Dive! */ 4 | class Day2(private val lines: List) { 5 | fun part1(): Int { 6 | var x = 0 7 | var y = 0 8 | for (line in lines) { 9 | val (prefix, suffix) = line.split(" ", limit = 2) 10 | when (prefix) { 11 | "forward" -> x += suffix.toInt() 12 | "down" -> y += suffix.toInt() 13 | "up" -> y -= suffix.toInt() 14 | } 15 | } 16 | return x * y 17 | } 18 | 19 | fun part2(): Int { 20 | var x = 0 21 | var y = 0 22 | var depth = 0 23 | for (line in lines) { 24 | val (prefix, suffix) = line.split(" ", limit = 2) 25 | when (prefix) { 26 | "forward" -> suffix.toInt().let { 27 | x += it 28 | y += it * depth 29 | } 30 | "down" -> depth += suffix.toInt() 31 | "up" -> depth -= suffix.toInt() 32 | } 33 | } 34 | return x * y 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day20.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 20](https://adventofcode.com/2021/day/20): Trench Map*/ 4 | class Day20(lines: List) { 5 | private val alg = lines.first() 6 | private fun lookup(vararg key: Char): Char = alg[key.fold(0) { acc, c -> 2 * acc + c.code.and(1) }] 7 | private val images = generateSequence(lines.drop(2) to '.') { (image, fill) -> 8 | List(image.size + 2) { y -> 9 | val line1 = image.getOrNull(y - 2) 10 | val line2 = image.getOrNull(y - 1) 11 | val line3 = image.getOrNull(y) 12 | CharArray(image[(y - 1).coerceIn(image.indices)].length + 2) { x -> 13 | lookup( 14 | line1?.getOrNull(x - 2) ?: fill, line1?.getOrNull(x - 1) ?: fill, line1?.getOrNull(x) ?: fill, 15 | line2?.getOrNull(x - 2) ?: fill, line2?.getOrNull(x - 1) ?: fill, line2?.getOrNull(x) ?: fill, 16 | line3?.getOrNull(x - 2) ?: fill, line3?.getOrNull(x - 1) ?: fill, line3?.getOrNull(x) ?: fill, 17 | ) 18 | }.concatToString() 19 | } to lookup(fill, fill, fill, fill, fill, fill, fill, fill, fill) 20 | }.map { (image, fill) -> if (fill == '.') image else null } 21 | 22 | fun part1(): Int? = images.drop(2).firstOrNull()?.sumOf { it.count { it == '#' } } 23 | 24 | fun part2(): Int? = images.drop(50).firstOrNull()?.sumOf { it.count { it == '#' } } 25 | } 26 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day21.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.native.concurrent.ThreadLocal 4 | 5 | /** [Day 21](https://adventofcode.com/2021/day/21): Dirac Dice */ 6 | class Day21(lines: List) { 7 | private val player1Start = lines[0].removePrefix("Player 1 starting position: ").toInt() 8 | private val player2Start = lines[1].removePrefix("Player 2 starting position: ").toInt() 9 | 10 | fun part1(): Int { 11 | var player1 = player1Start 12 | var player2 = player2Start 13 | var score1 = 0 14 | var score2 = 0 15 | var n = 0 16 | while (score2 < 1000) { 17 | player1 = player2.also { player2 = (player1 + n % 100 + (n + 1) % 100 + (n + 2) % 100 + 2) % 10 + 1 } 18 | score1 = score2.also { score2 = score1 + player2 } 19 | n += 3 20 | } 21 | return n * score1 22 | } 23 | 24 | fun part2(): Long = Score(player1Start, player2Start, 0, 0).let { (first, second) -> maxOf(first, second) } 25 | 26 | @ThreadLocal 27 | private object Score { 28 | private val dice = sequence { 29 | for (i in 1..3) for (j in 1..3) for (k in 1..3) yield(i + j + k) 30 | }.groupingBy { it }.eachCount() 31 | private val x = LongArray(44100) 32 | private val y = LongArray(44100) 33 | 34 | operator fun invoke(player1: Int, player2: Int, score1: Int, score2: Int): LongPair { 35 | val i = player1 + 10 * player2 + 100 * score1 + 2100 * score2 - 11 36 | if (x[i] == 0L && y[i] == 0L) { 37 | var x1 = 0L 38 | var y1 = 0L 39 | for ((d, n) in dice) { 40 | val play = (player1 + d - 1) % 10 + 1 41 | if (score1 + play < 21) { 42 | val (x2, y2) = this(player2, play, score2, score1 + play) 43 | x1 += n * y2 44 | y1 += n * x2 45 | } else { 46 | x1 += n 47 | } 48 | } 49 | x[i] = x1 50 | y[i] = y1 51 | } 52 | return x[i] to y[i] 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day24.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 24](https://adventofcode.com/2021/day/24): Arithmetic Logic Unit */ 4 | expect class Day24(lines: List) { 5 | fun part1(): Long? 6 | fun part2(): Long? 7 | } 8 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day24Impl.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | interface Day24Impl { 4 | fun part1(): Long? 5 | fun part2(): Long? 6 | 7 | interface Provider { 8 | operator fun invoke(lines: List): T? 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day25.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 25](https://adventofcode.com/2022/day/25): Sea cucumber */ 4 | class Day25(lines: List) { 5 | private val height = lines.size 6 | private val width = lines.maxOfOrNull { it.length } ?: 0 7 | private val initialState = CharArray(width * height) { lines[it / width].getOrElse(it % width) { '.' } } 8 | 9 | fun part1(): Int { 10 | var n = 1 11 | val state = initialState.copyOf() 12 | while (true) { 13 | val a = (0 until height).fold(true) { acc, y -> 14 | val indices = (0 until width).filter { x -> 15 | state[y * width + x] == '>' && state[y * width + (x + 1) % width] == '.' 16 | } 17 | for (x in indices) { 18 | state[y * width + x] = '.' 19 | state[y * width + (x + 1) % width] = '>' 20 | } 21 | acc && indices.isEmpty() 22 | } 23 | val b = (0 until width).fold(true) { acc, x -> 24 | val indices = (0 until height).filter { y -> 25 | state[y * width + x] == 'v' && state[(y + 1) % height * width + x] == '.' 26 | } 27 | for (y in indices) { 28 | state[y * width + x] = '.' 29 | state[(y + 1) % height * width + x] = 'v' 30 | } 31 | acc && indices.isEmpty() 32 | } 33 | if (a && b) return n else n++ 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day3.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 3](https://adventofcode.com/2021/day/3): Binary Diagnostic */ 4 | class Day3(private val lines: List) { 5 | fun part1(): Int { 6 | val common = CharArray(lines.minOfOrNull { it.length } ?: 0) { i -> 7 | lines.groupingBy { it[i] }.eachCount().entries.maxByOrNull { it.value }!!.key 8 | } 9 | val gamma = common.concatToString().toInt(2) 10 | common.forEachIndexed { i, c -> common[i] = (c.code xor 1).toChar() } 11 | val epsilon = common.concatToString().toInt(2) 12 | return gamma * epsilon 13 | } 14 | 15 | fun part2(): Int { 16 | val o2 = lines.toMutableList() 17 | val co2 = lines.toMutableList() 18 | for (i in 0 until (lines.minOfOrNull { it.length } ?: 0)) { 19 | val common = o2.groupingBy { it[i] }.eachCount().entries.maxWithOrNull(comparator)!!.key 20 | val uncommon = co2.groupingBy { it[i] }.eachCount().entries.minWithOrNull(comparator)!!.key 21 | o2.retainAll { it[i] == common } 22 | co2.retainAll { it[i] == uncommon } 23 | } 24 | return o2.single().toInt(2) * co2.single().toInt(2) 25 | } 26 | 27 | companion object { 28 | private val comparator: Comparator> = compareBy({ it.value }, { it.key }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day4.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 4](https://adventofcode.com/2021/day/4): Giant Squid */ 4 | class Day4(lines: List) { 5 | private val boards = buildList { 6 | val iterator = lines.iterator() 7 | check(iterator.hasNext()) 8 | val draws = iterator.next().split(',').map { it.toInt() } 9 | val drawTurns = buildMap { 10 | draws.forEachIndexed { turn, draw -> getOrPut(draw) { turn } } 11 | }.withDefault { Int.MAX_VALUE } 12 | if (!iterator.hasNext()) return@buildList 13 | check(iterator.next().isEmpty()) 14 | while (iterator.hasNext()) { 15 | var width = -1 16 | val values = buildList { 17 | for (line in iterator) { 18 | if (line.isEmpty()) break 19 | val row = line.split(WHITESPACE).mapNotNull { if (it.isEmpty()) null else it.toInt() } 20 | if (width < 0) width = row.size else check(width == row.size) 21 | addAll(row) 22 | } 23 | } 24 | val turns = values.map { drawTurns.getValue(it) } 25 | val turn = minOf( 26 | (turns.indices step width).minOf { start -> (start until start + width).maxOf { turns[it] } }, 27 | (0 until width).minOf { start -> (start..turns.lastIndex step width).maxOf { turns[it] } }, 28 | ) 29 | if (turn < Int.MAX_VALUE) { 30 | add(IndexedValue(turn, draws[turn] * values.sumOf { if (drawTurns.getValue(it) > turn) it else 0 })) 31 | } 32 | } 33 | } 34 | 35 | fun part1(): Int? = boards.minByOrNull { it.index }?.value 36 | 37 | fun part2(): Int? = boards.maxByOrNull { it.index }?.value 38 | 39 | companion object { 40 | private val WHITESPACE = """\s""".toRegex() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day6.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 6](https://adventofcode.com/2021/day/6): Lanternfish */ 4 | class Day6(lines: List) { 5 | private val nums = lines.flatMap { line -> line.splitToSequence(',').map { it.toInt() } } 6 | 7 | fun part1(): Long = nums.sumOf { Day6Constants.matrix80[it] } 8 | 9 | fun part2(): Long = nums.sumOf { Day6Constants.matrix256[it] } 10 | } 11 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day7.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.math.abs 4 | import kotlin.math.roundToInt 5 | 6 | /** [Day 7](https://adventofcode.com/2021/day/7): The Treachery of Whales */ 7 | class Day7(lines: List) { 8 | private val nums = lines.flatMap { line -> line.splitToSequence(',').map { it.toInt() } } 9 | 10 | fun part1(): Int { 11 | val median = nums.sorted()[nums.size / 2] 12 | return nums.sumOf { abs(it - median) } 13 | } 14 | 15 | fun part2(): Int { 16 | val mean = nums.sum().toDouble() / nums.size 17 | val mean0 = mean.roundToInt() 18 | val mean1 = if (mean0 <= mean) mean0 + 1 else mean0 - 1 19 | return minOf(nums.sumOf { t(abs(it - mean0)) }, nums.sumOf { t(abs(it - mean1)) }) 20 | } 21 | 22 | companion object { 23 | private fun t(x: Int): Int = x * (x + 1) / 2 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day8.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 8](https://adventofcode.com/2021/day/8): Seven Segment Search */ 4 | class Day8(private val lines: List) { 5 | fun part1(): Int = lines.sumOf { 6 | it.substringAfter(" | ") 7 | .split(' ') 8 | .count { it.length == 2 || it.length == 4 || it.length == 3 || it.length == 7 } 9 | } 10 | 11 | fun part2(): Int = lines.sumOf { line -> 12 | val (lhs, rhs) = line.split(" | ", limit = 2) 13 | val signals = lhs.split(' ').map { it.toBits() }.groupBy { it.countOneBits() } 14 | val one = signals.getValue(2).single() 15 | val seven = signals.getValue(3).single() 16 | val four = signals.getValue(4).single() 17 | val fourSeven = four or seven 18 | val (twos, threeFive) = signals.getValue(5).partition { (it and fourSeven.inv()).countOneBits() == 2 } 19 | val two = twos.single() 20 | val (threes, fives) = threeFive.partition { (it and two.inv()).countOneBits() == 1 } 21 | val three = threes.single() 22 | val five = fives.single() 23 | val (sixes, zeroNine) = signals.getValue(6).partition { one and it.inv() != 0 } 24 | val six = sixes.single() 25 | val (zeros, nines) = zeroNine.partition { (it and three.inv()).countOneBits() == 2 } 26 | val nine = nines.single() 27 | val zero = zeros.single() 28 | val eight = signals.getValue(7).single() 29 | val digits = intArrayOf(zero, one, two, three, four, five, six, seven, eight, nine) 30 | rhs.split(' ').fold(0) { acc: Int, s -> 10 * acc + digits.indexOf(s.toBits()) } 31 | } 32 | 33 | companion object { 34 | private fun String.toBits(): Int = fold(0) { acc, c -> acc or 1.shl(c.code) } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/Day9.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | /** [Day 9](https://adventofcode.com/2021/day/9): Smoke Basin */ 4 | class Day9(private val lines: List) { 5 | fun part1(): Int = lines.withIndex().sumOf { (i, line) -> 6 | line.withIndex().sumOf { (j, c) -> 7 | @Suppress("ComplexCondition") 8 | if ( 9 | c.isDigit() && 10 | line.getOrNull(j - 1)?.let { c < it } != false && 11 | line.getOrNull(j + 1)?.let { c < it } != false && 12 | lines.getOrNull(i - 1)?.getOrNull(j)?.let { c < it } != false && 13 | lines.getOrNull(i + 1)?.getOrNull(j)?.let { c < it } != false 14 | ) c.digitToInt() + 1 else 0 15 | } 16 | } 17 | 18 | fun part2(): Int { 19 | val basins = mutableListOf() 20 | val visited = Array(lines.size) { i -> 21 | BooleanArray(lines[i].length) { j -> 22 | val c = lines[i][j] 23 | !c.isDigit() || c.digitToInt() >= 9 24 | } 25 | } 26 | lines.forEachIndexed { i, line -> 27 | line.forEachIndexed inner@{ j, c -> 28 | if (visited[i][j] || !c.isDigit() || c.digitToInt() >= 9) return@inner 29 | visited[i][j] = true 30 | var basin = 0 31 | val stack = mutableListOf(i to j) 32 | while (true) { 33 | val (i2, j2) = stack.removeLastOrNull() ?: break 34 | basin++ 35 | CardinalDirection.forEach(i2, j2) { i3, j3 -> 36 | if (i3 in visited.indices && j3 in visited[i3].indices && !visited[i3][j3]) { 37 | visited[i3][j3] = true 38 | stack.add(i3 to j3) 39 | } 40 | } 41 | } 42 | basins.add(basin) 43 | } 44 | } 45 | basins.sort() 46 | return basins.asReversed().take(3).fold(1) { x, y -> x * y } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/IntPair.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | data class IntPair(val first: Int, val second: Int) { 4 | override fun toString(): String = "($first, $second)" 5 | } 6 | 7 | infix fun Int.to(other: Int): IntPair = IntPair(this, other) 8 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/LongPair.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | data class LongPair(val first: Long, val second: Long) { 4 | override fun toString(): String = "($first, $second)" 5 | } 6 | 7 | infix fun Long.to(other: Long): LongPair = LongPair(this, other) 8 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/PriorityQueue.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | expect class PriorityQueue(comparator: Comparator) { 4 | fun add(element: E): Boolean 5 | 6 | @Throws(NoSuchElementException::class) 7 | fun remove(): E 8 | } 9 | -------------------------------------------------------------------------------- /kt/src/commonMain/kotlin/com/github/ephemient/aoc2021/common.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | fun Int.pow(x: Int): Int = when { 4 | x < 0 -> throw ArithmeticException("negative exponent") 5 | x == 0 -> 1 6 | x == 1 || this == 0 || this == 1 -> this 7 | this == -1 -> if (x and 1 == 0) 1 else -1 8 | else -> { 9 | var r = 1 10 | var b = this 11 | var e = x 12 | while (true) { 13 | if (e and 1 != 0) r *= b 14 | e = e ushr 1 15 | if (e == 0) break 16 | b *= b 17 | } 18 | r 19 | } 20 | } 21 | 22 | fun Long.pow(x: Int): Long = when { 23 | x < 0 -> throw ArithmeticException("negative exponent") 24 | x == 0 -> 1 25 | x == 1 || this == 0L || this == 1L -> this 26 | this == -1L -> if (x and 1 == 0) 1 else -1 27 | else -> { 28 | var r = 1L 29 | var b = this 30 | var e = x 31 | while (true) { 32 | if (e and 1 != 0) r *= b 33 | e = e ushr 1 34 | if (e == 0) break 35 | b *= b 36 | } 37 | r 38 | } 39 | } 40 | 41 | inline fun permutationIndices(size: Int, block: (IntArray, Boolean) -> Unit) { 42 | val indices = IntArray(size) { it } 43 | var parity = false 44 | while (true) { 45 | block(indices, parity) 46 | val i = (indices.size - 2 downTo 0).firstOrNull { indices[it] < indices[it + 1] } ?: break 47 | var j = i + 1 48 | for (k in i + 2 until indices.size) if (indices[k] in indices[i]..indices[j]) j = k 49 | indices[i] = indices[j].also { indices[j] = indices[i] } 50 | indices.reverse(i + 1, indices.size) 51 | if (i xor indices.size and 1 == 0) parity = !parity 52 | } 53 | } 54 | 55 | fun List.permutations(): Sequence> = sequence { 56 | permutationIndices(size) { indices, _ -> yield(indices.map(this@permutations::get)) } 57 | } 58 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day10Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day10Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(26397, Day10(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(288957, Day10(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf( 19 | "[({(<(())[]>[[{[]{<()<>>", 20 | "[(()[<>])]({[<{<<[]>>(", 21 | "{([(<{}[<>[]}>{[]{[(<()>", 22 | "(((({<>}<{<{<>}{[]{[]{}", 23 | "[[<[([]))<([[{}[[()]]]", 24 | "[{[{({}]{}}([{[{{{}}([]", 25 | "{<[[]]>}<{[{[{[]{()[[[]", 26 | "[<(<(<(<{}))><([]([]()", 27 | "<{([([[(<>()){}]>(<<{{", 28 | "<{([{{}}[<[[[<>{}]]]>[]]" 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day11Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day11Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(1656, Day11(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(195, Day11(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf( 19 | "5483143223", 20 | "2745854711", 21 | "5264556173", 22 | "6141336146", 23 | "6357385478", 24 | "4167524645", 25 | "2176841721", 26 | "6882881134", 27 | "4846848554", 28 | "5283751526", 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day12Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day12Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(10, Day12(SAMPLE_INPUT_1).part1()) 10 | assertEquals(19, Day12(SAMPLE_INPUT_2).part1()) 11 | assertEquals(226, Day12(SAMPLE_INPUT_3).part1()) 12 | } 13 | 14 | @Test 15 | fun part2() { 16 | assertEquals(36, Day12(SAMPLE_INPUT_1).part2()) 17 | assertEquals(103, Day12(SAMPLE_INPUT_2).part2()) 18 | assertEquals(3509, Day12(SAMPLE_INPUT_3).part2()) 19 | } 20 | 21 | companion object { 22 | private val SAMPLE_INPUT_1 = listOf( 23 | "start-A", 24 | "start-b", 25 | "A-c", 26 | "A-b", 27 | "b-d", 28 | "A-end", 29 | "b-end", 30 | ) 31 | private val SAMPLE_INPUT_2 = listOf( 32 | "dc-end", 33 | "HN-start", 34 | "start-kj", 35 | "dc-start", 36 | "dc-HN", 37 | "LN-dc", 38 | "HN-end", 39 | "kj-sa", 40 | "kj-HN", 41 | "kj-dc", 42 | ) 43 | private val SAMPLE_INPUT_3 = listOf( 44 | "fs-end", 45 | "he-DX", 46 | "fs-he", 47 | "start-DX", 48 | "pj-DX", 49 | "end-zg", 50 | "zg-sl", 51 | "zg-pj", 52 | "pj-he", 53 | "RW-he", 54 | "fs-DX", 55 | "pj-RW", 56 | "zg-RW", 57 | "start-pj", 58 | "he-WI", 59 | "zg-he", 60 | "pj-fs", 61 | "start-RW", 62 | ) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day13Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day13Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(17, Day13(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals("▓▓▓▓▓\n▓░░░▓\n▓░░░▓\n▓░░░▓\n▓▓▓▓▓", Day13(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf( 19 | "6,10", 20 | "0,14", 21 | "9,10", 22 | "0,3", 23 | "10,4", 24 | "4,11", 25 | "6,0", 26 | "6,12", 27 | "4,1", 28 | "0,13", 29 | "10,12", 30 | "3,4", 31 | "3,0", 32 | "8,4", 33 | "1,10", 34 | "2,14", 35 | "8,10", 36 | "9,0", 37 | "", 38 | "fold along y=7", 39 | "fold along x=5", 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day14Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day14Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(1588, Day14(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(2188189693529, Day14(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf( 19 | "NNCB", 20 | "", 21 | "CH -> B", 22 | "HH -> N", 23 | "CB -> H", 24 | "NH -> C", 25 | "HB -> C", 26 | "HC -> B", 27 | "HN -> C", 28 | "NN -> C", 29 | "BH -> H", 30 | "NC -> B", 31 | "NB -> B", 32 | "BN -> B", 33 | "BB -> N", 34 | "BC -> B", 35 | "CC -> N", 36 | "CN -> C", 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day15Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day15Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(40, Day15(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(315, Day15(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf( 19 | "1163751742", 20 | "1381373672", 21 | "2136511328", 22 | "3694931569", 23 | "7463417111", 24 | "1319128137", 25 | "1359912421", 26 | "3125421639", 27 | "1293138521", 28 | "2311944581", 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day16Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day16Test { 7 | @Test 8 | @ExperimentalStdlibApi 9 | fun part1() { 10 | assertEquals(16, Day16(listOf("8A004A801A8002F478")).part1()) 11 | assertEquals(12, Day16(listOf("620080001611562C8802118E34")).part1()) 12 | assertEquals(23, Day16(listOf("C0015000016115A2E0802F182340")).part1()) 13 | assertEquals(31, Day16(listOf("A0016C880162017C3686B18A3D4780")).part1()) 14 | } 15 | 16 | @Test 17 | @ExperimentalStdlibApi 18 | fun part2() { 19 | assertEquals(3, Day16(listOf("C200B40A82")).part2()) 20 | assertEquals(54, Day16(listOf("04005AC33890")).part2()) 21 | assertEquals(7, Day16(listOf("880086C3E88112")).part2()) 22 | assertEquals(9, Day16(listOf("CE00C43D881120")).part2()) 23 | assertEquals(1, Day16(listOf("D8005AC2A8F0")).part2()) 24 | assertEquals(0, Day16(listOf("F600BC2D8F")).part2()) 25 | assertEquals(0, Day16(listOf("9C005AC2F8F0")).part2()) 26 | assertEquals(1, Day16(listOf("9C0141080250320F1802104A08")).part2()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day17Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day17Test { 7 | @Test 8 | fun solve() { 9 | assertEquals(45 to 112, Day17(SAMPLE_INPUT).solve()) 10 | } 11 | 12 | companion object { 13 | private val SAMPLE_INPUT = listOf("target area: x=20..30, y=-10..-5") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day18Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day18Test { 7 | @Test 8 | @ExperimentalStdlibApi 9 | fun part1() { 10 | assertEquals(4140, Day18(SAMPLE_INPUT).part1()) 11 | } 12 | 13 | @Test 14 | @ExperimentalStdlibApi 15 | fun part2() { 16 | assertEquals(3993, Day18(SAMPLE_INPUT).part2()) 17 | } 18 | 19 | companion object { 20 | private val SAMPLE_INPUT = listOf( 21 | "[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]", 22 | "[[[5,[2,8]],4],[5,[[9,9],0]]]", 23 | "[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]", 24 | "[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]", 25 | "[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]", 26 | "[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]", 27 | "[[[[5,4],[7,7]],8],[[8,3],8]]", 28 | "[[9,3],[[9,9],[6,[4,9]]]]", 29 | "[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]", 30 | "[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]", 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day1Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day1Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(7, Day1(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(5, Day1(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf("199", "200", "208", "210", "200", "207", "240", "269", "260", "263") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day20Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day20Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(35, Day20(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(3351, Day20(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf( 19 | "..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..###..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#..#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.....####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#.......##..####..#...#.#.#...##..#.#..###..#####........#..####......#..#", 20 | "", 21 | "#..#.", 22 | "#....", 23 | "##..#", 24 | "..#..", 25 | "..###", 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day21Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day21Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(739785, Day21(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(444356092776315, Day21(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf( 19 | "Player 1 starting position: 4", 20 | "Player 2 starting position: 8", 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day23Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | @ExperimentalMultiplatform 7 | @JsIgnore 8 | class Day23Test { 9 | @Test 10 | fun part1() { 11 | assertEquals(12521, Day23(SAMPLE_INPUT).part1()) 12 | } 13 | 14 | @Test 15 | fun part2() { 16 | assertEquals(44169, Day23(SAMPLE_INPUT).part2()) 17 | } 18 | 19 | companion object { 20 | private val SAMPLE_INPUT = listOf( 21 | "#############", 22 | "#...........#", 23 | "###B#C#B#D###", 24 | " #A#D#C#A#", 25 | " #########", 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day24Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | @ExperimentalMultiplatform 7 | @JvmIgnore 8 | class Day24Test { 9 | @Test 10 | @JsIgnore 11 | fun part1Common() { 12 | assertEquals(99999993811817, Day24Common(SAMPLE_INPUT).part1()) 13 | } 14 | 15 | @Test 16 | fun part1Range() { 17 | assertEquals(99999993811817, Day24Range(SAMPLE_INPUT).part1()) 18 | } 19 | 20 | @Test 21 | @JsIgnore 22 | fun part2Common() { 23 | assertEquals(11111128657365, Day24Common(SAMPLE_INPUT).part2()) 24 | } 25 | 26 | @Test 27 | fun part2Range() { 28 | assertEquals(11111128657365, Day24Range(SAMPLE_INPUT).part2()) 29 | } 30 | 31 | companion object { 32 | val SAMPLE_INPUT = listOf( 33 | "inp w", 34 | "mul z 31", 35 | "add z w", 36 | "mod z 16777216", 37 | "inp w", 38 | "mul z 31", 39 | "add z w", 40 | "mod z 16777216", 41 | "inp w", 42 | "mul z 31", 43 | "add z w", 44 | "mod z 16777216", 45 | "inp w", 46 | "mul z 31", 47 | "add z w", 48 | "mod z 16777216", 49 | "inp w", 50 | "mul z 31", 51 | "add z w", 52 | "mod z 16777216", 53 | "inp w", 54 | "mul z 31", 55 | "add z w", 56 | "mod z 16777216", 57 | "inp w", 58 | "mul z 31", 59 | "add z w", 60 | "mod z 16777216", 61 | "inp w", 62 | "mul z 31", 63 | "add z w", 64 | "mod z 16777216", 65 | "inp w", 66 | "mul z 31", 67 | "add z w", 68 | "mod z 16777216", 69 | "inp w", 70 | "mul z 31", 71 | "add z w", 72 | "mod z 16777216", 73 | "inp w", 74 | "mul z 31", 75 | "add z w", 76 | "mod z 16777216", 77 | "inp w", 78 | "mul z 31", 79 | "add z w", 80 | "mod z 16777216", 81 | "inp w", 82 | "mul z 31", 83 | "add z w", 84 | "mod z 16777216", 85 | "inp w", 86 | "mul z 31", 87 | "add z w", 88 | "mod z 16777216", 89 | ) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day25Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day25Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(58, Day25(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | companion object { 13 | private val SAMPLE_INPUT = listOf( 14 | "v...>>.vv>", 15 | ".vv>>.vv..", 16 | ">>.>v>...v", 17 | ">>v>>.>.v.", 18 | "v>v.vv.v..", 19 | ">.>>..v...", 20 | ".vv..>.>v.", 21 | "v.v..>>v.v", 22 | "....v..v.>", 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day2Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day2Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(150, Day2(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(900, Day2(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf("forward 5", "down 5", "forward 8", "up 3", "down 8", "forward 2") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day3Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day3Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(198, Day3(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(230, Day3(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf( 19 | "00100", 20 | "11110", 21 | "10110", 22 | "10111", 23 | "10101", 24 | "01111", 25 | "00111", 26 | "11100", 27 | "10000", 28 | "11001", 29 | "00010", 30 | "01010" 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day4Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day4Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(4512, Day4(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(1924, Day4(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf( 19 | "7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1", 20 | "", 21 | "22 13 17 11 0", 22 | " 8 2 23 4 24", 23 | "21 9 14 16 7", 24 | " 6 10 3 18 5", 25 | " 1 12 20 15 19", 26 | "", 27 | " 3 15 0 2 22", 28 | " 9 18 13 17 5", 29 | "19 8 7 25 23", 30 | "20 11 10 24 4", 31 | "14 21 16 12 6", 32 | "", 33 | "14 21 17 24 4", 34 | "10 16 15 9 19", 35 | "18 8 23 26 20", 36 | "22 11 13 6 5", 37 | " 2 0 12 3 7", 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day5Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day5Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(5, Day5(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(12, Day5(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf( 19 | "0,9 -> 5,9", 20 | "8,0 -> 0,8", 21 | "9,4 -> 3,4", 22 | "2,2 -> 2,1", 23 | "7,0 -> 7,4", 24 | "6,4 -> 2,0", 25 | "0,9 -> 2,9", 26 | "3,4 -> 1,4", 27 | "0,0 -> 8,8", 28 | "5,5 -> 8,2", 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day6Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day6Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(5934, Day6(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(26984457539, Day6(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf("3,4,3,1,2") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day7Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day7Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(37, Day7(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(168, Day7(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf("16,1,2,0,4,2,7,1,2,14") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day8Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day8Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(26, Day8(SAMPLE_INPUT_2).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(5353, Day8(SAMPLE_INPUT_1).part2()) 15 | assertEquals(61229, Day8(SAMPLE_INPUT_2).part2()) 16 | } 17 | 18 | companion object { 19 | private val SAMPLE_INPUT_1 = listOf( 20 | "acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb fcadb cdfeb cdbaf", 21 | ) 22 | 23 | private val SAMPLE_INPUT_2 = listOf( 24 | "be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe", 25 | "edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc", 26 | "fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg", 27 | "fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb", 28 | "aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea", 29 | "fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb", 30 | "dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe", 31 | "bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef", 32 | "egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb", 33 | "gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce", 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/Day9Test.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class Day9Test { 7 | @Test 8 | fun part1() { 9 | assertEquals(15, Day9(SAMPLE_INPUT).part1()) 10 | } 11 | 12 | @Test 13 | fun part2() { 14 | assertEquals(1134, Day9(SAMPLE_INPUT).part2()) 15 | } 16 | 17 | companion object { 18 | private val SAMPLE_INPUT = listOf("2199943210", "3987894921", "9856789892", "8767896789", "9899965678") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/JsIgnore.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | @ExperimentalMultiplatform 4 | @OptionalExpectation 5 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) 6 | expect annotation class JsIgnore() 7 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/JvmIgnore.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | @ExperimentalMultiplatform 4 | @OptionalExpectation 5 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) 6 | expect annotation class JvmIgnore() 7 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/PermutationsTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | import kotlin.test.assertTrue 6 | 7 | class PermutationsTest { 8 | @Test 9 | fun permutationIndices() { 10 | assertEquals( 11 | listOf( 12 | listOf(0, 1, 2) to false, 13 | listOf(0, 2, 1) to true, 14 | listOf(1, 0, 2) to true, 15 | listOf(1, 2, 0) to false, 16 | listOf(2, 0, 1) to false, 17 | listOf(2, 1, 0) to true, 18 | ), 19 | buildList { 20 | permutationIndices(3) { indices, parity -> add(indices.toList() to parity) } 21 | } 22 | ) 23 | } 24 | 25 | @Test 26 | fun permutations() { 27 | for (str in arrayOf("", "a", "kotlin")) { 28 | val permutations = str.toList().permutations().toList() 29 | val set = str.toSet() 30 | for (permutation in permutations) { 31 | assertEquals(set, permutation.toSet(), "$str -> ${permutation.joinToString("")}") 32 | } 33 | val duplicates = permutations.groupByTo(mutableMapOf()) { it }.values.apply { retainAll { it.size != 1 } } 34 | assertTrue("$str duplicates: $duplicates") { duplicates.isEmpty() } 35 | assertEquals((1..str.length).fold(1) { acc, i -> acc * i }, permutations.size, str) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /kt/src/commonTest/kotlin/com/github/ephemient/aoc2021/PowTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | import kotlin.test.assertFails 6 | 7 | class PowTest { 8 | @Test 9 | fun testInt() { 10 | for (x in -9..9) { 11 | for (y in -2..9) { 12 | if (y < 0) assertFails("$x ^ $y") { x.pow(y) } 13 | else assertEquals((0 until y).fold(1) { acc, _ -> acc * x }, x.pow(y), "$x ^ $y") 14 | } 15 | } 16 | } 17 | 18 | @Test 19 | fun testLong() { 20 | for (x in -15..15L) { 21 | for (y in -2..15) { 22 | if (y < 0) assertFails("$x ^ $y") { x.pow(y) } 23 | else assertEquals((0 until y).fold(1L) { acc, _ -> acc * x }, x.pow(y), "$x ^ $y") 24 | } 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /kt/src/jsMain/kotlin/com/github/ephemient/aoc2021/Config.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | @JsModule("detect-mocha") 4 | @JsNonModule 5 | external fun detectMocha(): Boolean 6 | 7 | // https://youtrack.jetbrains.com/issue/KT-31888 8 | internal actual fun getRunAllIfArgsEmpty(): Boolean = !detectMocha() 9 | -------------------------------------------------------------------------------- /kt/src/jsTest/kotlin/com/github/ephemient/aoc2021/JsIgnore.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import kotlin.test.Ignore 4 | 5 | actual typealias JsIgnore = Ignore 6 | -------------------------------------------------------------------------------- /kt/src/jvmMain/kotlin/com/github/ephemient/aoc2021/Day24.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import java.util.ServiceLoader 4 | 5 | actual class Day24 actual constructor(lines: List) { 6 | private val impl = ServiceLoader.load(Day24Impl.Provider::class.java).firstNotNullOf { it(lines) } 7 | 8 | actual fun part1(): Long? = impl.part1() 9 | 10 | actual fun part2(): Long? = impl.part2() 11 | } 12 | -------------------------------------------------------------------------------- /kt/src/jvmMain/kotlin/com/github/ephemient/aoc2021/PriorityQueue.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | actual typealias PriorityQueue = java.util.PriorityQueue 4 | -------------------------------------------------------------------------------- /kt/src/jvmMain/kotlin/com/github/ephemient/aoc2021/Resources.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | private enum class Resources 4 | 5 | actual fun getInput(day: Int): List = 6 | checkNotNull(Resources::class.java.classLoader.getResourceAsStream("day$day.txt")) { "No data for day $day" } 7 | .bufferedReader() 8 | .use { it.readLines() } 9 | -------------------------------------------------------------------------------- /kt/src/jvmMain/resources/META-INF/services/com.github.ephemient.aoc2021.Day24Impl$Provider: -------------------------------------------------------------------------------- 1 | com.github.ephemient.aoc2021.Day24Range$Provider 2 | com.github.ephemient.aoc2021.Day24Jvm$Provider 3 | com.github.ephemient.aoc2021.Day24Common$Provider 4 | -------------------------------------------------------------------------------- /kt/src/jvmTest/kotlin/com/github/ephemient/aoc2021/Day24JvmTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import org.junit.jupiter.params.ParameterizedTest 4 | import org.junit.jupiter.params.provider.ValueSource 5 | import kotlin.test.assertEquals 6 | 7 | class Day24JvmTest { 8 | @ExperimentalMultiplatform 9 | @ParameterizedTest 10 | @ValueSource(classes = [Day24Common::class, Day24Jvm::class, Day24Range::class]) 11 | fun part1(implClass: Class) { 12 | val impl = implClass.getDeclaredConstructor(List::class.java).newInstance(Day24Test.SAMPLE_INPUT) 13 | assertEquals(99999993811817, impl.part1()) 14 | } 15 | 16 | @ExperimentalMultiplatform 17 | @ParameterizedTest 18 | @ValueSource(classes = [Day24Common::class, Day24Jvm::class, Day24Range::class]) 19 | fun part2(implClass: Class) { 20 | val impl = implClass.getDeclaredConstructor(List::class.java).newInstance(Day24Test.SAMPLE_INPUT) 21 | assertEquals(11111128657365, impl.part2()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /kt/src/jvmTest/kotlin/com/github/ephemient/aoc2021/JvmIgnore.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | import org.junit.jupiter.api.Disabled 4 | 5 | actual typealias JvmIgnore = Disabled 6 | -------------------------------------------------------------------------------- /kt/src/nonJsMain/kotlin/com/github/ephemient/aoc2021/Config.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | internal actual fun getRunAllIfArgsEmpty(): Boolean = true 4 | -------------------------------------------------------------------------------- /kt/src/nonJvmMain/kotlin/com/github/ephemient/aoc2021/Day24.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | actual typealias Day24 = Day24Common 4 | -------------------------------------------------------------------------------- /kt/src/nonJvmMain/kotlin/com/github/ephemient/aoc2021/PriorityQueue.kt: -------------------------------------------------------------------------------- 1 | package com.github.ephemient.aoc2021 2 | 3 | actual class PriorityQueue actual constructor(private val comparator: Comparator) { 4 | private var storage: MutableList = mutableListOf() 5 | 6 | actual fun add(element: E): Boolean { 7 | storage.add(element) 8 | var i = storage.lastIndex 9 | while (i > 0) { 10 | val j = (i - 1) / 2 11 | val a = storage[j] 12 | val b = storage[i] 13 | if (comparator.compare(a, b) <= 0) break 14 | storage[i] = a 15 | storage[j] = b 16 | i = j 17 | } 18 | return true 19 | } 20 | 21 | @Throws(NoSuchElementException::class) 22 | actual fun remove(): E { 23 | val first = storage.first() 24 | val last = storage.removeLast() 25 | if (storage.isNotEmpty()) { 26 | storage[0] = last 27 | var i = 0 28 | while (2 * i + 1 < storage.lastIndex) { 29 | val j = if (2 * i + 2 < storage.lastIndex) { 30 | if (comparator.compare(storage[2 * i + 1], storage[2 * i + 2]) < 0) 2 * i + 1 else 2 * i + 2 31 | } else 2 * i + 1 32 | if (comparator.compare(storage[i], storage[j]) <= 0) break 33 | storage[i] = storage[j].also { storage[j] = storage[i] } 34 | i = j 35 | } 36 | } 37 | return first 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /py/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | select = C,E,F,W,B,B950 4 | extend-ignore = E203, E501 5 | -------------------------------------------------------------------------------- /py/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | dist/ 3 | aoc2021/day*.txt 4 | *~ 5 | -------------------------------------------------------------------------------- /py/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Daniel Lin (c) 2021 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Daniel Lin nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /py/README.md: -------------------------------------------------------------------------------- 1 | # [Advent of Code 2021](https://adventofcode.com/2021) 2 | ### my answers in [Python](https://www.python.org/) ![Python CI](https://github.com/ephemient/aoc2021/workflows/Python%20CI/badge.svg) 3 | 4 | This project builds with [Poetry](https://python-poetry.org/). 5 | 6 | Setup: 7 | 8 | ```sh 9 | curl -sSL https://install.python-poetry.org | python3 - 10 | poetry install 11 | ``` 12 | 13 | Run the test suite: 14 | 15 | ```sh 16 | poetry run pytest 17 | ``` 18 | 19 | Run the benchmarks: 20 | 21 | ```sh 22 | poetry run pytest --benchmark-enable 23 | ``` 24 | 25 | Print solutions for the inputs provided in local data files: 26 | 27 | ```sh 28 | poetry run aoc2021 29 | ``` 30 | 31 | Lint and code with [Black](https://black.readthedocs.io/), [flake8](https://flake8.pycqa.org/), and [isort](https://pycqa.github.io/isort/): 32 | 33 | ```sh 34 | poetry run black . 35 | poetry run flake8 . 36 | poetry run isort . 37 | ``` 38 | -------------------------------------------------------------------------------- /py/aoc2021/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ephemient/aoc2021/aa98c082c539e4a13c112d3b8bc81be2d54d2d21/py/aoc2021/__init__.py -------------------------------------------------------------------------------- /py/aoc2021/day1.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import itertools 3 | 4 | 5 | def part1(lines): 6 | """ 7 | >>> part1(["199", "200", "208", "210", "200", "207", "240", "269", "260", "263"]) 8 | 7 9 | """ 10 | return sum(x < y for x, y in itertools.pairwise(map(int, lines))) 11 | 12 | 13 | def sliding_window(iterable, n): 14 | # sliding_window('ABCDEFG', 4) -> ABCD BCDE CDEF DEFG 15 | it = iter(iterable) 16 | window = collections.deque(itertools.islice(it, n), maxlen=n) 17 | if len(window) == n: 18 | yield tuple(window) 19 | for x in it: 20 | window.append(x) 21 | yield tuple(window) 22 | 23 | 24 | def part2(lines): 25 | """ 26 | >>> part2(["199", "200", "208", "210", "200", "207", "240", "269", "260", "263"]) 27 | 5 28 | """ 29 | return sum( 30 | x < y 31 | for x, y in itertools.pairwise(map(sum, sliding_window(map(int, lines), 3))) 32 | ) 33 | 34 | 35 | parts = (part1, part2) 36 | -------------------------------------------------------------------------------- /py/aoc2021/day10.py: -------------------------------------------------------------------------------- 1 | pairs = {"(": ")", "[": "]", "{": "}", "<": ">"} 2 | part1_points = {")": 3, "]": 57, "}": 1197, ">": 25137} 3 | part2_points = {")": 1, "]": 2, "}": 3, ">": 4} 4 | 5 | 6 | def part1(lines): 7 | """ 8 | >>> part1(["[({(<(())[]>[[{[]{<()<>>", "[(()[<>])]({[<{<<[]>>(", "{([(<{}[<>[]}>{[]{[(<()>", "(((({<>}<{<{<>}{[]{[]{}", "[[<[([]))<([[{}[[()]]]", "[{[{({}]{}}([{[{{{}}([]", "{<[[]]>}<{[{[{[]{()[[[]", "[<(<(<(<{}))><([]([]()", "<{([([[(<>()){}]>(<<{{", "<{([{{}}[<[[[<>{}]]]>[]]"]) 9 | 26397 10 | """ 11 | total = 0 12 | for line in lines: 13 | expected = [] 14 | for char in line.strip(): 15 | if char in pairs: 16 | expected.append(pairs[char]) 17 | elif not expected or char != expected.pop(): 18 | total += part1_points[char] 19 | break 20 | return total 21 | 22 | 23 | def part2(lines): 24 | """ 25 | >>> part2(["[({(<(())[]>[[{[]{<()<>>", "[(()[<>])]({[<{<<[]>>(", "{([(<{}[<>[]}>{[]{[(<()>", "(((({<>}<{<{<>}{[]{[]{}", "[[<[([]))<([[{}[[()]]]", "[{[{({}]{}}([{[{{{}}([]", "{<[[]]>}<{[{[{[]{()[[[]", "[<(<(<(<{}))><([]([]()", "<{([([[(<>()){}]>(<<{{", "<{([{{}}[<[[[<>{}]]]>[]]"]) 26 | 288957 27 | """ 28 | scores = [] 29 | for line in lines: 30 | expected = [] 31 | for char in line.strip(): 32 | if char in pairs: 33 | expected.append(pairs[char]) 34 | elif not expected or char != expected.pop(): 35 | break 36 | else: 37 | score = 0 38 | for char in expected[::-1]: 39 | score = 5 * score + part2_points[char] 40 | scores.append(score) 41 | scores.sort() 42 | return scores[len(scores) // 2] 43 | 44 | 45 | parts = (part1, part2) 46 | -------------------------------------------------------------------------------- /py/aoc2021/day11.py: -------------------------------------------------------------------------------- 1 | def solve(lines): 2 | """ 3 | >>> solve(["5483143223", "2745854711", "5264556173", "6141336146", "6357385478", "4167524645", "2176841721", "6882881134", "4846848554", "5283751526"]) 4 | (1656, 195) 5 | """ 6 | levels = [list(map(int, line.strip())) for line in lines] 7 | flashes = [] 8 | while any(n for row in levels for n in row): 9 | for row in levels: 10 | for i in range(len(row)): 11 | row[i] += 1 12 | count = 0 13 | while any(n > 9 for row in levels for n in row): 14 | for y0, row in enumerate(levels): 15 | for x0, level in enumerate(row): 16 | if level <= 9: 17 | continue 18 | count += 1 19 | for y1 in range(y0 - 1, y0 + 2): 20 | if not 0 <= y1 < len(levels): 21 | continue 22 | for x1 in range(x0 - 1, x0 + 2): 23 | if not 0 <= x1 < len(levels[y1]): 24 | continue 25 | if x0 == x1 and y0 == y1: 26 | levels[y1][x1] = -1 27 | elif levels[y1][x1] >= 0: 28 | levels[y1][x1] += 1 29 | flashes.append(count) 30 | for row in levels: 31 | for i in range(len(row)): 32 | if row[i] < 0: 33 | row[i] = 0 34 | return (sum(flashes[:100]), len(flashes)) 35 | 36 | 37 | parts = (solve,) 38 | -------------------------------------------------------------------------------- /py/aoc2021/day12.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | SAMPLE_INPUT_1 = [ 4 | "start-A", 5 | "start-b", 6 | "A-c", 7 | "A-b", 8 | "b-d", 9 | "A-end", 10 | "b-end", 11 | ] 12 | SAMPLE_INPUT_2 = [ 13 | "dc-end", 14 | "HN-start", 15 | "start-kj", 16 | "dc-start", 17 | "dc-HN", 18 | "LN-dc", 19 | "HN-end", 20 | "kj-sa", 21 | "kj-HN", 22 | "kj-dc", 23 | ] 24 | SAMPLE_INPUT_3 = [ 25 | "fs-end", 26 | "he-DX", 27 | "fs-he", 28 | "start-DX", 29 | "pj-DX", 30 | "end-zg", 31 | "zg-sl", 32 | "zg-pj", 33 | "pj-he", 34 | "RW-he", 35 | "fs-DX", 36 | "pj-RW", 37 | "zg-RW", 38 | "start-pj", 39 | "he-WI", 40 | "zg-he", 41 | "pj-fs", 42 | "start-RW", 43 | ] 44 | 45 | 46 | def solve(lines, bonus): 47 | edges = defaultdict(set) 48 | for line in lines: 49 | lhs, rhs = line.strip().split("-", maxsplit=2) 50 | edges[lhs].add(rhs) 51 | edges[rhs].add(lhs) 52 | 53 | def walk(current, path, bonus): 54 | if current == "end": 55 | yield path 56 | else: 57 | for next in edges[current]: 58 | if next == "start": 59 | pass 60 | elif next.isupper() or next not in path: 61 | yield from walk(next, path + [next], bonus) 62 | elif bonus and next.islower(): 63 | yield from walk(next, path + [next], False) 64 | 65 | return sum(1 for _ in walk("start", ["start"], bonus)) 66 | 67 | 68 | def part1(lines): 69 | """ 70 | >>> part1(SAMPLE_INPUT_1) 71 | 10 72 | >>> part1(SAMPLE_INPUT_2) 73 | 19 74 | >>> part1(SAMPLE_INPUT_3) 75 | 226 76 | """ 77 | return solve(lines, False) 78 | 79 | 80 | def part2(lines): 81 | """ 82 | >>> part2(SAMPLE_INPUT_1) 83 | 36 84 | >>> part2(SAMPLE_INPUT_2) 85 | 103 86 | >>> part2(SAMPLE_INPUT_3) 87 | 3509 88 | """ 89 | return solve(lines, True) 90 | 91 | 92 | parts = (part1, part2) 93 | -------------------------------------------------------------------------------- /py/aoc2021/day13.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | SAMPLE_INPUT = [ 4 | "6,10", 5 | "0,14", 6 | "9,10", 7 | "0,3", 8 | "10,4", 9 | "4,11", 10 | "6,0", 11 | "6,12", 12 | "4,1", 13 | "0,13", 14 | "10,12", 15 | "3,4", 16 | "3,0", 17 | "8,4", 18 | "1,10", 19 | "2,14", 20 | "8,10", 21 | "9,0", 22 | "", 23 | "fold along y=7", 24 | "fold along x=5", 25 | ] 26 | 27 | 28 | def foldX(foldX, x, y): 29 | return (foldX - abs(x - foldX), y) 30 | 31 | 32 | def foldY(foldY, x, y): 33 | return (x, foldY - abs(y - foldY)) 34 | 35 | 36 | def parse(lines): 37 | it = iter(lines) 38 | points = set() 39 | for line in it: 40 | if not line or line.isspace(): 41 | break 42 | x, y = line.split(",", maxsplit=2) 43 | points.add((int(x), int(y))) 44 | folds = [] 45 | for line in it: 46 | if line.startswith("fold along x="): 47 | folds.append(functools.partial(foldX, int(line[13:].rstrip()))) 48 | elif line.startswith("fold along y="): 49 | folds.append(functools.partial(foldY, int(line[13:].rstrip()))) 50 | else: 51 | raise ValueError(f"bad input: {line}") 52 | return points, folds 53 | 54 | 55 | def part1(lines): 56 | """ 57 | >>> part1(SAMPLE_INPUT) 58 | 17 59 | """ 60 | points, folds = parse(lines) 61 | fold, *_ = folds 62 | points = set(fold(x, y) for x, y in points) 63 | return len(points) 64 | 65 | 66 | def part2(lines): 67 | """ 68 | >>> part2(SAMPLE_INPUT) 69 | '▓▓▓▓▓\\n▓░░░▓\\n▓░░░▓\\n▓░░░▓\\n▓▓▓▓▓' 70 | """ 71 | points, folds = parse(lines) 72 | 73 | def fold(point, fold): 74 | x, y = point 75 | return fold(x, y) 76 | 77 | points = set(functools.reduce(fold, folds, point) for point in points) 78 | x0 = min(x for x, _ in points) 79 | y0 = min(y for _, y in points) 80 | x1 = max(x for x, _ in points) 81 | y1 = max(y for _, y in points) 82 | return "\n".join( 83 | "".join("\u2593" if (x, y) in points else "\u2591" for x in range(x0, x1 + 1)) 84 | for y in range(y0, y1 + 1) 85 | ) 86 | 87 | 88 | parts = (part1, part2) 89 | -------------------------------------------------------------------------------- /py/aoc2021/day14.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | SAMPLE_INPUT = [ 4 | "NNCB", 5 | "", 6 | "CH -> B", 7 | "HH -> N", 8 | "CB -> H", 9 | "NH -> C", 10 | "HB -> C", 11 | "HC -> B", 12 | "HN -> C", 13 | "NN -> C", 14 | "BH -> H", 15 | "NC -> B", 16 | "NB -> B", 17 | "BN -> B", 18 | "BB -> N", 19 | "BC -> B", 20 | "CC -> N", 21 | "CN -> C", 22 | ] 23 | 24 | 25 | def solve(lines, times): 26 | rules = { 27 | line[:2]: (line[0] + line[-1], line[-1] + line[1]) 28 | for line in map(str.strip, lines[2:]) 29 | } 30 | state = Counter(a + b for a, b in zip(lines[0], lines[0][1:].rstrip())) 31 | for _ in range(times): 32 | newstate = Counter() 33 | for src, n in state.items(): 34 | for dst in rules[src]: 35 | newstate[dst] += n 36 | state = newstate 37 | counts = Counter(lines[0][0] + lines[0].rstrip()[-1]) 38 | for pair, n in state.items(): 39 | for c in pair: 40 | counts[c] += n 41 | return (max(counts.values()) - min(counts.values())) // 2 42 | 43 | 44 | def part1(lines): 45 | """ 46 | >>> part1(SAMPLE_INPUT) 47 | 1588 48 | """ 49 | return solve(lines, 10) 50 | 51 | 52 | def part2(lines): 53 | """ 54 | >>> part2(SAMPLE_INPUT) 55 | 2188189693529 56 | """ 57 | return solve(lines, 40) 58 | 59 | 60 | parts = (part1, part2) 61 | -------------------------------------------------------------------------------- /py/aoc2021/day15.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | import math 3 | 4 | SAMPLE_INPUT = [ 5 | "1163751742", 6 | "1381373672", 7 | "2136511328", 8 | "3694931569", 9 | "7463417111", 10 | "1319128137", 11 | "1359912421", 12 | "3125421639", 13 | "1293138521", 14 | "2311944581", 15 | ] 16 | 17 | 18 | def solve(risks): 19 | bests = [[math.inf] * len(row) for row in risks] 20 | bests[0][0] = 0 21 | queue = [] 22 | heapq.heappush(queue, (0, 0, 0)) 23 | while True: 24 | _, x0, y0 = heapq.heappop(queue) 25 | c = bests[y0][x0] 26 | if y0 == len(risks) - 1 and x0 == len(risks[y0]) - 1: 27 | return c 28 | for x1, y0 in ((x0 - 1, y0), (x0, y0 - 1), (x0, y0 + 1), (x0 + 1, y0)): 29 | if y0 not in range(len(risks)) or x1 not in range(len(risks[y0])): 30 | continue 31 | d = c + risks[y0][x1] 32 | if d < bests[y0][x1]: 33 | bests[y0][x1] = d 34 | heapq.heappush(queue, (d, x1, y0)) 35 | 36 | 37 | def part1(lines): 38 | """ 39 | >>> part1(SAMPLE_INPUT) 40 | 40 41 | """ 42 | risks = [list(map(int, line.strip())) for line in lines] 43 | return solve(risks) 44 | 45 | 46 | def part2(lines): 47 | """ 48 | >>> part2(SAMPLE_INPUT) 49 | 315 50 | """ 51 | risks = [ 52 | [(int(char) - 1 + dx + dy) % 9 + 1 for dx in range(5) for char in line.strip()] 53 | for dy in range(5) 54 | for line in lines 55 | ] 56 | return solve(risks) 57 | 58 | 59 | parts = (part1, part2) 60 | -------------------------------------------------------------------------------- /py/aoc2021/day17.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import math 3 | import operator 4 | import re 5 | from collections import defaultdict 6 | 7 | RE = re.compile(r"target area: x=(\d+)..(\d+), y=(-\d+)..(-\d+)") 8 | 9 | 10 | def solve(lines): 11 | """ 12 | >>> solve(["target area: x=20..30, y=-10..-5"]) 13 | (45, 112) 14 | """ 15 | match = RE.match(lines[0]) 16 | x0 = int(match.group(1)) 17 | x1 = int(match.group(2)) 18 | y0 = int(match.group(3)) 19 | y1 = int(match.group(4)) 20 | max_t, dy_hits = 0, defaultdict(set) 21 | for dy in range(y0, -y0 + 1): 22 | for t, y in enumerate( 23 | itertools.accumulate(itertools.count(dy, -1), operator.add, initial=0) 24 | ): 25 | if y < y0: 26 | break 27 | if y0 <= y <= y1: 28 | max_t = max(max_t, t) 29 | dy_hits[t].add(dy) 30 | max_dy, count = 0, 0 31 | for dx in range(math.ceil(math.sqrt(2.0 * x0 + 0.25) - 0.5), x1 + 1): 32 | dys = set() 33 | for t, x in enumerate( 34 | itertools.accumulate( 35 | itertools.chain(range(dx, 0, -1), itertools.repeat(0)), 36 | operator.add, 37 | initial=0, 38 | ) 39 | ): 40 | if t > max_t or x > x1: 41 | break 42 | if x0 <= x <= x1: 43 | dys.update(dy_hits[t]) 44 | max_dy = max(max_dy, max(dys, default=0)) 45 | count += len(dys) 46 | return max_dy * (max_dy + 1) // 2, count 47 | 48 | 49 | parts = (solve,) 50 | -------------------------------------------------------------------------------- /py/aoc2021/day2.py: -------------------------------------------------------------------------------- 1 | def part1(lines): 2 | """ 3 | >>> part1(["forward 5", "down 5", "forward 8", "up 3", "down 8", "forward 2"]) 4 | 150 5 | """ 6 | x, y = 0, 0 7 | for line in lines: 8 | match line.partition(" "): 9 | case ("forward", _, d): 10 | x += int(d) 11 | case ("down", _, d): 12 | y += int(d) 13 | case ("up", _, d): 14 | y -= int(d) 15 | return x * y 16 | 17 | 18 | def part2(lines): 19 | """ 20 | >>> part2(["forward 5", "down 5", "forward 8", "up 3", "down 8", "forward 2"]) 21 | 900 22 | """ 23 | x, y, depth = 0, 0, 0 24 | for line in lines: 25 | match line.partition(" "): 26 | case ("forward", _, d): 27 | d = int(d) 28 | x += d 29 | y += d * depth 30 | case ("down", _, d): 31 | depth += int(d) 32 | case ("up", _, d): 33 | depth -= int(d) 34 | return x * y 35 | 36 | 37 | parts = (part1, part2) 38 | -------------------------------------------------------------------------------- /py/aoc2021/day21.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | 4 | def part1(lines): 5 | """ 6 | >>> part1(["Player 1 starting position: 4", "Player 2 starting position: 8"]) 7 | 739785 8 | """ 9 | player1, player2 = int(lines[0][28:].strip()), int(lines[1][28:].strip()) 10 | score1, score2, n = 0, 0, 0 11 | while score2 < 1000: 12 | player1, player2 = ( 13 | player2, 14 | (player1 + n % 100 + (n + 1) % 100 + (n + 2) % 100 + 2) % 10 + 1, 15 | ) 16 | score1, score2 = score2, score1 + player2 17 | n += 3 18 | return n * score1 19 | 20 | 21 | @functools.lru_cache(maxsize=None) 22 | def score(player1, player2, score1, score2): 23 | x, y = 0, 0 24 | for d, n in ((3, 1), (4, 3), (5, 6), (6, 7), (7, 6), (8, 3), (9, 1)): 25 | play = (player1 + d - 1) % 10 + 1 26 | if score1 + play < 21: 27 | x2, y2 = score(player2, play, score2, score1 + play) 28 | x += n * y2 29 | y += n * x2 30 | else: 31 | x += n 32 | return x, y 33 | 34 | 35 | def part2(lines): 36 | """ 37 | >>> part2(["Player 1 starting position: 4", "Player 2 starting position: 8"]) 38 | 444356092776315 39 | """ 40 | x, y = score(int(lines[0][28:].strip()), int(lines[1][28:].strip()), 0, 0) 41 | return max(x, y) 42 | 43 | 44 | parts = (part1, part2) 45 | -------------------------------------------------------------------------------- /py/aoc2021/day25.py: -------------------------------------------------------------------------------- 1 | def part1(lines): 2 | """ 3 | >>> part1(["v...>>.vv>", ".vv>>.vv..", ">>.>v>...v", ">>v>>.>.v.", "v>v.vv.v..", ">.>>..v...", ".vv..>.>v.", "v.v..>>v.v", "....v..v.>"]) 4 | 58 5 | """ 6 | grid = [list(line.strip()) for line in lines] 7 | n = 1 8 | while True: 9 | a, b = False, False 10 | for row in grid: 11 | indices = [ 12 | i 13 | for i in range(len(row)) 14 | if row[i] == ">" and row[(i + 1) % len(row)] == "." 15 | ] 16 | if indices: 17 | a = True 18 | for i in indices: 19 | row[i] = "." 20 | row[(i + 1) % len(row)] = ">" 21 | for col in range(len(grid[0])): 22 | indices = [ 23 | i 24 | for i in range(len(grid)) 25 | if grid[i][col] == "v" and grid[(i + 1) % len(grid)][col] == "." 26 | ] 27 | if indices: 28 | b = True 29 | for i in indices: 30 | grid[i][col] = "." 31 | grid[(i + 1) % len(grid)][col] = "v" 32 | if not (a or b): 33 | return n 34 | n += 1 35 | 36 | 37 | parts = (part1,) 38 | -------------------------------------------------------------------------------- /py/aoc2021/day3.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | 4 | def part1(lines): 5 | """ 6 | >>> part1(["00100", "11110", "10110", "10111", "10101", "01111", "00111", "11100", "10000", "11001", "00010", "01010"]) 7 | 198 8 | """ 9 | gamma = "".join(Counter(column).most_common(1)[0][0] for column in zip(*lines)) 10 | epsilon = "".join(chr(ord(c) ^ 1) for c in gamma) 11 | return int(gamma, 2) * int(epsilon, 2) 12 | 13 | 14 | def part2(lines): 15 | """ 16 | >>> part2(["00100", "11110", "10110", "10111", "10101", "01111", "00111", "11100", "10000", "11001", "00010", "01010"]) 17 | 230 18 | """ 19 | o2 = lines 20 | co2 = lines 21 | i = 0 22 | while len(o2) > 1 or len(co2) > 1: 23 | common = max( 24 | Counter(s[i] for s in o2).items(), key=lambda entry: (entry[1], entry[0]) 25 | )[0] 26 | uncommon = min( 27 | Counter(s[i] for s in co2).items(), key=lambda entry: (entry[1], entry[0]) 28 | )[0] 29 | o2 = [s for s in o2 if s[i] == common] 30 | co2 = [s for s in co2 if s[i] == uncommon] 31 | i += 1 32 | return int(o2[0], 2) * int(co2[0], 2) 33 | 34 | 35 | parts = (part1, part2) 36 | -------------------------------------------------------------------------------- /py/aoc2021/day4.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import math 3 | import operator 4 | 5 | 6 | def solve(lines): 7 | """ 8 | >>> solve(["7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1", "", "22 13 17 11 0", " 8 2 23 4 24", "21 9 14 16 7", " 6 10 3 18 5", " 1 12 20 15 19", "", " 3 15 0 2 22", " 9 18 13 17 5", "19 8 7 25 23", "20 11 10 24 4", "14 21 16 12 6", "", "14 21 17 24 4", "10 16 15 9 19", "18 8 23 26 20", "22 11 13 6 5", " 2 0 12 3 7"]) 9 | (4512, 1924) 10 | """ 11 | it = iter(lines) 12 | draws = [int(draw) for draw in next(it).split(",")] 13 | draw_turns = {} 14 | for turn, draw in enumerate(draws): 15 | draw_turns.setdefault(draw, turn) 16 | boards = [] 17 | for key, group in itertools.groupby(it, key=lambda line: bool(line.strip())): 18 | if not key: 19 | continue 20 | width = -1 21 | values = [] 22 | for y, line in enumerate(group): 23 | row = [int(item) for item in line.split()] 24 | if width < 0: 25 | width = len(row) 26 | else: 27 | assert width == len(row) 28 | values.extend(row) 29 | assert values 30 | turns = [draw_turns.get(value, math.inf) for value in values] 31 | turn = min( 32 | min( 33 | max(turns[j] for j in range(i, i + width)) 34 | for i in range(0, len(turns), width) 35 | ), 36 | min( 37 | max(turns[j] for j in range(i, len(turns), width)) 38 | for i in range(0, width) 39 | ), 40 | ) 41 | if turn < math.inf: 42 | boards.append( 43 | ( 44 | turn, 45 | draws[turn] 46 | * sum( 47 | value 48 | for value in values 49 | if draw_turns.get(value, math.inf) > turn 50 | ), 51 | ) 52 | ) 53 | return ( 54 | min(boards, key=operator.itemgetter(0))[1], 55 | max(boards, key=operator.itemgetter(0))[1], 56 | ) 57 | 58 | 59 | parts = (solve,) 60 | -------------------------------------------------------------------------------- /py/aoc2021/day5.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | 4 | def part1(lines): 5 | """ 6 | >>> part1(["0,9 -> 5,9", "8,0 -> 0,8", "9,4 -> 3,4", "2,2 -> 2,1", "7,0 -> 7,4", "6,4 -> 2,0", "0,9 -> 2,9", "3,4 -> 1,4", "0,0 -> 8,8", "5,5 -> 8,2"]) 7 | 5 8 | """ 9 | points = Counter() 10 | for line in lines: 11 | start, end = line.split(" -> ", maxsplit=2) 12 | x0, y0 = map(int, start.split(",", maxsplit=2)) 13 | x1, y1 = map(int, end.split(",", maxsplit=2)) 14 | if x0 == x1: 15 | points.update((x0, y) for y in range(min(y0, y1), max(y0, y1) + 1)) 16 | elif y0 == y1: 17 | points.update((x, y0) for x in range(min(x0, x1), max(x0, x1) + 1)) 18 | return sum(count > 1 for count in points.values()) 19 | 20 | 21 | def part2(lines): 22 | """ 23 | >>> part2(["0,9 -> 5,9", "8,0 -> 0,8", "9,4 -> 3,4", "2,2 -> 2,1", "7,0 -> 7,4", "6,4 -> 2,0", "0,9 -> 2,9", "3,4 -> 1,4", "0,0 -> 8,8", "5,5 -> 8,2"]) 24 | 12 25 | """ 26 | points = Counter() 27 | for line in lines: 28 | start, end = line.split(" -> ", maxsplit=2) 29 | x0, y0 = map(int, start.split(",", maxsplit=2)) 30 | x1, y1 = map(int, end.split(",", maxsplit=2)) 31 | if x0 == x1: 32 | points.update((x0, y) for y in range(min(y0, y1), max(y0, y1) + 1)) 33 | elif y0 == y1: 34 | points.update((x, y0) for x in range(min(x0, x1), max(x0, x1) + 1)) 35 | elif abs(x1 - x0) == abs(y1 - y0): 36 | dx = 1 if x1 > x0 else -1 37 | dy = 1 if y1 > y0 else -1 38 | points.update((x0 + i * dx, y0 + i * dy) for i in range(abs(x1 - x0) + 1)) 39 | return sum(count > 1 for count in points.values()) 40 | 41 | 42 | parts = (part1, part2) 43 | -------------------------------------------------------------------------------- /py/aoc2021/day6.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | step = ( 4 | (0, 1, 0, 0, 0, 0, 0, 0, 0), 5 | (0, 0, 1, 0, 0, 0, 0, 0, 0), 6 | (0, 0, 0, 1, 0, 0, 0, 0, 0), 7 | (0, 0, 0, 0, 1, 0, 0, 0, 0), 8 | (0, 0, 0, 0, 0, 1, 0, 0, 0), 9 | (0, 0, 0, 0, 0, 0, 1, 0, 0), 10 | (1, 0, 0, 0, 0, 0, 0, 1, 0), 11 | (0, 0, 0, 0, 0, 0, 0, 0, 1), 12 | (1, 0, 0, 0, 0, 0, 0, 0, 0), 13 | ) 14 | 15 | 16 | def mul(x, y): 17 | cols = min(len(row) for row in y) 18 | return tuple( 19 | tuple(sum(a * b[j] for a, b in zip(row, y)) for j in range(cols)) 20 | for i, row in enumerate(x) 21 | ) 22 | 23 | 24 | def pow(base, e): 25 | if e <= 1: 26 | return base 27 | double = pow(mul(base, base), e // 2) 28 | return mul(double, base) if e % 2 else double 29 | 30 | 31 | def precompute(times): 32 | """ 33 | >>> precompute(80) 34 | (1421, 1401, 1191, 1154, 1034, 950, 905, 779, 768) 35 | >>> precompute(256) 36 | (6703087164, 6206821033, 5617089148, 5217223242, 4726100874, 4368232009, 3989468462, 3649885552, 3369186778) 37 | """ 38 | matrix = pow(step, times) 39 | return tuple(map(sum, zip(*matrix))) 40 | 41 | 42 | def solve(constants, lines): 43 | """ 44 | >>> part1(["3,4,3,1,2"]) 45 | 5934 46 | >>> part2(["3,4,3,1,2"]) 47 | 26984457539 48 | """ 49 | return sum(constants[int(num)] for line in lines for num in line.strip().split(",")) 50 | 51 | 52 | part1 = functools.partial(solve, precompute(80)) 53 | part2 = functools.partial(solve, precompute(256)) 54 | 55 | 56 | parts = (part1, part2) 57 | -------------------------------------------------------------------------------- /py/aoc2021/day7.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | def part1(lines): 5 | """ 6 | >>> part1(["16,1,2,0,4,2,7,1,2,14"]) 7 | 37 8 | """ 9 | nums = sorted(int(word) for line in lines for word in line.strip().split(",")) 10 | median = nums[len(nums) // 2] 11 | return sum(abs(num - median) for num in nums) 12 | 13 | 14 | def t(n): 15 | return n * (n + 1) // 2 16 | 17 | 18 | def part2(lines): 19 | """ 20 | >>> part2(["16,1,2,0,4,2,7,1,2,14"]) 21 | 168 22 | """ 23 | nums = list(int(word) for line in lines for word in line.strip().split(",")) 24 | mean = sum(nums) / len(nums) 25 | mean0 = math.floor(mean) 26 | mean1 = math.ceil(mean) 27 | return min( 28 | sum(t(abs(num - mean0)) for num in nums), 29 | sum(t(abs(num - mean1)) for num in nums), 30 | ) 31 | 32 | 33 | parts = (part1, part2) 34 | -------------------------------------------------------------------------------- /py/aoc2021/day8.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from functools import reduce 3 | 4 | SAMPLE_INPUT_1 = [ 5 | "acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb fcadb cdfeb cdbaf", 6 | ] 7 | 8 | SAMPLE_INPUT_2 = [ 9 | "be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe", 10 | "edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc", 11 | "fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg", 12 | "fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb", 13 | "aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea", 14 | "fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb", 15 | "dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe", 16 | "bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef", 17 | "egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb", 18 | "gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce", 19 | ] 20 | 21 | 22 | def part1(lines): 23 | """ 24 | >>> part1(SAMPLE_INPUT_2) 25 | 26 26 | """ 27 | return sum( 28 | len(word) in (2, 4, 3, 7) 29 | for line in lines 30 | for word in line.split(" | ", maxsplit=2)[-1].strip().split() 31 | ) 32 | 33 | 34 | def part2(lines): 35 | """ 36 | >>> part2(SAMPLE_INPUT_1) 37 | 5353 38 | >>> part2(SAMPLE_INPUT_2) 39 | 61229 40 | """ 41 | total = 0 42 | for line in lines: 43 | lhs, rhs = line.strip().split(" | ", maxsplit=2) 44 | signals = defaultdict(list) 45 | for signal in lhs.split(): 46 | signals[len(signal)].append(set(signal)) 47 | (one,) = signals[2] 48 | (seven,) = signals[3] 49 | (four,) = signals[4] 50 | (*three_five, two) = sorted(signals[5], key=(one | four).__rsub__) 51 | (three, five) = sorted(three_five, key=two.__rsub__) 52 | (*zero_nine, six) = sorted(signals[6], key=one.__sub__) 53 | (nine, zero) = sorted(zero_nine, key=three.__rsub__) 54 | (eight,) = signals[7] 55 | digits = (zero, one, two, three, four, five, six, seven, eight, nine) 56 | output = [digits.index(set(output)) for output in rhs.split()] 57 | total += reduce(lambda x, y: 10 * x + y, output) 58 | return total 59 | 60 | 61 | parts = (part1, part2) 62 | -------------------------------------------------------------------------------- /py/aoc2021/day9.py: -------------------------------------------------------------------------------- 1 | from math import prod 2 | 3 | 4 | def part1(lines): 5 | """ 6 | >>> part1(["2199943210", "3987894921", "9856789892", "8767896789", "9899965678"]) 7 | 15 8 | """ 9 | return sum( 10 | int(c) + 1 11 | for i, line in enumerate(lines) 12 | for j, c in enumerate(line.strip()) 13 | if "0" <= c <= "9" 14 | and all( 15 | c < lines[i2][j2] 16 | for (i2, j2) in ((i - 1, j), (i, j - 1), (i, j + 1), (i + 1, j)) 17 | if 0 <= i2 < len(lines) and 0 <= j2 < len(lines[i2].strip()) 18 | ) 19 | ) 20 | 21 | 22 | def part2(lines): 23 | """ 24 | >>> part2(["2199943210", "3987894921", "9856789892", "8767896789", "9899965678"]) 25 | 1134 26 | """ 27 | basins = [] 28 | visited = [[not "0" <= c < "9" for c in line.strip()] for line in lines] 29 | for i, row in enumerate(visited): 30 | for j, v in enumerate(row): 31 | if v: 32 | continue 33 | visited[i][j] = True 34 | basin = 0 35 | stack = [(i, j)] 36 | while stack: 37 | i2, j2 = stack.pop() 38 | basin += 1 39 | for i3, j3 in ((i2 - 1, j2), (i2, j2 - 1), (i2, j2 + 1), (i2 + 1, j2)): 40 | if ( 41 | 0 <= i3 < len(visited) 42 | and 0 <= j3 < len(visited[i3]) 43 | and not visited[i3][j3] 44 | ): 45 | visited[i3][j3] = True 46 | stack.append((i3, j3)) 47 | basins.append(basin) 48 | return prod(sorted(basins)[-3:]) 49 | 50 | 51 | parts = (part1, part2) 52 | -------------------------------------------------------------------------------- /py/aoc2021/main.py: -------------------------------------------------------------------------------- 1 | import io 2 | import sys 3 | 4 | import pkg_resources 5 | 6 | 7 | def main(): 8 | args = set(int(arg) for arg in sys.argv[1:] if arg.isnumeric()) 9 | days = pkg_resources.get_entry_map("aoc2021", "aoc2021.days") 10 | for day, entry in sorted(days.items(), key=lambda item: int(item[0])): 11 | if args and int(day) not in args: 12 | continue 13 | print(f"Day {day}") 14 | with io.TextIOWrapper( 15 | pkg_resources.resource_stream("aoc2021", f"day{day}.txt") 16 | ) as fh: 17 | data = fh.readlines() 18 | for part in entry.load(): 19 | print(part(data)) 20 | print() 21 | 22 | 23 | if __name__ == "__main__": 24 | main() 25 | -------------------------------------------------------------------------------- /py/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "aoc2021" 3 | version = "0.1.0" 4 | description = "" 5 | license = "BSD-2-Clause" 6 | authors = ["Daniel Lin "] 7 | readme = "README.md" 8 | repository = "https://github.com/ephemient/aoc2021/tree/main/py" 9 | classifiers = [ 10 | "Development Status :: 3 - Alpha", 11 | "Programming Language :: Python :: 3", 12 | "Environment :: Console", 13 | "Intended Audience :: Developers", 14 | "Intended Audience :: Education", 15 | "Operating System :: OS Independent" 16 | ] 17 | include = ["aoc2021/day*.txt"] 18 | 19 | [tool.poetry.dependencies] 20 | python = "^3.10" 21 | 22 | [tool.poetry.dev-dependencies] 23 | black = "^21.12b0" 24 | flake8 = "^4.0.1" 25 | isort = "^5.10.1" 26 | pytest = "^6.2.5" 27 | pytest-benchmark = { version = "^3.4.1", extras = ["histogram"] } 28 | 29 | [tool.poetry.scripts] 30 | aoc2021 = "aoc2021.main:main" 31 | 32 | [tool.poetry.plugins."aoc2021.days"] 33 | 1 = "aoc2021.day1:parts" 34 | 2 = "aoc2021.day2:parts" 35 | 3 = "aoc2021.day3:parts" 36 | 4 = "aoc2021.day4:parts" 37 | 5 = "aoc2021.day5:parts" 38 | 6 = "aoc2021.day6:parts" 39 | 7 = "aoc2021.day7:parts" 40 | 8 = "aoc2021.day8:parts" 41 | 9 = "aoc2021.day9:parts" 42 | 10 = "aoc2021.day10:parts" 43 | 11 = "aoc2021.day11:parts" 44 | 12 = "aoc2021.day12:parts" 45 | 13 = "aoc2021.day13:parts" 46 | 14 = "aoc2021.day14:parts" 47 | 15 = "aoc2021.day15:parts" 48 | 16 = "aoc2021.day16:parts" 49 | 17 = "aoc2021.day17:parts" 50 | 18 = "aoc2021.day18:parts" 51 | 19 = "aoc2021.day19:parts" 52 | 20 = "aoc2021.day20:parts" 53 | 21 = "aoc2021.day21:parts" 54 | 22 = "aoc2021.day22:parts" 55 | 23 = "aoc2021.day23:parts" 56 | 24 = "aoc2021.day24:parts" 57 | 25 = "aoc2021.day25:parts" 58 | 59 | [tool.black] 60 | target_version = ["py310"] 61 | 62 | [tool.isort] 63 | profile = "black" 64 | 65 | [tool.pytest.ini_options] 66 | addopts = '--doctest-modules --benchmark-disable --benchmark-sort=fullname' 67 | required_plugins = ['pytest-benchmark'] 68 | 69 | [build-system] 70 | requires = ["poetry-core>=1.0.0"] 71 | build-backend = "poetry.core.masonry.api" 72 | -------------------------------------------------------------------------------- /rs/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc2021" 3 | description = "Advent of Code 2021 - my answers" 4 | version = "0.1.0" 5 | authors = ["Daniel Lin "] 6 | license = "BSD-3-Clause" 7 | edition = "2021" 8 | readme = "README.md" 9 | homepage = "https://github.com/ephemient/aoc2021/tree/main/rs" 10 | build = "build.rs" 11 | 12 | [lib] 13 | name = "aoc2021" 14 | path = "src/lib.rs" 15 | 16 | [[bin]] 17 | name = "aoc2021" 18 | path = "src/main.rs" 19 | 20 | [build-dependencies] 21 | build_const = "0.2" 22 | 23 | [dependencies] 24 | build_const = "0.2" 25 | itertools = "0.10" 26 | lazy_static = "1" 27 | regex = "1" 28 | 29 | [dev-dependencies] 30 | criterion = "0.3" 31 | pretty_assertions = "1" 32 | 33 | [[bench]] 34 | name = "criterion" 35 | harness = false 36 | -------------------------------------------------------------------------------- /rs/README.md: -------------------------------------------------------------------------------- 1 | # [Advent of Code 2021](https://adventofcode.com/2021) 2 | ### my answers in [Rust](https://www.rust-lang.org/) ![Rust CI](https://github.com/ephemient/aoc2021/workflows/Rust%20CI/badge.svg) 3 | 4 | This project builds with [Cargo](https://docs.rust-lang.org/cargo). 5 | 6 | Run the test suite: 7 | 8 | ```sh 9 | cargo test 10 | ``` 11 | 12 | Run the [Criterion.rs](https://github.com/bheisler/criterion.rs) benchmarks: 13 | 14 | ```sh 15 | cargo install cargo-criterion 16 | cargo criterion 17 | ``` 18 | 19 | Print solutions for the inputs provided in local data files: 20 | 21 | ```sh 22 | cargo run 23 | ``` 24 | -------------------------------------------------------------------------------- /rs/src/day1.rs: -------------------------------------------------------------------------------- 1 | use super::util; 2 | use std::error::Error; 3 | use std::vec::Vec; 4 | 5 | pub fn part1<'a, I, S>(lines: I) -> Result> 6 | where 7 | I: IntoIterator, 8 | S: AsRef + 'a, 9 | { 10 | let nums: Vec = util::parse_many(lines)?; 11 | Ok(nums 12 | .iter() 13 | .zip(nums.iter().skip(1)) 14 | .filter(|(x, y)| x < y) 15 | .count()) 16 | } 17 | 18 | pub fn part2<'a, I, S>(lines: I) -> Result> 19 | where 20 | I: IntoIterator, 21 | S: AsRef + 'a, 22 | { 23 | let nums: Vec = util::parse_many(lines)?; 24 | let sums: Vec = nums.windows(3).map(|w| w.iter().sum()).collect(); 25 | Ok(sums 26 | .iter() 27 | .zip(sums.iter().skip(1)) 28 | .filter(|(x, y)| x < y) 29 | .count()) 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | use pretty_assertions::assert_eq; 36 | 37 | static EXAMPLE: &[&str] = &[ 38 | "199", "200", "208", "210", "200", "207", "240", "269", "260", "263", 39 | ]; 40 | 41 | #[test] 42 | fn part1_examples() -> Result<(), Box> { 43 | assert_eq!(7, part1(EXAMPLE)?); 44 | Ok(()) 45 | } 46 | 47 | #[test] 48 | fn part2_examples() -> Result<(), Box> { 49 | assert_eq!(5, part2(EXAMPLE)?); 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rs/src/day11.rs: -------------------------------------------------------------------------------- 1 | pub fn solve<'a, I, S>(lines: I) -> Option<(usize, usize)> 2 | where 3 | I: IntoIterator, 4 | S: AsRef + 'a, 5 | { 6 | let mut state: Vec>> = lines 7 | .into_iter() 8 | .map(|line| line.as_ref().chars().map(|c| c.to_digit(10)).collect()) 9 | .collect(); 10 | let mut flashes = Vec::new(); 11 | while state 12 | .iter() 13 | .any(|row| row.iter().any(|n| n.map_or(false, |n| n != 0))) 14 | { 15 | for row in state.iter_mut() { 16 | for n in row.iter_mut() { 17 | *n = Some(n.map_or(1, |n| n + 1)); 18 | } 19 | } 20 | let mut count = 0; 21 | while state 22 | .iter() 23 | .any(|row| row.iter().any(|n| n.map_or(false, |n| n > 9))) 24 | { 25 | for y0 in 0..state.len() { 26 | for x0 in 0..state[y0].len() { 27 | if state[y0][x0].map_or(true, |n| n <= 9) { 28 | continue; 29 | } 30 | count += 1; 31 | for y1 in y0.saturating_sub(1)..=y0 + 1 { 32 | let row = match state.get_mut(y1) { 33 | Some(row) => row, 34 | _ => continue, 35 | }; 36 | for x1 in x0.saturating_sub(1)..=x0 + 1 { 37 | if let Some(n) = row.get_mut(x1) { 38 | if x0 == x1 && y0 == y1 { 39 | *n = None; 40 | } else if let Some(m) = *n { 41 | *n = Some(m + 1); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | flashes.push(count); 50 | } 51 | Some((flashes.iter().take(100).sum(), flashes.len())) 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | use pretty_assertions::assert_eq; 58 | use std::error::Error; 59 | 60 | static EXAMPLE: &[&str] = &[ 61 | "5483143223", 62 | "2745854711", 63 | "5264556173", 64 | "6141336146", 65 | "6357385478", 66 | "4167524645", 67 | "2176841721", 68 | "6882881134", 69 | "4846848554", 70 | "5283751526", 71 | ]; 72 | 73 | #[test] 74 | fn solve_examples() -> Result<(), Box> { 75 | assert_eq!(Some((1656, 195)), solve(EXAMPLE)); 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rs/src/day25.rs: -------------------------------------------------------------------------------- 1 | pub fn part1<'a, I, S>(lines: I) -> Option 2 | where 3 | I: IntoIterator, 4 | S: AsRef + 'a, 5 | { 6 | let mut grid: Vec> = lines 7 | .into_iter() 8 | .map(|line| line.as_ref().chars().collect()) 9 | .collect(); 10 | let width = grid.get(0)?.len(); 11 | let height = grid.len(); 12 | if grid.iter().skip(1).any(|row| row.len() != width) { 13 | return None; 14 | } 15 | let mut n = 1; 16 | loop { 17 | let mut a = false; 18 | let mut b = false; 19 | for row in &mut grid { 20 | let indices: Vec<_> = (0..width) 21 | .filter(|&i| row[i] == '>' && row[(i + 1) % width] == '.') 22 | .collect(); 23 | if !indices.is_empty() { 24 | a = true; 25 | } 26 | for i in indices { 27 | row[i] = '.'; 28 | row[(i + 1) % width] = '>'; 29 | } 30 | } 31 | for col in 0..width { 32 | let indices: Vec<_> = (0..height) 33 | .filter(|&i| grid[i][col] == 'v' && grid[(i + 1) % height][col] == '.') 34 | .collect(); 35 | if !indices.is_empty() { 36 | b = true; 37 | } 38 | for i in indices { 39 | grid[i][col] = '.'; 40 | grid[(i + 1) % height][col] = 'v'; 41 | } 42 | } 43 | if !a && !b { 44 | return Some(n); 45 | } 46 | n += 1; 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | use pretty_assertions::assert_eq; 54 | use std::error::Error; 55 | 56 | static EXAMPLE: &[&str] = &[ 57 | "v...>>.vv>", 58 | ".vv>>.vv..", 59 | ">>.>v>...v", 60 | ">>v>>.>.v.", 61 | "v>v.vv.v..", 62 | ">.>>..v...", 63 | ".vv..>.>v.", 64 | "v.v..>>v.v", 65 | "....v..v.>", 66 | ]; 67 | 68 | #[test] 69 | fn part1_examples() -> Result<(), Box> { 70 | assert_eq!(Some(58), part1(EXAMPLE)); 71 | Ok(()) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rs/src/day6.rs: -------------------------------------------------------------------------------- 1 | use super::util; 2 | use std::error::Error; 3 | 4 | build_const!("libaoc2021_day6"); 5 | 6 | fn solve<'a, I, S, const N: usize>( 7 | lut: &[usize; N], 8 | lines: I, 9 | ) -> Result> 10 | where 11 | I: IntoIterator, 12 | S: AsRef + 'a, 13 | { 14 | let mut ret = 0; 15 | for line in lines { 16 | for word in line.as_ref().split(',') { 17 | ret += lut.get(word.parse::()?).ok_or(util::Error)?; 18 | } 19 | } 20 | Ok(ret) 21 | } 22 | 23 | pub fn part1<'a, I, S>(lines: I) -> Result> 24 | where 25 | I: IntoIterator, 26 | S: AsRef + 'a, 27 | { 28 | solve(&LUT_80, lines) 29 | } 30 | 31 | pub fn part2<'a, I, S>(lines: I) -> Result> 32 | where 33 | I: IntoIterator, 34 | S: AsRef + 'a, 35 | { 36 | solve(&LUT_256, lines) 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | use pretty_assertions::assert_eq; 43 | 44 | static EXAMPLE: &[&str] = &["3,4,3,1,2"]; 45 | 46 | #[test] 47 | fn part1_examples() -> Result<(), Box> { 48 | assert_eq!(5934, part1(EXAMPLE)?); 49 | Ok(()) 50 | } 51 | 52 | #[test] 53 | fn part2_examples() -> Result<(), Box> { 54 | assert_eq!(26984457539, part2(EXAMPLE)?); 55 | Ok(()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /rs/src/day7.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::min; 2 | use std::error::Error; 3 | use std::vec::Vec; 4 | 5 | pub fn part1<'a, I, S>(lines: I) -> Result> 6 | where 7 | I: IntoIterator, 8 | S: AsRef + 'a, 9 | { 10 | let mut nums = lines 11 | .into_iter() 12 | .flat_map(|line| line.as_ref().split(',')) 13 | .map(|s| s.parse()) 14 | .collect::, _>>()?; 15 | nums.sort_unstable(); 16 | let median = nums[nums.len() / 2]; 17 | Ok(nums.into_iter().map(|num| (num - median).abs()).sum()) 18 | } 19 | 20 | pub fn part2<'a, I, S>(lines: I) -> Result> 21 | where 22 | I: IntoIterator, 23 | S: AsRef + 'a, 24 | { 25 | let nums = lines 26 | .into_iter() 27 | .flat_map(|line| line.as_ref().split(',')) 28 | .map(|s| s.parse()) 29 | .collect::, _>>()?; 30 | let mean = nums.iter().sum::() / i32::try_from(nums.len())?; 31 | let f = |y: i32| -> i32 { 32 | nums.iter() 33 | .map(|x| { 34 | let z = (x - y).abs(); 35 | z * (z + 1) / 2 36 | }) 37 | .sum() 38 | }; 39 | Ok(min(f(mean), f(mean + 1))) 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | use pretty_assertions::assert_eq; 46 | 47 | static EXAMPLE: &[&str] = &["16,1,2,0,4,2,7,1,2,14"]; 48 | 49 | #[test] 50 | fn part1_examples() -> Result<(), Box> { 51 | assert_eq!(37, part1(EXAMPLE)?); 52 | Ok(()) 53 | } 54 | 55 | #[test] 56 | fn part2_examples() -> Result<(), Box> { 57 | assert_eq!(168, part2(EXAMPLE)?); 58 | Ok(()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate build_const; 3 | #[macro_use] 4 | extern crate lazy_static; 5 | 6 | pub mod day1; 7 | pub mod day10; 8 | pub mod day11; 9 | pub mod day12; 10 | pub mod day13; 11 | pub mod day14; 12 | pub mod day15; 13 | pub mod day16; 14 | pub mod day17; 15 | pub mod day18; 16 | pub mod day19; 17 | pub mod day2; 18 | pub mod day20; 19 | pub mod day21; 20 | pub mod day22; 21 | pub mod day23; 22 | pub mod day24; 23 | pub mod day25; 24 | pub mod day3; 25 | pub mod day4; 26 | pub mod day5; 27 | pub mod day6; 28 | pub mod day7; 29 | pub mod day8; 30 | pub mod day9; 31 | pub mod util; 32 | --------------------------------------------------------------------------------