diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..fee62297 --- /dev/null +++ b/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: Google +IndentWidth: 4 +ColumnLimit: 80 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..76cd9a41 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.sh] +indent_style = space +indent_size = 4 +space_redirects = true diff --git a/.github/workflows/action-aoc-badges-2023.yml b/.github/workflows/action-aoc-badges-2024.yml similarity index 97% rename from .github/workflows/action-aoc-badges-2023.yml rename to .github/workflows/action-aoc-badges-2024.yml index 01a76867..9730c79a 100644 --- a/.github/workflows/action-aoc-badges-2023.yml +++ b/.github/workflows/action-aoc-badges-2024.yml @@ -1,4 +1,4 @@ -name: Update AoC Badges 2023 +name: Update AoC Badges 2024 on: schedule: # run workflow based on schedule - cron: '6 5 1-25 12 *' # from the 1. December till 25. December every day at 5:06am (avoid load at full hours) @@ -18,7 +18,7 @@ jobs: with: userid: ${{ secrets.AOC_USER_ID }} # your user id, see setup on how to obtain session: ${{ secrets.AOC_SESSION }} # secret containing session code, see setup on how to obtain - year: 2023 + year: 2024 # Optional inputs: # diff --git a/.github/workflows/action-aoc-badges.yml b/.github/workflows/action-aoc-badges.yml index ab4445a0..5d3cdae3 100644 --- a/.github/workflows/action-aoc-badges.yml +++ b/.github/workflows/action-aoc-badges.yml @@ -7,6 +7,14 @@ jobs: steps: - uses: actions/checkout@v2 # clones your repo + - uses: joblo2213/aoc-badges-action@v3 + with: + userid: ${{ secrets.AOC_USER_ID }} # your user id, see setup on how to obtain + session: ${{ secrets.AOC_SESSION }} # secret containing session code, see setup on how to obtain + year: 2023 + starsRegex: '(?<=https:\/\/img\.shields\.io\/badge\/2023%20stars%20⭐-)[0-9]+(?=-yellow)' # Regular expression that finds the content of the stars badge in your file. + daysCompletedRegex: '(?<=https:\/\/img\.shields\.io\/badge\/2023%20days%20completed-)[0-9]+(?=-red)' # Regular expression that finds the content of the days completed badge iun your file. + - uses: joblo2213/aoc-badges-action@v3 with: userid: ${{ secrets.AOC_USER_ID }} # your user id, see setup on how to obtain @@ -14,7 +22,6 @@ jobs: year: 2022 starsRegex: '(?<=https:\/\/img\.shields\.io\/badge\/2022%20stars%20⭐-)[0-9]+(?=-yellow)' # Regular expression that finds the content of the stars badge in your file. daysCompletedRegex: '(?<=https:\/\/img\.shields\.io\/badge\/2022%20days%20completed-)[0-9]+(?=-red)' # Regular expression that finds the content of the days completed badge iun your file. - - uses: actions/checkout@v2 # clones your repo - uses: joblo2213/aoc-badges-action@v3 with: diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 00000000..df99c691 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +max_width = 80 diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 00000000..f722d650 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,4 @@ +external-sources=true +check-sourced=true +source-path=SCRIPTDIR +disable=SC2317 diff --git a/Makefile b/Makefile index 3aeba344..67579acc 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ JAVA_CMD := $(JAVA_EXE) -ea JAVAC_CMD := $(JAVAC_EXE) -encoding utf-8 -proc:full JAVA_UNITTEST_CMD := org.junit.platform.console.ConsoleLauncher JULIA_CMD := julia --optimize -CARGO_CMD := cargo +CARGO_CMD := RUSTFLAGS='-C target-cpu=native' cargo RUSTFMT := rustfmt BAZEL := bazel WSLPATH := wslpath diff --git a/README.md b/README.md index 01ee08db..d6e9c420 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,33 @@ [Advent of Code](https://adventofcode.com) +## 2024 + +![](https://img.shields.io/badge/stars%20⭐-50-yellow) +![](https://img.shields.io/badge/days%20completed-25-red) + +![2024 Calendar](doc/aoc2024.jpg "2024 Calendar") + + +| | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | +| ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| python3 | [✓](src/main/python/AoC2024_01.py) | [✓](src/main/python/AoC2024_02.py) | [✓](src/main/python/AoC2024_03.py) | [✓](src/main/python/AoC2024_04.py) | [✓](src/main/python/AoC2024_05.py) | [✓](src/main/python/AoC2024_06.py) | [✓](src/main/python/AoC2024_07.py) | [✓](src/main/python/AoC2024_08.py) | [✓](src/main/python/AoC2024_09.py) | [✓](src/main/python/AoC2024_10.py) | [✓](src/main/python/AoC2024_11.py) | [✓](src/main/python/AoC2024_12.py) | [✓](src/main/python/AoC2024_13.py) | [✓](src/main/python/AoC2024_14.py) | [✓](src/main/python/AoC2024_15.py) | [✓](src/main/python/AoC2024_16.py) | [✓](src/main/python/AoC2024_17.py) | [✓](src/main/python/AoC2024_18.py) | [✓](src/main/python/AoC2024_19.py) | [✓](src/main/python/AoC2024_20.py) | [✓](src/main/python/AoC2024_21.py) | [✓](src/main/python/AoC2024_22.py) | [✓](src/main/python/AoC2024_23.py) | [✓](src/main/python/AoC2024_24.py) | [✓](src/main/python/AoC2024_25.py) | +| java | [✓](src/main/java/AoC2024_01.java) | [✓](src/main/java/AoC2024_02.java) | [✓](src/main/java/AoC2024_03.java) | [✓](src/main/java/AoC2024_04.java) | [✓](src/main/java/AoC2024_05.java) | [✓](src/main/java/AoC2024_06.java) | [✓](src/main/java/AoC2024_07.java) | [✓](src/main/java/AoC2024_08.java) | [✓](src/main/java/AoC2024_09.java) | [✓](src/main/java/AoC2024_10.java) | [✓](src/main/java/AoC2024_11.java) | [✓](src/main/java/AoC2024_12.java) | [✓](src/main/java/AoC2024_13.java) | [✓](src/main/java/AoC2024_14.java) | [✓](src/main/java/AoC2024_15.java) | [✓](src/main/java/AoC2024_16.java) | | | | | [✓](src/main/java/AoC2024_21.java) | | | [✓](src/main/java/AoC2024_24.java) | | +| rust | [✓](src/main/rust/AoC2024_01/src/main.rs) | [✓](src/main/rust/AoC2024_02/src/main.rs) | [✓](src/main/rust/AoC2024_03/src/main.rs) | [✓](src/main/rust/AoC2024_04/src/main.rs) | [✓](src/main/rust/AoC2024_05/src/main.rs) | [✓](src/main/rust/AoC2024_06/src/main.rs) | [✓](src/main/rust/AoC2024_07/src/main.rs) | [✓](src/main/rust/AoC2024_08/src/main.rs) | [✓](src/main/rust/AoC2024_09/src/main.rs) | [✓](src/main/rust/AoC2024_10/src/main.rs) | [✓](src/main/rust/AoC2024_11/src/main.rs) | [✓](src/main/rust/AoC2024_12/src/main.rs) | [✓](src/main/rust/AoC2024_13/src/main.rs) | [✓](src/main/rust/AoC2024_14/src/main.rs) | [✓](src/main/rust/AoC2024_15/src/main.rs) | [✓](src/main/rust/AoC2024_16/src/main.rs) | [✓](src/main/rust/AoC2024_17/src/main.rs) | [✓](src/main/rust/AoC2024_18/src/main.rs) | [✓](src/main/rust/AoC2024_19/src/main.rs) | [✓](src/main/rust/AoC2024_20/src/main.rs) | | [✓](src/main/rust/AoC2024_22/src/main.rs) | [✓](src/main/rust/AoC2024_23/src/main.rs) | [✓](src/main/rust/AoC2024_24/src/main.rs) | [✓](src/main/rust/AoC2024_25/src/main.rs) | + + ## 2023 -![](https://img.shields.io/badge/stars%20⭐-26-yellow) -![](https://img.shields.io/badge/days%20completed-13-red) +![](https://img.shields.io/badge/2023%20stars%20⭐-50-yellow) +![](https://img.shields.io/badge/2023%20days%20completed-25-red) + +![2023 Calendar](doc/aoc2023.jpg "2023 Calendar") | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| python3 | [✓](src/main/python/AoC2023_01.py) | [✓](src/main/python/AoC2023_02.py) | [✓](src/main/python/AoC2023_03.py) | [✓](src/main/python/AoC2023_04.py) | [✓](src/main/python/AoC2023_05.py) | [✓](src/main/python/AoC2023_06.py) | [✓](src/main/python/AoC2023_07.py) | [✓](src/main/python/AoC2023_08.py) | [✓](src/main/python/AoC2023_09.py) | [✓](src/main/python/AoC2023_10.py) | [✓](src/main/python/AoC2023_11.py) | [✓](src/main/python/AoC2023_12.py) | [✓](src/main/python/AoC2023_13.py) | | | | | | | | | | | | | -| java | [✓](src/main/java/AoC2023_01.java) | [✓](src/main/java/AoC2023_02.java) | [✓](src/main/java/AoC2023_03.java) | [✓](src/main/java/AoC2023_04.java) | [✓](src/main/java/AoC2023_05.java) | [✓](src/main/java/AoC2023_06.java) | [✓](src/main/java/AoC2023_07.java) | [✓](src/main/java/AoC2023_08.java) | [✓](src/main/java/AoC2023_09.java) | [✓](src/main/java/AoC2023_10.java) | [✓](src/main/java/AoC2023_11.java) | [✓](src/main/java/AoC2023_12.java) | [✓](src/main/java/AoC2023_13.java) | | | | | | | | | | | | | -| bash | | | | | | | | | | | | | | | | | | | | | | | | | | -| c++ | | | | | | | | | | | | | | | | | | | | | | | | | | -| julia | | | | | | | | | | | | | | | | | | | | | | | | | | -| rust | [✓](src/main/rust/AoC2023_01/src/main.rs) | [✓](src/main/rust/AoC2023_02/src/main.rs) | [✓](src/main/rust/AoC2023_03/src/main.rs) | [✓](src/main/rust/AoC2023_04/src/main.rs) | | [✓](src/main/rust/AoC2023_06/src/main.rs) | | [✓](src/main/rust/AoC2023_08/src/main.rs) | [✓](src/main/rust/AoC2023_09/src/main.rs) | | | | | | | | | | | | | | | | | +| python3 | [✓](src/main/python/AoC2023_01.py) | [✓](src/main/python/AoC2023_02.py) | [✓](src/main/python/AoC2023_03.py) | [✓](src/main/python/AoC2023_04.py) | [✓](src/main/python/AoC2023_05.py) | [✓](src/main/python/AoC2023_06.py) | [✓](src/main/python/AoC2023_07.py) | [✓](src/main/python/AoC2023_08.py) | [✓](src/main/python/AoC2023_09.py) | [✓](src/main/python/AoC2023_10.py) | [✓](src/main/python/AoC2023_11.py) | [✓](src/main/python/AoC2023_12.py) | [✓](src/main/python/AoC2023_13.py) | [✓](src/main/python/AoC2023_14.py) | [✓](src/main/python/AoC2023_15.py) | [✓](src/main/python/AoC2023_16.py) | [✓](src/main/python/AoC2023_17.py) | [✓](src/main/python/AoC2023_18.py) | [✓](src/main/python/AoC2023_19.py) | [✓](src/main/python/AoC2023_20.py) | [✓](src/main/python/AoC2023_21.py) | [✓](src/main/python/AoC2023_22.py) | [✓](src/main/python/AoC2023_23.py) | [✓](src/main/python/AoC2023_24.py) | [✓](src/main/python/AoC2023_25.py) | +| java | [✓](src/main/java/AoC2023_01.java) | [✓](src/main/java/AoC2023_02.java) | [✓](src/main/java/AoC2023_03.java) | [✓](src/main/java/AoC2023_04.java) | [✓](src/main/java/AoC2023_05.java) | [✓](src/main/java/AoC2023_06.java) | [✓](src/main/java/AoC2023_07.java) | [✓](src/main/java/AoC2023_08.java) | [✓](src/main/java/AoC2023_09.java) | [✓](src/main/java/AoC2023_10.java) | [✓](src/main/java/AoC2023_11.java) | [✓](src/main/java/AoC2023_12.java) | [✓](src/main/java/AoC2023_13.java) | [✓](src/main/java/AoC2023_14.java) | [✓](src/main/java/AoC2023_15.java) | [✓](src/main/java/AoC2023_16.java) | [✓](src/main/java/AoC2023_17.java) | [✓](src/main/java/AoC2023_18.java) | [✓](src/main/java/AoC2023_19.java) | [✓](src/main/java/AoC2023_20.java) | [✓](src/main/java/AoC2023_21.java) | [✓](src/main/java/AoC2023_22.java) | [✓](src/main/java/AoC2023_23.java) | | [✓](src/main/java/AoC2023_25.java) | +| rust | [✓](src/main/rust/AoC2023_01/src/main.rs) | [✓](src/main/rust/AoC2023_02/src/main.rs) | [✓](src/main/rust/AoC2023_03/src/main.rs) | [✓](src/main/rust/AoC2023_04/src/main.rs) | | [✓](src/main/rust/AoC2023_06/src/main.rs) | [✓](src/main/rust/AoC2023_07/src/main.rs) | [✓](src/main/rust/AoC2023_08/src/main.rs) | [✓](src/main/rust/AoC2023_09/src/main.rs) | [✓](src/main/rust/AoC2023_10/src/main.rs) | [✓](src/main/rust/AoC2023_11/src/main.rs) | | | | [✓](src/main/rust/AoC2023_15/src/main.rs) | [✓](src/main/rust/AoC2023_16/src/main.rs) | [✓](src/main/rust/AoC2023_17/src/main.rs) | [✓](src/main/rust/AoC2023_18/src/main.rs) | | | [✓](src/main/rust/AoC2023_21/src/main.rs) | | [✓](src/main/rust/AoC2023_23/src/main.rs) | | | ## 2022 @@ -26,12 +40,12 @@ | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| python3 | [✓](src/main/python/AoC2022_01.py) | [✓](src/main/python/AoC2022_02.py) | [✓](src/main/python/AoC2022_03.py) | [✓](src/main/python/AoC2022_04.py) | [✓](src/main/python/AoC2022_05.py) | [✓](src/main/python/AoC2022_06.py) | [✓](src/main/python/AoC2022_07.py) | [✓](src/main/python/AoC2022_08.py) | [✓](src/main/python/AoC2022_09.py) | [✓](src/main/python/AoC2022_10.py) | [✓](src/main/python/AoC2022_11.py) | [✓](src/main/python/AoC2022_12.py) | [✓](src/main/python/AoC2022_13.py) | [✓](src/main/python/AoC2022_14.py) | [✓](src/main/python/AoC2022_15.py) | [✓](src/main/python/AoC2022_16.py) | | [✓](src/main/python/AoC2022_18.py) | | [✓](src/main/python/AoC2022_20.py) | | [✓](src/main/python/AoC2022_22.py) | [✓](src/main/python/AoC2022_23.py) | | [✓](src/main/python/AoC2022_25.py) | +| python3 | [✓](src/main/python/AoC2022_01.py) | [✓](src/main/python/AoC2022_02.py) | [✓](src/main/python/AoC2022_03.py) | [✓](src/main/python/AoC2022_04.py) | [✓](src/main/python/AoC2022_05.py) | [✓](src/main/python/AoC2022_06.py) | [✓](src/main/python/AoC2022_07.py) | [✓](src/main/python/AoC2022_08.py) | [✓](src/main/python/AoC2022_09.py) | [✓](src/main/python/AoC2022_10.py) | [✓](src/main/python/AoC2022_11.py) | [✓](src/main/python/AoC2022_12.py) | [✓](src/main/python/AoC2022_13.py) | [✓](src/main/python/AoC2022_14.py) | [✓](src/main/python/AoC2022_15.py) | [✓](src/main/python/AoC2022_16.py) | [✓](src/main/python/AoC2022_17.py) | [✓](src/main/python/AoC2022_18.py) | | [✓](src/main/python/AoC2022_20.py) | | [✓](src/main/python/AoC2022_22.py) | [✓](src/main/python/AoC2022_23.py) | | [✓](src/main/python/AoC2022_25.py) | | java | [✓](src/main/java/AoC2022_01.java) | [✓](src/main/java/AoC2022_02.java) | [✓](src/main/java/AoC2022_03.java) | [✓](src/main/java/AoC2022_04.java) | [✓](src/main/java/AoC2022_05.java) | [✓](src/main/java/AoC2022_06.java) | [✓](src/main/java/AoC2022_07.java) | [✓](src/main/java/AoC2022_08.java) | [✓](src/main/java/AoC2022_09.java) | [✓](src/main/java/AoC2022_10.java) | [✓](src/main/java/AoC2022_11.java) | [✓](src/main/java/AoC2022_12.java) | [✓](src/main/java/AoC2022_13.java) | [✓](src/main/java/AoC2022_14.java) | [✓](src/main/java/AoC2022_15.java) | [✓](src/main/java/AoC2022_16.java) | [✓](src/main/java/AoC2022_17.java) | [✓](src/main/java/AoC2022_18.java) | [✓](src/main/java/AoC2022_19.java) | [✓](src/main/java/AoC2022_20.java) | [✓](src/main/java/AoC2022_21.java) | [✓](src/main/java/AoC2022_22.java) | [✓](src/main/java/AoC2022_23.java) | [✓](src/main/java/AoC2022_24.java) | [✓](src/main/java/AoC2022_25.java) | | bash | [✓](src/main/bash/AoC2022_01.sh) | [✓](src/main/bash/AoC2022_02.sh) | [✓](src/main/bash/AoC2022_03.sh) | [✓](src/main/bash/AoC2022_04.sh) | | [✓](src/main/bash/AoC2022_06.sh) | [✓](src/main/bash/AoC2022_07.sh) | | | [✓](src/main/bash/AoC2022_10.sh) | | | | | | | | | | | | | | | [✓](src/main/bash/AoC2022_25.sh) | | c++ | [✓](src/main/cpp/2022/01/AoC2022_01.cpp) | [✓](src/main/cpp/2022/02/AoC2022_02.cpp) | [✓](src/main/cpp/2022/03/AoC2022_03.cpp) | [✓](src/main/cpp/2022/04/AoC2022_04.cpp) | [✓](src/main/cpp/2022/05/AoC2022_05.cpp) | [✓](src/main/cpp/2022/06/AoC2022_06.cpp) | | | [✓](src/main/cpp/2022/09/AoC2022_09.cpp) | [✓](src/main/cpp/2022/10/AoC2022_10.cpp) | | | | [✓](src/main/cpp/2022/14/AoC2022_14.cpp) | | | | | | | | | [✓](src/main/cpp/2022/23/AoC2022_23.cpp) | [✓](src/main/cpp/2022/24/AoC2022_24.cpp) | [✓](src/main/cpp/2022/25/AoC2022_25.cpp) | | julia | [✓](src/main/julia/AoC2022_01.jl) | [✓](src/main/julia/AoC2022_02.jl) | [✓](src/main/julia/AoC2022_03.jl) | [✓](src/main/julia/AoC2022_04.jl) | | [✓](src/main/julia/AoC2022_06.jl) | | | | [✓](src/main/julia/AoC2022_10.jl) | [✓](src/main/julia/AoC2022_11.jl) | | | | | | | | | | | | | | | -| rust | [✓](src/main/rust/AoC2022_01/src/main.rs) | [✓](src/main/rust/AoC2022_02/src/main.rs) | [✓](src/main/rust/AoC2022_03/src/main.rs) | [✓](src/main/rust/AoC2022_04/src/main.rs) | [✓](src/main/rust/AoC2022_05/src/main.rs) | [✓](src/main/rust/AoC2022_06/src/main.rs) | [✓](src/main/rust/AoC2022_07/src/main.rs) | [✓](src/main/rust/AoC2022_08/src/main.rs) | [✓](src/main/rust/AoC2022_09/src/main.rs) | [✓](src/main/rust/AoC2022_10/src/main.rs) | [✓](src/main/rust/AoC2022_11/src/main.rs) | [✓](src/main/rust/AoC2022_12/src/main.rs) | [✓](src/main/rust/AoC2022_13/src/main.rs) | [✓](src/main/rust/AoC2022_14/src/main.rs) | | [✓](src/main/rust/AoC2022_16/src/main.rs) | | [✓](src/main/rust/AoC2022_18/src/main.rs) | [✓](src/main/rust/AoC2022_19/src/main.rs) | [✓](src/main/rust/AoC2022_20/src/main.rs) | | | | | [✓](src/main/rust/AoC2022_25/src/main.rs) | +| rust | [✓](src/main/rust/AoC2022_01/src/main.rs) | [✓](src/main/rust/AoC2022_02/src/main.rs) | [✓](src/main/rust/AoC2022_03/src/main.rs) | [✓](src/main/rust/AoC2022_04/src/main.rs) | [✓](src/main/rust/AoC2022_05/src/main.rs) | [✓](src/main/rust/AoC2022_06/src/main.rs) | [✓](src/main/rust/AoC2022_07/src/main.rs) | [✓](src/main/rust/AoC2022_08/src/main.rs) | [✓](src/main/rust/AoC2022_09/src/main.rs) | [✓](src/main/rust/AoC2022_10/src/main.rs) | [✓](src/main/rust/AoC2022_11/src/main.rs) | [✓](src/main/rust/AoC2022_12/src/main.rs) | [✓](src/main/rust/AoC2022_13/src/main.rs) | [✓](src/main/rust/AoC2022_14/src/main.rs) | [✓](src/main/rust/AoC2022_15/src/main.rs) | [✓](src/main/rust/AoC2022_16/src/main.rs) | [✓](src/main/rust/AoC2022_17/src/main.rs) | [✓](src/main/rust/AoC2022_18/src/main.rs) | [✓](src/main/rust/AoC2022_19/src/main.rs) | [✓](src/main/rust/AoC2022_20/src/main.rs) | | | | | [✓](src/main/rust/AoC2022_25/src/main.rs) | ## 2021 @@ -49,7 +63,6 @@ | bash | [✓](src/main/bash/AoC2021_01.sh) | [✓](src/main/bash/AoC2021_02.sh) | | | | | | | | | | | [✓](src/main/bash/AoC2021_13.sh) | | | | | | | | | | | | | | c++ | [✓](src/main/cpp/2021/01/AoC2021_01.cpp) | [✓](src/main/cpp/2021/02/AoC2021_02.cpp) | [✓](src/main/cpp/2021/03/AoC2021_03.cpp) | [✓](src/main/cpp/2021/04/AoC2021_04.cpp) | [✓](src/main/cpp/2021/05/AoC2021_05.cpp) | [✓](src/main/cpp/2021/06/AoC2021_06.cpp) | [✓](src/main/cpp/2021/07/AoC2021_07.cpp) | | [✓](src/main/cpp/2021/09/AoC2021_09.cpp) | | [✓](src/main/cpp/2021/11/AoC2021_11.cpp) | | [✓](src/main/cpp/2021/13/AoC2021_13.cpp) | [✓](src/main/cpp/2021/14/AoC2021_14.cpp) | [✓](src/main/cpp/2021/15/AoC2021_15.cpp) | | | | | | | | | | | | julia | [✓](src/main/julia/AoC2021_01.jl) | [✓](src/main/julia/AoC2021_02.jl) | [✓](src/main/julia/AoC2021_03.jl) | [✓](src/main/julia/AoC2021_04.jl) | [✓](src/main/julia/AoC2021_05.jl) | [✓](src/main/julia/AoC2021_06.jl) | [✓](src/main/julia/AoC2021_07.jl) | [✓](src/main/julia/AoC2021_08.jl) | [✓](src/main/julia/AoC2021_09.jl) | [✓](src/main/julia/AoC2021_10.jl) | [✓](src/main/julia/AoC2021_11.jl) | | | | | | | | | | | | | | | -| rust | | | | | | | | | | | | | | | | | | | | | | | | | | ## 2020 @@ -63,26 +76,24 @@ | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | python3 | [✓](src/main/python/AoC2020_01.py) | [✓](src/main/python/AoC2020_02.py) | [✓](src/main/python/AoC2020_03.py) | [✓](src/main/python/AoC2020_04.py) | [✓](src/main/python/AoC2020_05.py) | [✓](src/main/python/AoC2020_06.py) | [✓](src/main/python/AoC2020_07.py) | [✓](src/main/python/AoC2020_08.py) | [✓](src/main/python/AoC2020_09.py) | [✓](src/main/python/AoC2020_10.py) | [✓](src/main/python/AoC2020_11.py) | [✓](src/main/python/AoC2020_12.py) | [✓](src/main/python/AoC2020_13.py) | [✓](src/main/python/AoC2020_14.py) | [✓](src/main/python/AoC2020_15.py) | [✓](src/main/python/AoC2020_16.py) | [✓](src/main/python/AoC2020_17.py) | [✓](src/main/python/AoC2020_18.py) | [✓](src/main/python/AoC2020_19.py) | [✓](src/main/python/AoC2020_20.py) | [✓](src/main/python/AoC2020_21.py) | [✓](src/main/python/AoC2020_22.py) | [✓](src/main/python/AoC2020_23.py) | [✓](src/main/python/AoC2020_24.py) | [✓](src/main/python/AoC2020_25.py) | -| java | [✓](src/main/java/AoC2020_01.java) | [✓](src/main/java/AoC2020_02.java) | [✓](src/main/java/AoC2020_03.java) | [✓](src/main/java/AoC2020_04.java) | [✓](src/main/java/AoC2020_05.java) | [✓](src/main/java/AoC2020_06.java) | [✓](src/main/java/AoC2020_07.java) | [✓](src/main/java/AoC2020_08.java) | [✓](src/main/java/AoC2020_09.java) | [✓](src/main/java/AoC2020_10.java) | [✓](src/main/java/AoC2020_11.java) | [✓](src/main/java/AoC2020_12.java) | [✓](src/main/java/AoC2020_13.java) | [✓](src/main/java/AoC2020_14.java) | [✓](src/main/java/AoC2020_15.java) | [✓](src/main/java/AoC2020_16.java) | [✓](src/main/java/AoC2020_17.java) | [✓](src/main/java/AoC2020_18.java) | | [✓](src/main/java/AoC2020_20.java) | | [✓](src/main/java/AoC2020_22.java) | [✓](src/main/java/AoC2020_23.java) | [✓](src/main/java/AoC2020_24.java) | [✓](src/main/java/AoC2020_25.java) | -| bash | | | | | | | | | | | | | | | | | | | | | | | | | | +| java | [✓](src/main/java/AoC2020_01.java) | [✓](src/main/java/AoC2020_02.java) | [✓](src/main/java/AoC2020_03.java) | [✓](src/main/java/AoC2020_04.java) | [✓](src/main/java/AoC2020_05.java) | [✓](src/main/java/AoC2020_06.java) | [✓](src/main/java/AoC2020_07.java) | [✓](src/main/java/AoC2020_08.java) | [✓](src/main/java/AoC2020_09.java) | [✓](src/main/java/AoC2020_10.java) | [✓](src/main/java/AoC2020_11.java) | [✓](src/main/java/AoC2020_12.java) | [✓](src/main/java/AoC2020_13.java) | [✓](src/main/java/AoC2020_14.java) | [✓](src/main/java/AoC2020_15.java) | [✓](src/main/java/AoC2020_16.java) | [✓](src/main/java/AoC2020_17.java) | [✓](src/main/java/AoC2020_18.java) | [✓](src/main/java/AoC2020_19.java) | [✓](src/main/java/AoC2020_20.java) | | [✓](src/main/java/AoC2020_22.java) | [✓](src/main/java/AoC2020_23.java) | [✓](src/main/java/AoC2020_24.java) | [✓](src/main/java/AoC2020_25.java) | | c++ | | | | | | | | | | | | | | | | | [✓](src/main/cpp/2020/17/AoC2020_17.cpp) | | | | | | | | | | julia | | | | | [✓](src/main/julia/AoC2020_05.jl) | | | | | | | | | | | [✓](src/main/julia/AoC2020_16.jl) | [✓](src/main/julia/AoC2020_17.jl) | | | | | | | | | -| rust | | | | | | | | | | | | | | | | | | | | | | | | | | +| rust | | | | | | | | | | | | | | | | | | | | | | | [✓](src/main/rust/AoC2020_23/src/main.rs) | | | ## 2019 -![](https://img.shields.io/badge/2019%20stars%20⭐-30-yellow) -![](https://img.shields.io/badge/2019%20days%20completed-15-red) +![](https://img.shields.io/badge/2019%20stars%20⭐-34-yellow) +![](https://img.shields.io/badge/2019%20days%20completed-17-red) | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| python3 | [✓](src/main/python/AoC2019_01.py) | [✓](src/main/python/AoC2019_02.py) | [✓](src/main/python/AoC2019_03.py) | [✓](src/main/python/AoC2019_04.py) | [✓](src/main/python/AoC2019_05.py) | [✓](src/main/python/AoC2019_06.py) | [✓](src/main/python/AoC2019_07.py) | [✓](src/main/python/AoC2019_08.py) | [✓](src/main/python/AoC2019_09.py) | | [✓](src/main/python/AoC2019_11.py) | | [✓](src/main/python/AoC2019_13.py) | | | [✓](src/main/python/AoC2019_16.py) | | | | | | | | | | -| java | [✓](src/main/java/AoC2019_01.java) | [✓](src/main/java/AoC2019_02.java) | [✓](src/main/java/AoC2019_03.java) | [✓](src/main/java/AoC2019_04.java) | [✓](src/main/java/AoC2019_05.java) | [✓](src/main/java/AoC2019_06.java) | [✓](src/main/java/AoC2019_07.java) | [✓](src/main/java/AoC2019_08.java) | [✓](src/main/java/AoC2019_09.java) | [✓](src/main/java/AoC2019_10.java) | [✓](src/main/java/AoC2019_11.java) | [✓](src/main/java/AoC2019_12.java) | [✓](src/main/java/AoC2019_13.java) | | [✓](src/main/java/AoC2019_15.java) | [✓](src/main/java/AoC2019_16.java) | [✓](src/main/java/AoC2019_17.java) | | | | | | | | | +| python3 | [✓](src/main/python/AoC2019_01.py) | [✓](src/main/python/AoC2019_02.py) | [✓](src/main/python/AoC2019_03.py) | [✓](src/main/python/AoC2019_04.py) | [✓](src/main/python/AoC2019_05.py) | [✓](src/main/python/AoC2019_06.py) | [✓](src/main/python/AoC2019_07.py) | [✓](src/main/python/AoC2019_08.py) | [✓](src/main/python/AoC2019_09.py) | [✓](src/main/python/AoC2019_10.py) | [✓](src/main/python/AoC2019_11.py) | | [✓](src/main/python/AoC2019_13.py) | [✓](src/main/python/AoC2019_14.py) | | [✓](src/main/python/AoC2019_16.py) | [✓](src/main/python/AoC2019_17.py) | | [✓](src/main/python/AoC2019_19.py) | | | | | | | +| java | [✓](src/main/java/AoC2019_01.java) | [✓](src/main/java/AoC2019_02.java) | [✓](src/main/java/AoC2019_03.java) | [✓](src/main/java/AoC2019_04.java) | [✓](src/main/java/AoC2019_05.java) | [✓](src/main/java/AoC2019_06.java) | [✓](src/main/java/AoC2019_07.java) | [✓](src/main/java/AoC2019_08.java) | [✓](src/main/java/AoC2019_09.java) | [✓](src/main/java/AoC2019_10.java) | [✓](src/main/java/AoC2019_11.java) | [✓](src/main/java/AoC2019_12.java) | [✓](src/main/java/AoC2019_13.java) | [✓](src/main/java/AoC2019_14.java) | [✓](src/main/java/AoC2019_15.java) | [✓](src/main/java/AoC2019_16.java) | [✓](src/main/java/AoC2019_17.java) | | [✓](src/main/java/AoC2019_19.java) | | | | | | | | bash | | | | | | | | [✓](src/main/bash/AoC2019_08.sh) | | | | | | | | | | | | | | | | | | | c++ | | | | | | | | [✓](src/main/cpp/2019/08/AoC2019_08.cpp) | | | | | | | | | | | | | | | | | | -| julia | | | | | | | | | | | | | | | | | | | | | | | | | | | rust | [✓](src/main/rust/AoC2019_01/src/main.rs) | | | | | | | [✓](src/main/rust/AoC2019_08/src/main.rs) | | | | | | | | [✓](src/main/rust/AoC2019_16/src/main.rs) | | | | | | | | | | @@ -94,12 +105,6 @@ | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| python3 | | | | | | | | | | | | | | | | | | | | | | | | | | -| java | | | | | | | | | | | | | | | | | | | | | | | | | | -| bash | | | | | | | | | | | | | | | | | | | | | | | | | | -| c++ | | | | | | | | | | | | | | | | | | | | | | | | | | -| julia | | | | | | | | | | | | | | | | | | | | | | | | | | -| rust | | | | | | | | | | | | | | | | | | | | | | | | | | ## 2017 @@ -117,7 +122,7 @@ | bash | [✓](src/main/bash/AoC2017_01.sh) | [✓](src/main/bash/AoC2017_02.sh) | | [✓](src/main/bash/AoC2017_04.sh) | [✓](src/main/bash/AoC2017_05.sh) | | | | | | | | | | | | | | | | | | | | | | c++ | [✓](src/main/cpp/2017/01/AoC2017_01.cpp) | [✓](src/main/cpp/2017/02/AoC2017_02.cpp) | [✓](src/main/cpp/2017/03/AoC2017_03.cpp) | [✓](src/main/cpp/2017/04/AoC2017_04.cpp) | [✓](src/main/cpp/2017/05/AoC2017_05.cpp) | | | | [✓](src/main/cpp/2017/09/AoC2017_09.cpp) | | [✓](src/main/cpp/2017/11/AoC2017_11.cpp) | | [✓](src/main/cpp/2017/13/AoC2017_13.cpp) | | | | | [✓](src/main/cpp/2017/18/AoC2017_18.cpp) | | | [✓](src/main/cpp/2017/21/AoC2017_21.cpp) | | | | [✓](src/main/cpp/2017/25/AoC2017_25.cpp) | | julia | [✓](src/main/julia/AoC2017_01.jl) | [✓](src/main/julia/AoC2017_02.jl) | [✓](src/main/julia/AoC2017_03.jl) | [✓](src/main/julia/AoC2017_04.jl) | | | | | | | | | [✓](src/main/julia/AoC2017_13.jl) | | | | | | | | | | | | | -| rust | | | | | | | | | | | | | | | | | | | | | | | | | | +| rust | | | | | | | | | | | | | | | [✓](src/main/rust/AoC2017_15/src/main.rs) | | | | | | | | | | | ## 2016 @@ -130,11 +135,10 @@ | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| python3 | [✓](src/main/python/AoC2016_01.py) | [✓](src/main/python/AoC2016_02.py) | [✓](src/main/python/AoC2016_03.py) | [✓](src/main/python/AoC2016_04.py) | [✓](src/main/python/AoC2016_05.py) | [✓](src/main/python/AoC2016_06.py) | [✓](src/main/python/AoC2016_07.py) | | [✓](src/main/python/AoC2016_09.py) | | | [✓](src/main/python/AoC2016_12.py) | | | [✓](src/main/python/AoC2016_15.py) | [✓](src/main/python/AoC2016_16.py) | | [✓](src/main/python/AoC2016_18.py) | [✓](src/main/python/AoC2016_19.py) | [✓](src/main/python/AoC2016_20.py) | [✓](src/main/python/AoC2016_21.py) | [✓](src/main/python/AoC2016_22.py) | [✓](src/main/python/AoC2016_23.py) | | [✓](src/main/python/AoC2016_25.py) | +| python3 | [✓](src/main/python/AoC2016_01.py) | [✓](src/main/python/AoC2016_02.py) | [✓](src/main/python/AoC2016_03.py) | [✓](src/main/python/AoC2016_04.py) | [✓](src/main/python/AoC2016_05.py) | [✓](src/main/python/AoC2016_06.py) | [✓](src/main/python/AoC2016_07.py) | [✓](src/main/python/AoC2016_08.py) | [✓](src/main/python/AoC2016_09.py) | | | [✓](src/main/python/AoC2016_12.py) | | | [✓](src/main/python/AoC2016_15.py) | [✓](src/main/python/AoC2016_16.py) | | [✓](src/main/python/AoC2016_18.py) | [✓](src/main/python/AoC2016_19.py) | [✓](src/main/python/AoC2016_20.py) | [✓](src/main/python/AoC2016_21.py) | [✓](src/main/python/AoC2016_22.py) | [✓](src/main/python/AoC2016_23.py) | | [✓](src/main/python/AoC2016_25.py) | | java | [✓](src/main/java/AoC2016_01.java) | [✓](src/main/java/AoC2016_02.java) | [✓](src/main/java/AoC2016_03.java) | [✓](src/main/java/AoC2016_04.java) | [✓](src/main/java/AoC2016_05.java) | [✓](src/main/java/AoC2016_06.java) | [✓](src/main/java/AoC2016_07.java) | [✓](src/main/java/AoC2016_08.java) | [✓](src/main/java/AoC2016_09.java) | [✓](src/main/java/AoC2016_10.java) | [✓](src/main/java/AoC2016_11.java) | [✓](src/main/java/AoC2016_12.java) | [✓](src/main/java/AoC2016_13.java) | [✓](src/main/java/AoC2016_14.java) | [✓](src/main/java/AoC2016_15.java) | [✓](src/main/java/AoC2016_16.java) | [✓](src/main/java/AoC2016_17.java) | [✓](src/main/java/AoC2016_18.java) | [✓](src/main/java/AoC2016_19.java) | [✓](src/main/java/AoC2016_20.java) | [✓](src/main/java/AoC2016_21.java) | [✓](src/main/java/AoC2016_22.java) | [✓](src/main/java/AoC2016_23.java) | [✓](src/main/java/AoC2016_24.java) | [✓](src/main/java/AoC2016_25.java) | | bash | [✓](src/main/bash/AoC2016_01.sh) | [✓](src/main/bash/AoC2016_02.sh) | [✓](src/main/bash/AoC2016_03.sh) | [✓](src/main/bash/AoC2016_04.sh) | | [✓](src/main/bash/AoC2016_06.sh) | [✓](src/main/bash/AoC2016_07.sh) | [✓](src/main/bash/AoC2016_08.sh) | [✓](src/main/bash/AoC2016_09.sh) | | | | | | | | | | | | | | | | | | c++ | | | | | | | | [✓](src/main/cpp/2016/08/AoC2016_08.cpp) | | | | | [✓](src/main/cpp/2016/13/AoC2016_13.cpp) | | | | | | | | | | | | | -| julia | | | | | | | | | | | | | | | | | | | | | | | | | | | rust | | [✓](src/main/rust/AoC2016_02/src/main.rs) | | | | | | | | | | | [✓](src/main/rust/AoC2016_13/src/main.rs) | | | | | | | | | | | | | @@ -148,10 +152,10 @@ | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| python3 | [✓](src/main/python/AoC2015_01.py) | [✓](src/main/python/AoC2015_02.py) | [✓](src/main/python/AoC2015_03.py) | [✓](src/main/python/AoC2015_04.py) | [✓](src/main/python/AoC2015_05.py) | [✓](src/main/python/AoC2015_06.py) | | [✓](src/main/python/AoC2015_08.py) | [✓](src/main/python/AoC2015_09.py) | [✓](src/main/python/AoC2015_10.py) | [✓](src/main/python/AoC2015_11.py) | [✓](src/main/python/AoC2015_12.py) | [✓](src/main/python/AoC2015_13.py) | [✓](src/main/python/AoC2015_14.py) | [✓](src/main/python/AoC2015_15.py) | [✓](src/main/python/AoC2015_16.py) | [✓](src/main/python/AoC2015_17.py) | [✓](src/main/python/AoC2015_18.py) | [✓](src/main/python/AoC2015_19.py) | | | | [✓](src/main/python/AoC2015_23.py) | [✓](src/main/python/AoC2015_24.py) | [✓](src/main/python/AoC2015_25.py) | +| python3 | [✓](src/main/python/AoC2015_01.py) | [✓](src/main/python/AoC2015_02.py) | [✓](src/main/python/AoC2015_03.py) | [✓](src/main/python/AoC2015_04.py) | [✓](src/main/python/AoC2015_05.py) | [✓](src/main/python/AoC2015_06.py) | [✓](src/main/python/AoC2015_07.py) | [✓](src/main/python/AoC2015_08.py) | [✓](src/main/python/AoC2015_09.py) | [✓](src/main/python/AoC2015_10.py) | [✓](src/main/python/AoC2015_11.py) | [✓](src/main/python/AoC2015_12.py) | [✓](src/main/python/AoC2015_13.py) | [✓](src/main/python/AoC2015_14.py) | [✓](src/main/python/AoC2015_15.py) | [✓](src/main/python/AoC2015_16.py) | [✓](src/main/python/AoC2015_17.py) | [✓](src/main/python/AoC2015_18.py) | [✓](src/main/python/AoC2015_19.py) | | | | [✓](src/main/python/AoC2015_23.py) | [✓](src/main/python/AoC2015_24.py) | [✓](src/main/python/AoC2015_25.py) | | java | [✓](src/main/java/AoC2015_01.java) | [✓](src/main/java/AoC2015_02.java) | [✓](src/main/java/AoC2015_03.java) | [✓](src/main/java/AoC2015_04.java) | [✓](src/main/java/AoC2015_05.java) | [✓](src/main/java/AoC2015_06.java) | [✓](src/main/java/AoC2015_07.java) | [✓](src/main/java/AoC2015_08.java) | [✓](src/main/java/AoC2015_09.java) | [✓](src/main/java/AoC2015_10.java) | [✓](src/main/java/AoC2015_11.java) | [✓](src/main/java/AoC2015_12.java) | [✓](src/main/java/AoC2015_13.java) | [✓](src/main/java/AoC2015_14.java) | [✓](src/main/java/AoC2015_15.java) | [✓](src/main/java/AoC2015_16.java) | [✓](src/main/java/AoC2015_17.java) | [✓](src/main/java/AoC2015_18.java) | [✓](src/main/java/AoC2015_19.java) | [✓](src/main/java/AoC2015_20.java) | [✓](src/main/java/AoC2015_21.java) | [✓](src/main/java/AoC2015_22.java) | | | | | bash | [✓](src/main/bash/AoC2015_01.sh) | [✓](src/main/bash/AoC2015_02.sh) | [✓](src/main/bash/AoC2015_03.sh) | [✓](src/main/bash/AoC2015_04.sh) | [✓](src/main/bash/AoC2015_05.sh) | | | | [✓](src/main/bash/AoC2015_09.sh) | [✓](src/main/bash/AoC2015_10.sh) | | | | [✓](src/main/bash/AoC2015_14.sh) | | | | | | | | | | | | | c++ | [✓](src/main/cpp/2015/01/AoC2015_01.cpp) | [✓](src/main/cpp/2015/02/AoC2015_02.cpp) | [✓](src/main/cpp/2015/03/AoC2015_03.cpp) | [✓](src/main/cpp/2015/04/AoC2015_04.cpp) | [✓](src/main/cpp/2015/05/AoC2015_05.cpp) | [✓](src/main/cpp/2015/06/AoC2015_06.cpp) | | | | | | | | | | | | | | | | | | | | | julia | [✓](src/main/julia/AoC2015_01.jl) | [✓](src/main/julia/AoC2015_02.jl) | [✓](src/main/julia/AoC2015_03.jl) | [✓](src/main/julia/AoC2015_04.jl) | [✓](src/main/julia/AoC2015_05.jl) | [✓](src/main/julia/AoC2015_06.jl) | | | | | | | | | | | | | | | | | | | | -| rust | [✓](src/main/rust/AoC2015_01/src/main.rs) | [✓](src/main/rust/AoC2015_02/src/main.rs) | [✓](src/main/rust/AoC2015_03/src/main.rs) | [✓](src/main/rust/AoC2015_04/src/main.rs) | [✓](src/main/rust/AoC2015_05/src/main.rs) | | | | [✓](src/main/rust/AoC2015_09/src/main.rs) | | [✓](src/main/rust/AoC2015_11/src/main.rs) | | [✓](src/main/rust/AoC2015_13/src/main.rs) | | [✓](src/main/rust/AoC2015_15/src/main.rs) | [✓](src/main/rust/AoC2015_16/src/main.rs) | | | | | | | | | | +| rust | [✓](src/main/rust/AoC2015_01/src/main.rs) | [✓](src/main/rust/AoC2015_02/src/main.rs) | [✓](src/main/rust/AoC2015_03/src/main.rs) | [✓](src/main/rust/AoC2015_04/src/main.rs) | [✓](src/main/rust/AoC2015_05/src/main.rs) | [✓](src/main/rust/AoC2015_06/src/main.rs) | [✓](src/main/rust/AoC2015_07/src/main.rs) | [✓](src/main/rust/AoC2015_08/src/main.rs) | [✓](src/main/rust/AoC2015_09/src/main.rs) | [✓](src/main/rust/AoC2015_10/src/main.rs) | [✓](src/main/rust/AoC2015_11/src/main.rs) | | [✓](src/main/rust/AoC2015_13/src/main.rs) | | [✓](src/main/rust/AoC2015_15/src/main.rs) | [✓](src/main/rust/AoC2015_16/src/main.rs) | | | | | | | | | | diff --git a/clang.cfg b/clang.cfg new file mode 100644 index 00000000..e218a162 --- /dev/null +++ b/clang.cfg @@ -0,0 +1 @@ +-std=c++17 -Wall diff --git a/doc/aoc2023.jpg b/doc/aoc2023.jpg new file mode 100644 index 00000000..597d52f0 Binary files /dev/null and b/doc/aoc2023.jpg differ diff --git a/doc/aoc2024.jpg b/doc/aoc2024.jpg new file mode 100755 index 00000000..769c2f03 Binary files /dev/null and b/doc/aoc2024.jpg differ diff --git a/pyproject.toml b/pyproject.toml index ce812017..da660562 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,11 @@ force_single_line = true skip_gitignore = true include_trailing_comma = true +[tool.black] +line-length = 79 + [tool.mypy] +strict = true [[tool.mypy.overrides]] module = "aocd.*" @@ -34,3 +38,7 @@ ignore_missing_imports = true [[tool.mypy.overrides]] module = "junitparser.*" ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "termcolor.*" +ignore_missing_imports = true diff --git a/requirements.txt b/requirements.txt index 5a810ccb..3c589d41 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,9 @@ advent-of-code-data==1.3.2 advent-of-code-ocr==1.0.0 -bandit[toml]==1.7.5 -flake8==6.1.0 -ipython==8.16.1 -isort==5.12.0 -junitparser==3.1.0 -numpy==1.26.1 +bandit[toml]==1.8.0 +flake8==7.1.1 +ipython==8.29.0 +isort==5.13.2 +junitparser==3.2.0 prettyprinter==0.18.0 -vulture==2.10 +vulture==2.13 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 639f4f17..0193dee3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.74.0" +channel = "1.83.0" diff --git a/setup.yml b/setup.yml index 579f312a..aa15daf1 100644 --- a/setup.yml +++ b/setup.yml @@ -89,14 +89,14 @@ runner: day_format: *cpp-file_pattern julia: command: julia - options: -O + options: -O3 base_dir: *julia-base_dir day_format: *julia-file_pattern ext: *julia-file_ext server: command: - julia - - -O + - -O3 - -- - src/main/julia/Runner.jl host: localhost diff --git a/src/main/cpp/2015/06/AoC2015_06.cpp b/src/main/cpp/2015/06/AoC2015_06.cpp index 0ba8227d..aacbde82 100644 --- a/src/main/cpp/2015/06/AoC2015_06.cpp +++ b/src/main/cpp/2015/06/AoC2015_06.cpp @@ -7,30 +7,27 @@ #include "../../aoc/aoc.hpp" #include "../../aoc/grid/grid.hpp" -#include "../../aocd/aocd.hpp" using namespace std; const string REGEX = "([ a-z]+) ([0-9]+),([0-9]+) through ([0-9]+),([0-9]+)"; -void forEachCell(Grid& grid, const Cell& start, const Cell& end, - const function&, const Cell&)>& f) { - for (int rr : aoc::Range::rangeClosed(start.row(), end.row())) { - for (int cc : aoc::Range::rangeClosed(start.col(), end.col())) { - f(grid, Cell::at(rr, cc)); +void forEachCell(int (&grid)[1'000'000], const Cell& start, const Cell& end, + const function& f) { + for (int r = start.row() * 1000; r <= end.row() * 1000; r += 1000) { + for (int i = r + start.col(); i <= r + end.col(); i++) { + grid[i] = f(grid[i]); } } } -int solve(const vector& input, - const function&, const Cell&)>& turn_on, - const function&, const Cell&)>& turn_off, - const function&, const Cell&)>& toggle) { - vector cells; - for (int i = 0; i < 1000; ++i) { - cells.push_back(string(1000, '0')); - } - Grid grid = Grid::from(cells); +int solve( + const vector& input, + const function& turn_on, + const function& turn_off, + const function& toggle +) { + int grid[1'000'000] = {}; const regex re(REGEX); for (const string& line : input) { smatch sm; @@ -46,33 +43,25 @@ int solve(const vector& input, forEachCell(grid, start, end, toggle); } } - return accumulate( - grid.begin(), grid.end(), 0, - [&grid](const int a, const Cell& b) { return a + grid.get(b); }); + return accumulate(begin(grid), end(grid), 0, plus()); } int part1(const vector& input) { return solve( input, - [](Grid& grid, const Cell& cell) { grid.setValue(cell, 1); }, - [](Grid& grid, const Cell& cell) { grid.setValue(cell, 0); }, - [](Grid& grid, const Cell& cell) { - grid.setValue(cell, grid.get(cell) == 1 ? 0 : 1); - }); + [](int v) { return 1; }, + [](int v) { return 0; }, + [](int v) { return v == 0 ? 1 : 0; } + ); } int part2(const vector& input) { return solve( input, - [](Grid& grid, const Cell& cell) { - grid.setValue(cell, grid.get(cell) + 1); - }, - [](Grid& grid, const Cell& cell) { - grid.setValue(cell, max(grid.get(cell) - 1, 0)); - }, - [](Grid& grid, const Cell& cell) { - grid.setValue(cell, grid.get(cell) + 2); - }); + [](int v) { return v + 1; }, + [](int v) { return v == 0 ? 0 : v - 1; }, + [](int v) { return v + 2; } + ); } const vector TEST1 = {"turn on 0,0 through 999,999"}; diff --git a/src/main/java/AoC2015_01.java b/src/main/java/AoC2015_01.java index d910ae50..f9468f30 100644 --- a/src/main/java/AoC2015_01.java +++ b/src/main/java/AoC2015_01.java @@ -13,7 +13,7 @@ import com.github.pareronia.aoc.solution.SolutionBase; public final class AoC2015_01 - extends SolutionBase, Integer, Integer> { + extends SolutionBase, Integer, Integer> { private AoC2015_01(final boolean debug) { super(debug); @@ -87,34 +87,34 @@ public static void main(final String[] args) throws Exception { private static final String TEST8 = ")())())"; private static final String TEST9 = ")"; private static final String TEST10 = "()())"; -} -enum Direction { + enum Direction { - UP(1), DOWN(-1); - - private static final char UP_CHAR = '('; - private static final char DOWN_CHAR = ')'; - - private final int value; - - Direction(final int value) { - this.value = value; - } - - public static Direction fromChar(final Character ch) { - return switch (ch) { - case UP_CHAR -> Direction.UP; - case DOWN_CHAR -> Direction.DOWN; - default -> throw unreachable(); - }; - } - - public int addTo(final int lhs) { - return lhs + this.value; - } - - public static Collector summingInt() { - return Collectors.summingInt(dir -> dir.value); + UP(1), DOWN(-1); + + private static final char UP_CHAR = '('; + private static final char DOWN_CHAR = ')'; + + private final int value; + + Direction(final int value) { + this.value = value; + } + + public static Direction fromChar(final Character ch) { + return switch (ch) { + case UP_CHAR -> Direction.UP; + case DOWN_CHAR -> Direction.DOWN; + default -> throw unreachable(); + }; + } + + public int addTo(final int lhs) { + return lhs + this.value; + } + + public static Collector summingInt() { + return Collectors.summingInt(dir -> dir.value); + } } } diff --git a/src/main/java/AoC2015_02.java b/src/main/java/AoC2015_02.java index 0e126a30..5650804e 100644 --- a/src/main/java/AoC2015_02.java +++ b/src/main/java/AoC2015_02.java @@ -8,10 +8,8 @@ import com.github.pareronia.aoc.solution.Samples; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -public final class AoC2015_02 extends SolutionBase, Integer, Integer> { +public final class AoC2015_02 + extends SolutionBase, Integer, Integer> { private AoC2015_02(final boolean debug) { super(debug); @@ -60,34 +58,31 @@ public static void main(final String[] args) throws Exception { private static final String TEST1 = "2x3x4"; private static final String TEST2 = "1x1x10"; -} - -@RequiredArgsConstructor -@ToString -final class Present { - private final Integer length; - private final Integer width; - private final Integer height; - public static Present fromInput(final String s) { - final int[] sp = Stream.of(s.split("x")).mapToInt(Integer::valueOf).toArray(); - return new Present(sp[0], sp[1], sp[2]); - } - - public int calculateRequiredArea() { - final int[] sides = new int[] { - 2 * this.length * this.width, - 2 * this.width * this.height, - 2 * this.height * this.length}; - return Arrays.stream(sides).sum() + Arrays.stream(sides).min().getAsInt() / 2; - } + record Present(int length, int width, int height) { + + public static Present fromInput(final String s) { + final int[] sp = Stream.of(s.split("x")) + .mapToInt(Integer::parseInt).toArray(); + return new Present(sp[0], sp[1], sp[2]); + } + + public int calculateRequiredArea() { + final int[] sides = { + 2 * this.length * this.width, + 2 * this.width * this.height, + 2 * this.height * this.length}; + return Arrays.stream(sides).sum() + + Arrays.stream(sides).min().getAsInt() / 2; + } - public int calculateRequiredLength() { - final int[] circumferences = new int[] { - 2 * (this.length + this.width), - 2 * (this.width + this.height), - 2 * (this.height + this.length)}; - return Arrays.stream(circumferences).min().getAsInt() - + this.length * this.width * this.height; + public int calculateRequiredLength() { + final int[] circumferences = { + 2 * (this.length + this.width), + 2 * (this.width + this.height), + 2 * (this.height + this.length)}; + return Arrays.stream(circumferences).min().getAsInt() + + this.length * this.width * this.height; + } } } \ No newline at end of file diff --git a/src/main/java/AoC2015_04.java b/src/main/java/AoC2015_04.java index 638440a0..00b1824d 100644 --- a/src/main/java/AoC2015_04.java +++ b/src/main/java/AoC2015_04.java @@ -8,10 +8,8 @@ import com.github.pareronia.aoc.solution.Samples; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.RequiredArgsConstructor; - public final class AoC2015_04 - extends SolutionBase { + extends SolutionBase { private AoC2015_04(final boolean debug) { super(debug); @@ -57,34 +55,33 @@ public static void main(final String[] args) throws Exception { private static final String TEST1 = "abcdef"; private static final String TEST2 = "pqrstuv"; -} + -@RequiredArgsConstructor -final class AdventCoinsMiner { - private final String secretKey; + record AdventCoinsMiner(String secretKey) { - private boolean checkZeroes(final byte[] digest, final int zeroes) { - int cnt = 0; - for (final int j : range(zeroes / 2 + zeroes % 2)) { - final byte c = digest[j]; - if ((c & 0xF0) != 0) { - break; - } - cnt++; - if ((c & 0x0F) != 0) { - break; + private boolean checkZeroes(final byte[] digest, final int zeroes) { + int cnt = 0; + for (final int j : range(zeroes / 2 + zeroes % 2)) { + final byte c = digest[j]; + if ((c & 0xF0) != 0) { + break; + } + cnt++; + if ((c & 0x0F) != 0) { + break; + } + cnt++; } - cnt++; + return cnt == zeroes; + } + + public int findMd5StartingWithZeroes(final int zeroes) { + return IntStream.iterate(1, i -> i + 1) + .dropWhile(i -> { + final String data = this.secretKey + String.valueOf(i); + return !checkZeroes(MD5.md5(data), zeroes); + }) + .findFirst().getAsInt(); } - return cnt == zeroes; - } - - public int findMd5StartingWithZeroes(final int zeroes) { - return IntStream.iterate(1, i -> i + 1) - .dropWhile(i -> { - final String data = this.secretKey + String.valueOf(i); - return !checkZeroes(MD5.md5(data), zeroes); - }) - .findFirst().getAsInt(); } } \ No newline at end of file diff --git a/src/main/java/AoC2015_06.java b/src/main/java/AoC2015_06.java index f21d500a..4e6983c4 100644 --- a/src/main/java/AoC2015_06.java +++ b/src/main/java/AoC2015_06.java @@ -1,22 +1,19 @@ import static java.util.stream.Collectors.toList; +import java.util.Arrays; import java.util.List; -import java.util.function.Function; -import java.util.stream.IntStream; -import java.util.stream.Stream; +import java.util.function.IntFunction; import com.github.pareronia.aoc.Grid.Cell; -import com.github.pareronia.aoc.IntGrid; +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.StringOps.StringSplit; import com.github.pareronia.aoc.solution.Sample; import com.github.pareronia.aoc.solution.Samples; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - // TODO: better -> https://www.reddit.com/r/adventofcode/comments/3vmltn/day_6_solutions/cxozgap/ public final class AoC2015_06 - extends SolutionBase, Integer, Integer> { + extends SolutionBase, Integer, Integer> { private AoC2015_06(final boolean debug) { super(debug); @@ -35,19 +32,80 @@ protected List parseInput(final List inputs) { return inputs.stream().map(Instruction::fromInput).collect(toList()); } + private int solve(final List instructions, final Mode mode) { + final int[] lights = new int[1_000_000]; + for (final Instruction instruction : instructions) { + instruction.action.apply( + lights, instruction.start, instruction.end, mode); + } + return Arrays.stream(lights).sum(); + } + @Override - public Integer solvePart1(final List input) { - final Grid lights = new Grid(c -> 1, c -> 0, c -> c == 1 ? 0 : 1); - lights.processInstructions(input); - return (int) lights.getAllLightValues().filter(l -> l == 1).count(); + public Integer solvePart1(final List instructions) { + return solve(instructions, Mode.MODE_1); } @Override - public Integer solvePart2(final List input) { - final Grid lights = new Grid(c -> c + 1, c -> Math.max(c - 1, 0), c -> c + 2); - lights.processInstructions(input); - return lights.getAllLightValues().sum(); + public Integer solvePart2(final List instructions) { + return solve(instructions, Mode.MODE_2); } + + enum Mode { MODE_1, MODE_2 } + + record Instruction(Action action, Cell start, Cell end) { + public static Instruction fromInput(final String input) { + final String s = input.replace("turn ", "turn_"); + final StringSplit splits = StringOps.splitOnce(s, " through "); + final StringSplit actionAndStartSplits + = StringOps.splitOnce(splits.left(), " "); + return new Instruction( + Action.fromString(actionAndStartSplits.left()), + Cell.fromString(actionAndStartSplits.right()), + Cell.fromString(splits.right())); + } + + enum Action { + TURN_ON, TURN_OFF, TOGGLE; + + public static Action fromString(final String s) { + return switch (s) { + case "turn_on" -> TURN_ON; + case "turn_off" -> TURN_OFF; + default -> TOGGLE; + }; + } + + public void apply( + final int[] grid, + final Cell start, + final Cell end, + final Mode mode + ) { + final IntFunction f = switch (this) { + case TURN_ON -> switch (mode) { + case MODE_1: yield v -> 1; + case MODE_2: yield v -> v + 1; + }; + case TURN_OFF -> switch (mode) { + case MODE_1: yield v -> 0; + case MODE_2: yield v -> Math.max(v - 1, 0); + }; + case TOGGLE -> switch (mode) { + case MODE_1: yield v -> v == 1 ? 0 : 1; + case MODE_2: yield v -> v + 2; + }; + }; + final int rStart = start.getRow() * 1_000; + final int rEnd = end.getRow() * 1_000; + for (int r = rStart; r <= rEnd; r += 1_000) { + for (int idx = r + start.getCol(); idx <= r + end.getCol(); idx++) { + grid[idx] = f.apply(grid[idx]); + } + } + } //apply + } // Action + } // Instruction @Override @Samples({ @@ -69,57 +127,4 @@ public static void main(final String[] args) throws Exception { private static final String TEST3 = "turn off 499,499 through 500,500"; private static final String TEST4 = "turn on 0,0 through 0,0"; private static final String TEST5 = "toggle 0,0 through 999,999"; -} - -@RequiredArgsConstructor -final class Grid { - private final IntGrid lights = new IntGrid(new int[1_000][1_000]); - private final Function turnOn; - private final Function turnOff; - private final Function toggle; - - public IntStream getAllLightValues() { - return Stream.of(this.lights.getValues()).flatMapToInt(IntStream::of); - } - - public void processInstructions(final List instructions) { - for (final Instruction instruction : instructions) { - final Function action = - instruction.action == Instruction.Action.TURN_ON - ? this.turnOn - : instruction.action == Instruction.Action.TURN_OFF - ? this.turnOff - : this.toggle; - for (int rr = instruction.start.getRow(); rr <= instruction.end.getRow(); rr++) { - for (int cc = instruction.start.getCol(); cc <= instruction.end.getCol(); cc++) { - this.lights.setValue(Cell.at(rr, cc), action.apply(this.lights.getValue(Cell.at(rr, cc)))); - } - } - } - } -} - -@RequiredArgsConstructor -@ToString -final class Instruction { - enum Action { TURN_ON, TURN_OFF, TOGGLE } - - final Action action; - final Cell start; - final Cell end; - - public static Instruction fromInput(final String input) { - final String s = input.replace("turn ", "turn_"); - final String[] splits = s.split(" through "); - final String[] actionAndStartSplits = splits[0].split(" "); - final Cell start = Cell.fromString(actionAndStartSplits[1]); - final Cell end = Cell.fromString(splits[1]); - if ("turn_on".equals(actionAndStartSplits[0])) { - return new Instruction(Action.TURN_ON, start, end); - } else if ("turn_off".equals(actionAndStartSplits[0])) { - return new Instruction(Action.TURN_OFF, start, end); - } else { - return new Instruction(Action.TOGGLE, start, end); - } - } } \ No newline at end of file diff --git a/src/main/java/AoC2015_07.java b/src/main/java/AoC2015_07.java index 7d5f7acc..f04fc6dd 100644 --- a/src/main/java/AoC2015_07.java +++ b/src/main/java/AoC2015_07.java @@ -16,12 +16,7 @@ import com.github.pareronia.aoc.StringUtils; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -public class AoC2015_07 extends SolutionBase, Integer, Integer> { +public class AoC2015_07 extends SolutionBase, Integer, Integer> { private AoC2015_07(final boolean debug) { super(debug); @@ -133,199 +128,200 @@ public static void main(final String[] args) throws Exception { } private static final List TEST = splitLines( - "123 -> x\r\n" + - "456 -> y\r\n" + - "x AND y -> d\r\n" + - "x OR y -> e\r\n" + - "x LSHIFT 2 -> f\r\n" + - "y RSHIFT 2 -> g\r\n" + - "NOT x -> h\r\n" + - "NOT y -> i\r\n" + - "i -> j" + """ + 123 -> x\r + 456 -> y\r + x AND y -> d\r + x OR y -> e\r + x LSHIFT 2 -> f\r + y RSHIFT 2 -> g\r + NOT x -> h\r + NOT y -> i\r + i -> j""" ); -} -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -final class Gate implements Cloneable { + static final class Gate implements Cloneable { + + private static final int BIT_SIZE = 16; + + enum Op { SET, NOT, AND, OR, LSHIFT, RSHIFT } + + final String name; + final String in1; + final String in2; + final Op op; + final Integer arg; + private Integer result; - private static final int BIT_SIZE = 16; - - enum Op { SET, NOT, AND, OR, LSHIFT, RSHIFT } - - @Getter - final String name; - final String in1; - final String in2; - final Op op; - final Integer arg; - @Getter - private Integer result; - - public static Gate fromInput(final String input) { - final String[] splits = input.split(" -> "); - if (splits[0].contains("AND")) { - final String[] andSplits = splits[0].split(" AND "); - return Gate.and(splits[1], andSplits[0], andSplits[1]); - } else if (splits[0].contains("OR")) { - final String[] orSplits = splits[0].split(" OR "); - return Gate.or(splits[1], orSplits[0], orSplits[1]); - } else if (splits[0].contains("LSHIFT")) { - final String[] shSplits = splits[0].split(" LSHIFT "); - return Gate.lshift(splits[1], shSplits[0], shSplits[1]); - } else if (splits[0].contains("RSHIFT")) { - final String[] shSplits = splits[0].split(" RSHIFT "); - return Gate.rshift(splits[1], shSplits[0], shSplits[1]); - } else if (splits[0].contains("NOT")) { - final String in = splits[0].substring("NOT ".length()); - return Gate.not(splits[1], in); - } else { - return Gate.set(splits[1], splits[0]); + protected Gate( + final String name, + final String in1, + final String in2, + final Op op, + final Integer arg + ) { + this.name = name; + this.in1 = in1; + this.in2 = in2; + this.op = op; + this.arg = arg; } - } + public static Gate fromInput(final String input) { + final String[] splits = input.split(" -> "); + if (splits[0].contains("AND")) { + final String[] andSplits = splits[0].split(" AND "); + return Gate.and(splits[1], andSplits[0], andSplits[1]); + } else if (splits[0].contains("OR")) { + final String[] orSplits = splits[0].split(" OR "); + return Gate.or(splits[1], orSplits[0], orSplits[1]); + } else if (splits[0].contains("LSHIFT")) { + final String[] shSplits = splits[0].split(" LSHIFT "); + return Gate.lshift(splits[1], shSplits[0], shSplits[1]); + } else if (splits[0].contains("RSHIFT")) { + final String[] shSplits = splits[0].split(" RSHIFT "); + return Gate.rshift(splits[1], shSplits[0], shSplits[1]); + } else if (splits[0].contains("NOT")) { + final String in = splits[0].substring("NOT ".length()); + return Gate.not(splits[1], in); + } else { + return Gate.set(splits[1], splits[0]); + } + } - @Override - protected Gate clone() throws CloneNotSupportedException { - return new Gate(this.name, this.in1, this.in2, this.op, this.arg); - } - - public static Gate cloneGate(final Gate gate) { - try { - return gate.clone(); - } catch (final CloneNotSupportedException e) { - throw new RuntimeException(e); + @Override + protected Gate clone() throws CloneNotSupportedException { + return new Gate(this.name, this.in1, this.in2, this.op, this.arg); + } + + public static Gate cloneGate(final Gate gate) { + try { + return gate.clone(); + } catch (final CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + public static Gate not(final String name, final String in) { + return new Gate(name, in, null, Op.NOT, null); + } + + public static Gate and(final String name, final String in1, final String in2) { + return new Gate(name, in1, in2, Op.AND, null); + } + + public static Gate or(final String name, final String in1, final String in2) { + return new Gate(name, in1, in2, Op.OR, null); + } + + public static Gate lshift(final String name, final String in, final String value) { + final Integer arg = Integer.valueOf(value); + assert arg < BIT_SIZE : "Shifting more than 15 positions"; + return new Gate(name, in, null, Op.LSHIFT, arg); + } + + public static Gate rshift(final String name, final String in, final String value) { + final Integer arg = Integer.valueOf(value); + assert arg < BIT_SIZE : "Shifting more than 15 positions"; + return new Gate(name, in, null, Op.RSHIFT, arg); + } + + public static Gate set(final String name, final String in) { + return new Gate(name, in, null, Op.SET, null); } - } - - public static Gate not(final String name, final String in) { - return new Gate(name, in, null, Op.NOT, null); - } - - public static Gate and(final String name, final String in1, final String in2) { - return new Gate(name, in1, in2, Op.AND, null); - } - - public static Gate or(final String name, final String in1, final String in2) { - return new Gate(name, in1, in2, Op.OR, null); - } - - public static Gate lshift(final String name, final String in, final String value) { - final Integer arg = Integer.valueOf(value); - assert arg < BIT_SIZE : "Shifting more than 15 positions"; - return new Gate(name, in, null, Op.LSHIFT, arg); - } - - public static Gate rshift(final String name, final String in, final String value) { - final Integer arg = Integer.valueOf(value); - assert arg < BIT_SIZE : "Shifting more than 15 positions"; - return new Gate(name, in, null, Op.RSHIFT, arg); - } - - public static Gate set(final String name, final String in) { - return new Gate(name, in, null, Op.SET, null); - } - public Integer updateResult(final Integer in1, final Integer in2) { - switch (this.op) { - case SET: - this.result = in1; - break; - case AND: - this.result = in1 & in2; - break; - case LSHIFT: - this.result = in1 << arg; - break; - case NOT: - this.result = (int) (Math.pow(2, BIT_SIZE) + ~in1); - break; - case OR: - this.result = in1 | in2; - break; - case RSHIFT: - this.result = in1 >>> arg; - break; - default: - throw new IllegalStateException(); + public String getName() { + return name; } - return this.result; - } - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - switch (this.op) { - case SET: - sb.append(this.in1); - break; - case AND: - sb.append(this.in1).append(" AND ").append(this.in2); - break; - case LSHIFT: - sb.append(this.in1).append(" LSHIFT ").append(arg); - break; - case NOT: - sb.append("NOT ").append(this.in1); - break; - case OR: - sb.append(this.in1).append(" OR ").append(this.in2); - break; - case RSHIFT: - sb.append(this.in1).append(" RSHIFT ").append(arg); - break; - default: - throw new IllegalStateException(); + public Integer getResult() { + return result; } - return sb.toString(); - } -} -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -@ToString -final class Circuit { - private final Map gates; + public Integer updateResult(final Integer in1, final Integer in2) { + switch (this.op) { + case SET -> this.result = in1; + case AND -> this.result = in1 & in2; + case LSHIFT -> this.result = in1 << arg; + case NOT -> this.result = (1 << BIT_SIZE) + ~in1; + case OR -> this.result = in1 | in2; + case RSHIFT -> this.result = in1 >>> arg; + default -> throw new IllegalStateException(); + } + return this.result; + } - public static Circuit of(final Collection gates) { - return new Circuit(requireNonNull(gates).stream() - .collect(toMap(Gate::getName, identity()))); - } - - public Collection getGates() { - return this.gates.values(); - } - - public Gate getGate(final String name) { - return this.gates.get(requireNonNull(name)); - } - - public void setGate(final String name, final Gate gate) { - this.gates.put(requireNonNull(name), requireNonNull(gate)); - } - - public Optional getGateIn1(final String name) { - final Gate gate = this.getGate(name); - return Optional.ofNullable(gate.in1).map(this::getGate); - } - - public Optional getGateIn2(final String name) { - final Gate gate = this.getGate(name); - return Optional.ofNullable(gate.in2).map(this::getGate); + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + switch (this.op) { + case SET -> sb.append(this.in1); + case AND -> sb.append(this.in1).append(" AND ").append(this.in2); + case LSHIFT -> sb.append(this.in1).append(" LSHIFT ").append(arg); + case NOT -> sb.append("NOT ").append(this.in1); + case OR -> sb.append(this.in1).append(" OR ").append(this.in2); + case RSHIFT -> sb.append(this.in1).append(" RSHIFT ").append(arg); + default -> throw new IllegalStateException(); + } + return sb.toString(); + } } - - public int getValue(final String name) { - assert name != null && !name.isEmpty(): "name is empty"; - if (StringUtils.isNumeric(name)) { - final int out = Integer.valueOf(name); - return out; + + private static final class Circuit { + private final Map gates; + + protected Circuit(final Map gates) { + this.gates = gates; } - final Gate gate = getGate(name); - assert gate != null : "Gate '" + name + "' not found"; - final Integer result = gate.getResult(); - if (result != null) { - return result; + + public static Circuit of(final Collection gates) { + return new Circuit(requireNonNull(gates).stream() + .collect(toMap(Gate::getName, identity()))); + } + + public Collection getGates() { + return this.gates.values(); + } + + public Gate getGate(final String name) { + return this.gates.get(requireNonNull(name)); + } + + public void setGate(final String name, final Gate gate) { + this.gates.put(requireNonNull(name), requireNonNull(gate)); + } + + public Optional getGateIn1(final String name) { + final Gate gate = this.getGate(name); + return Optional.ofNullable(gate.in1).map(this::getGate); + } + + public Optional getGateIn2(final String name) { + final Gate gate = this.getGate(name); + return Optional.ofNullable(gate.in2).map(this::getGate); + } + + public int getValue(final String name) { + assert name != null && !name.isEmpty(): "name is empty"; + if (StringUtils.isNumeric(name)) { + return Integer.parseInt(name); + } + final Gate gate = getGate(name); + assert gate != null : "Gate '" + name + "' not found"; + final Integer result = gate.getResult(); + if (result != null) { + return result; + } + final Integer in1 = getValue(gate.in1); + final Integer in2 = gate.in2 != null ? getValue(gate.in2) : null; + return gate.updateResult(in1, in2); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Circuit [gates=").append(gates).append("]"); + return builder.toString(); } - final Integer in1 = getValue(gate.in1); - final Integer in2 = gate.in2 != null ? getValue(gate.in2) : null; - return gate.updateResult(in1, in2); } -} +} \ No newline at end of file diff --git a/src/main/java/AoC2015_08.java b/src/main/java/AoC2015_08.java index 888d029d..5749746c 100644 --- a/src/main/java/AoC2015_08.java +++ b/src/main/java/AoC2015_08.java @@ -28,30 +28,40 @@ protected List parseInput(final List inputs) { return inputs; } + private int solve(final List inputs, final Mode mode) { + return inputs.stream().mapToInt(mode::overhead).sum(); + } + @Override public Integer solvePart1(final List inputs) { - return inputs.stream().mapToInt(this::countDecodingOverhead).sum(); + return solve(inputs, Mode.DECODE); } @Override public Integer solvePart2(final List inputs) { - return inputs.stream().mapToInt(this::countEncodingOverhead).sum(); + return solve(inputs, Mode.ENCODE); } - private int countDecodingOverhead(String str) { - assert str.charAt(0) == '"' && str.charAt(str.length() - 1) == '"'; - int cnt = 2; - while (str.contains("\\\\")) { - str = str.replaceFirst("[\\\\]{2}", ""); - cnt++; + private enum Mode { + DECODE, ENCODE; + + public int overhead(String str) { + return switch (this) { + case DECODE -> { + assert str.charAt(0) == '"' && str.charAt(str.length() - 1) == '"'; + int cnt = 2; + while (str.contains("\\\\")) { + str = str.replaceFirst("[\\\\]{2}", ""); + cnt++; + } + cnt += countMatches(str, "\\\""); + cnt += 3 * HEX.matcher(str).results().count(); + yield cnt; + } + case ENCODE -> + 2 + countMatches(str, "\\") + countMatches(str, "\""); + }; } - cnt += countMatches(str, "\\\""); - cnt += 3 * HEX.matcher(str).results().count(); - return cnt; - } - - private int countEncodingOverhead(final String str) { - return 2 + countMatches(str, "\\") + countMatches(str, "\""); } @Override @@ -67,8 +77,10 @@ public static void main(final String[] args) throws Exception { } private static final String TEST = - "\"\"\r\n" + - "\"abc\"\r\n" + - "\"aaa\\\"aaa\"\r\n" + - "\"\\x27\""; + """ + "" + "abc" + "aaa\\"aaa" + "\\x27\"\ + """; } diff --git a/src/main/java/AoC2015_09.java b/src/main/java/AoC2015_09.java index a4cb89dd..9ccaed4c 100644 --- a/src/main/java/AoC2015_09.java +++ b/src/main/java/AoC2015_09.java @@ -9,11 +9,7 @@ import com.github.pareronia.aoc.solution.Samples; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -public class AoC2015_09 extends SolutionBase { +public class AoC2015_09 extends SolutionBase { private AoC2015_09(final boolean debug) { super(debug); @@ -34,13 +30,11 @@ protected Distances parseInput(final List inputs) { @Override public Integer solvePart1(final Distances distances) { - log(distances); return distances.getDistancesOfCompleteRoutes().min().getAsInt(); } @Override public Integer solvePart2(final Distances distances) { - log(distances); return distances.getDistancesOfCompleteRoutes().max().getAsInt(); } @@ -57,42 +51,40 @@ public static void main(final String[] args) throws Exception { } private static final String TEST = - "London to Dublin = 464\r\n" + - "London to Belfast = 518\r\n" + - "Dublin to Belfast = 141"; -} + """ + London to Dublin = 464\r + London to Belfast = 518\r + Dublin to Belfast = 141"""; -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -@ToString -final class Distances { - private final int[][] matrix; - - public static Distances fromInput(final List inputs) { - final Map map = new HashMap<>(); - final Map values = new HashMap<>(); - final MutableInt cnt = new MutableInt(0); - for (final String input : inputs) { - final String[] ss1 = input.split(" = "); - final String[] ss2 = ss1[0].split(" to "); - final int idx1 = map.computeIfAbsent(ss2[0], x -> cnt.getAndIncrement()); - final int idx2 = map.computeIfAbsent(ss2[1], x -> cnt.getAndIncrement()); - final int value = Integer.parseInt(ss1[1]); - values.put(new int[] { idx1, idx2 }, value); - values.put(new int[] { idx2, idx1 }, value); + record Distances(int[][] matrix) { + + public static Distances fromInput(final List inputs) { + final Map map = new HashMap<>(); + final Map values = new HashMap<>(); + final MutableInt cnt = new MutableInt(0); + for (final String input : inputs) { + final String[] ss1 = input.split(" = "); + final String[] ss2 = ss1[0].split(" to "); + final int idx1 = map.computeIfAbsent(ss2[0], x -> cnt.getAndIncrement()); + final int idx2 = map.computeIfAbsent(ss2[1], x -> cnt.getAndIncrement()); + final int value = Integer.parseInt(ss1[1]); + values.put(new int[] { idx1, idx2 }, value); + values.put(new int[] { idx2, idx1 }, value); + } + final int[][] matrix = new int[map.size()][map.size()]; + values.entrySet().stream() + .forEach(e -> { + matrix[e.getKey()[0]][e.getKey()[1]] = e.getValue(); + }); + return new Distances(matrix); + } + + public IntStream getDistancesOfCompleteRoutes() { + final int[] idxs = IntStream.range(0, this.matrix.length).toArray(); + return IterTools.permutations(idxs).mapToInt(p -> + IntStream.range(1, p.length) + .map(i -> this.matrix[p[i -1]][p[i]]) + .sum()); } - final int[][] matrix = new int[map.size()][map.size()]; - values.entrySet().stream() - .forEach(e -> { - matrix[e.getKey()[0]][e.getKey()[1]] = e.getValue(); - }); - return new Distances(matrix); - } - - public IntStream getDistancesOfCompleteRoutes() { - final int[] idxs = IntStream.range(0, this.matrix.length).toArray(); - return IterTools.permutations(idxs).mapToInt(p -> - IntStream.range(1, p.length) - .map(i -> this.matrix[p[i -1]][p[i]]) - .sum()); } -} +} \ No newline at end of file diff --git a/src/main/java/AoC2015_10.java b/src/main/java/AoC2015_10.java index 91b24d10..4450f577 100644 --- a/src/main/java/AoC2015_10.java +++ b/src/main/java/AoC2015_10.java @@ -1,8 +1,15 @@ +import static com.github.pareronia.aoc.IterTools.enumerate; +import static com.github.pareronia.aoc.StringOps.splitLines; +import static java.util.stream.Collectors.toMap; + +import java.util.Arrays; import java.util.List; +import java.util.Map; +import com.github.pareronia.aoc.IterTools.Enumerated; import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2015_10 extends SolutionBase { +public class AoC2015_10 extends SolutionBase { private AoC2015_10(final boolean debug) { super(debug); @@ -16,49 +23,154 @@ public static final AoC2015_10 createDebug() { return new AoC2015_10(true); } - private String lookAndSay(final String string) { - final StringBuilder result = new StringBuilder(); - int i = 0; - while (i < string.length()) { - final char digit = string.charAt(i); - int j = 0; - while (i + j < string.length() && string.charAt(i + j) == digit) { - j++; - } - result.append(j).append(digit); - i += j; - } - return result.toString(); - } - @Override - protected String parseInput(final List inputs) { - assert inputs.size() == 1; - return inputs.get(0); + protected Input parseInput(final List inputs) { + final List elements = splitLines(ELEMENTS).stream() + .map(s -> s.split(" ")) + .toList(); + final Map indices = enumerate(elements.stream()) + .collect(toMap(e -> e.value()[2], Enumerated::index)); + final String[] sequence = new String[N]; + final int[][] decays = new int[N][]; + enumerate(elements.stream()).forEach(e -> { + sequence[e.index()] = e.value()[0]; + decays[e.index()] = Arrays.stream(e.value()) + .skip(4).mapToInt(indices::get).toArray(); + }); + return new Input(Arrays.asList(sequence), decays, inputs.get(0)); } - private String solve(final String input, final int iterations) { - String string = input; + private int solve(final Input input, final int iterations) { + int[] current = new int[N]; + current[input.sequence.indexOf(input.start)] = 1; for (int i = 0; i < iterations; i++) { - string = lookAndSay(string); - log(i + ": " + string.length()); + final int[] nxt = new int[N]; + for (int j = 0; j < N; j++) { + final int c = current[j]; + if (c > 0) { + for (int k = 0; k < input.decays[j].length; k++) { + nxt[input.decays[j][k]] += c; + } + } + } + current = nxt; + } + int ans = 0; + for (int i = 0; i < N; i++) { + ans += current[i] * input.sequence.get(i).length(); } - return string; + return ans; } @Override - public Integer solvePart1(final String input) { - return solve(input, 40).length(); + public Integer solvePart1(final Input input) { + return solve(input, 40); } @Override - public Integer solvePart2(final String input) { - return solve(input, 50).length(); + public Integer solvePart2(final Input input) { + return solve(input, 50); } public static void main(final String[] args) throws Exception { - assert AoC2015_10.createDebug().solve("1", 5).equals("312211"); - AoC2015_10.create().run(); } + + record Input(List sequence, int[][] decays, String start) {} + + private final int N = 92; + private final String ELEMENTS = """ + 22 -> H -> H + 13112221133211322112211213322112 -> He -> Hf Pa H Ca Li + 312211322212221121123222112 -> Li -> He + 111312211312113221133211322112211213322112 -> Be -> Ge Ca Li + 1321132122211322212221121123222112 -> B -> Be + 3113112211322112211213322112 -> C -> B + 111312212221121123222112 -> N -> C + 132112211213322112 -> O -> N + 31121123222112 -> F -> O + 111213322112 -> Ne -> F + 123222112 -> Na -> Ne + 3113322112 -> Mg -> Pm Na + 1113222112 -> Al -> Mg + 1322112 -> Si -> Al + 311311222112 -> P -> Ho Si + 1113122112 -> S -> P + 132112 -> Cl -> S + 3112 -> Ar -> Cl + 1112 -> K -> Ar + 12 -> Ca -> K + 3113112221133112 -> Sc -> Ho Pa H Ca Co + 11131221131112 -> Ti -> Sc + 13211312 -> V -> Ti + 31132 -> Cr -> V + 111311222112 -> Mn -> Cr Si + 13122112 -> Fe -> Mn + 32112 -> Co -> Fe + 11133112 -> Ni -> Zn Co + 131112 -> Cu -> Ni + 312 -> Zn -> Cu + 13221133122211332 -> Ga -> Eu Ca Ac H Ca Zn + 31131122211311122113222 -> Ge -> Ho Ga + 11131221131211322113322112 -> As -> Ge Na + 13211321222113222112 -> Se -> As + 3113112211322112 -> Br -> Se + 11131221222112 -> Kr -> Br + 1321122112 -> Rb -> Kr + 3112112 -> Sr -> Rb + 1112133 -> Y -> Sr U + 12322211331222113112211 -> Zr -> Y H Ca Tc + 1113122113322113111221131221 -> Nb -> Er Zr + 13211322211312113211 -> Mo -> Nb + 311322113212221 -> Tc -> Mo + 132211331222113112211 -> Ru -> Eu Ca Tc + 311311222113111221131221 -> Rh -> Ho Ru + 111312211312113211 -> Pd -> Rh + 132113212221 -> Ag -> Pd + 3113112211 -> Cd -> Ag + 11131221 -> In -> Cd + 13211 -> Sn -> In + 3112221 -> Sb -> Pm Sn + 1322113312211 -> Te -> Eu Ca Sb + 311311222113111221 -> I -> Ho Te + 11131221131211 -> Xe -> I + 13211321 -> Cs -> Xe + 311311 -> Ba -> Cs + 11131 -> La -> Ba + 1321133112 -> Ce -> La H Ca Co + 31131112 -> Pr -> Ce + 111312 -> Nd -> Pr + 132 -> Pm -> Nd + 311332 -> Sm -> Pm Ca Zn + 1113222 -> Eu -> Sm + 13221133112 -> Gd -> Eu Ca Co + 3113112221131112 -> Tb -> Ho Gd + 111312211312 -> Dy -> Tb + 1321132 -> Ho -> Dy + 311311222 -> Er -> Ho Pm + 11131221133112 -> Tm -> Er Ca Co + 1321131112 -> Yb -> Tm + 311312 -> Lu -> Yb + 11132 -> Hf -> Lu + 13112221133211322112211213322113 -> Ta -> Hf Pa H Ca W + 312211322212221121123222113 -> W -> Ta + 111312211312113221133211322112211213322113 -> Re -> Ge Ca W + 1321132122211322212221121123222113 -> Os -> Re + 3113112211322112211213322113 -> Ir -> Os + 111312212221121123222113 -> Pt -> Ir + 132112211213322113 -> Au -> Pt + 31121123222113 -> Hg -> Au + 111213322113 -> Tl -> Hg + 123222113 -> Pb -> Tl + 3113322113 -> Bi -> Pm Pb + 1113222113 -> Po -> Bi + 1322113 -> At -> Po + 311311222113 -> Rn -> Ho At + 1113122113 -> Fr -> Rn + 132113 -> Ra -> Fr + 3113 -> Ac -> Ra + 1113 -> Th -> Ac + 13 -> Pa -> Th + 3 -> U -> Pa + """; } diff --git a/src/main/java/AoC2015_13.java b/src/main/java/AoC2015_13.java index 3e00aeca..4d489e01 100644 --- a/src/main/java/AoC2015_13.java +++ b/src/main/java/AoC2015_13.java @@ -9,10 +9,7 @@ import com.github.pareronia.aoc.solution.Samples; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; - -public class AoC2015_13 extends SolutionBase { +public class AoC2015_13 extends SolutionBase { private AoC2015_13(final boolean debug) { super(debug); @@ -53,64 +50,64 @@ public static void main(final String[] args) throws Exception { } private static final String TEST = - "Alice would gain 54 happiness units by sitting next to Bob.\r\n" - + "Alice would lose 79 happiness units by sitting next to Carol.\r\n" - + "Alice would lose 2 happiness units by sitting next to David.\r\n" - + "Bob would gain 83 happiness units by sitting next to Alice.\r\n" - + "Bob would lose 7 happiness units by sitting next to Carol.\r\n" - + "Bob would lose 63 happiness units by sitting next to David.\r\n" - + "Carol would lose 62 happiness units by sitting next to Alice.\r\n" - + "Carol would gain 60 happiness units by sitting next to Bob.\r\n" - + "Carol would gain 55 happiness units by sitting next to David.\r\n" - + "David would gain 46 happiness units by sitting next to Alice.\r\n" - + "David would lose 7 happiness units by sitting next to Bob.\r\n" - + "David would gain 41 happiness units by sitting next to Carol."; -} - -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -final class Happiness { - private final int[][] happinessMatrix; + """ + Alice would gain 54 happiness units by sitting next to Bob.\r + Alice would lose 79 happiness units by sitting next to Carol.\r + Alice would lose 2 happiness units by sitting next to David.\r + Bob would gain 83 happiness units by sitting next to Alice.\r + Bob would lose 7 happiness units by sitting next to Carol.\r + Bob would lose 63 happiness units by sitting next to David.\r + Carol would lose 62 happiness units by sitting next to Alice.\r + Carol would gain 60 happiness units by sitting next to Bob.\r + Carol would gain 55 happiness units by sitting next to David.\r + David would gain 46 happiness units by sitting next to Alice.\r + David would lose 7 happiness units by sitting next to Bob.\r + David would gain 41 happiness units by sitting next to Carol."""; - public static Happiness fromInput(final List inputs) { - final Map map = new HashMap<>(); - final Map values = new HashMap<>(); - final MutableInt cnt = new MutableInt(0); - for (final String input : inputs) { - final String[] s = input.substring(0, input.length() - 1).split(" "); - final String d1 = s[0]; - final String d2 = s[10]; - final int idx1 = map.computeIfAbsent(d1, x -> cnt.getAndIncrement()); - final int idx2 = map.computeIfAbsent(d2, x -> cnt.getAndIncrement()); - final int value = Integer.parseInt(s[3]); - values.put(new int[] { idx1, idx2 }, "gain".equals(s[2]) ? value : -value); + record Happiness(int[][] happinessMatrix) { + + public static Happiness fromInput(final List inputs) { + final Map map = new HashMap<>(); + final Map values = new HashMap<>(); + final MutableInt cnt = new MutableInt(0); + for (final String input : inputs) { + final String[] s = input.substring(0, input.length() - 1).split(" "); + final String d1 = s[0]; + final String d2 = s[10]; + final int idx1 = map.computeIfAbsent(d1, x -> cnt.getAndIncrement()); + final int idx2 = map.computeIfAbsent(d2, x -> cnt.getAndIncrement()); + final int value = Integer.parseInt(s[3]); + values.put(new int[] { idx1, idx2 }, "gain".equals(s[2]) ? value : -value); + } + final int[][] happinessMatrix = new int[map.size() + 1][map.size() + 1]; + values.entrySet().stream() + .forEach(e -> { + happinessMatrix[e.getKey()[0]][e.getKey()[1]] = e.getValue(); + }); + return new Happiness(happinessMatrix); + } + + private int solve(final int size) { + final int[] idxs = IntStream.range(0, size).toArray(); + return IterTools.permutations(idxs) + .mapToInt(p -> + IntStream.range(0, p.length) + .map(i -> { + final int d1 = p[i]; + final int d2 = p[(i + 1) % p.length]; + return this.happinessMatrix[d1][d2] + this.happinessMatrix[d2][d1]; + }) + .sum()) + .max().orElseThrow(); + } + + public int getOptimalHappinessChangeWithoutMe() { + return solve(this.happinessMatrix.length - 1); + } + + public int getOptimalHappinessChangeWithMe() { + return solve(this.happinessMatrix.length); } - final int[][] happinessMatrix = new int[map.keySet().size() + 1][map.keySet().size() + 1]; - values.entrySet().stream() - .forEach(e -> { - happinessMatrix[e.getKey()[0]][e.getKey()[1]] = e.getValue(); - }); - return new Happiness(happinessMatrix); - } - - private int solve(final int size) { - final int[] idxs = IntStream.range(0, size).toArray(); - return IterTools.permutations(idxs) - .mapToInt(p -> - IntStream.range(0, p.length) - .map(i -> { - final int d1 = p[i]; - final int d2 = p[(i + 1) % p.length]; - return this.happinessMatrix[d1][d2] + this.happinessMatrix[d2][d1]; - }) - .sum()) - .max().orElseThrow(); - } - - public int getOptimalHappinessChangeWithoutMe() { - return solve(this.happinessMatrix.length - 1); - } - - public int getOptimalHappinessChangeWithMe() { - return solve(this.happinessMatrix.length); } -} + +} \ No newline at end of file diff --git a/src/main/java/AoC2015_14.java b/src/main/java/AoC2015_14.java index 5e539872..b6e15ebd 100644 --- a/src/main/java/AoC2015_14.java +++ b/src/main/java/AoC2015_14.java @@ -12,10 +12,7 @@ import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -public class AoC2015_14 extends SolutionBase, Integer, Integer> { +public class AoC2015_14 extends SolutionBase, Integer, Integer> { private AoC2015_14(final boolean debug) { super(debug); @@ -88,27 +85,21 @@ public static void main(final String[] args) throws Exception { "Comet can fly 14 km/s for 10 seconds, but then must rest for 127 seconds.\r\n" + "Dancer can fly 16 km/s for 11 seconds, but then must rest for 162 seconds." ); -} - -@RequiredArgsConstructor -@ToString -final class Reindeer { - private static final String REGEXP - = "([A-Za-z]+) can fly ([0-9]+) km/s for ([0-9]+) seconds," + - " but then must rest for ([0-9]+) seconds\\."; - private static final Pattern pattern = Pattern.compile(REGEXP); - final String name; - final int speed; - final int go; - final int stop; - - public static Reindeer fromInput(final String input) { - final Matcher m = pattern.matcher(input); - assertTrue(m.matches(), () -> "No match found"); - return new Reindeer(m.group(1), - Integer.valueOf(m.group(2)), - Integer.valueOf(m.group(3)), - Integer.valueOf(m.group(4))); + record Reindeer(String name, int speed, int go, int stop) { + + private static final String REGEXP + = "([A-Za-z]+) can fly ([0-9]+) km/s for ([0-9]+) seconds," + + " but then must rest for ([0-9]+) seconds\\."; + private static final Pattern pattern = Pattern.compile(REGEXP); + + public static Reindeer fromInput(final String input) { + final Matcher m = pattern.matcher(input); + assertTrue(m.matches(), () -> "No match found"); + return new Reindeer(m.group(1), + Integer.parseInt(m.group(2)), + Integer.parseInt(m.group(3)), + Integer.parseInt(m.group(4))); + } } } diff --git a/src/main/java/AoC2015_15.java b/src/main/java/AoC2015_15.java index a0c97d19..dbeef1d1 100644 --- a/src/main/java/AoC2015_15.java +++ b/src/main/java/AoC2015_15.java @@ -7,10 +7,7 @@ import com.github.pareronia.aoc.solution.Samples; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; - -public class AoC2015_15 extends SolutionBase { +public class AoC2015_15 extends SolutionBase { private AoC2015_15(final boolean debug) { super(debug); @@ -54,65 +51,62 @@ public static void main(final String[] args) throws Exception { private static final String TEST = "Butterscotch: capacity -1, durability -2, flavor 6, texture 3, calories 8\r\n" + "Cinnamon: capacity 2, durability 3, flavor -2, texture -1, calories 3"; -} -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -final class Ingredients { - private enum Property { CAPACITY, DURABILITY, FLAVOR, TEXTURE, CALORIES } - - private final int[][] ingredients; - - public static Ingredients fromInput(final List inputs) { - return new Ingredients(inputs.stream() - .map(line -> Utils.integerNumbers(line)) - .toArray(int[][]::new)); - } - - public int getHighestScore() { - return getMaximumScore(null); - } - - public int getHighestScoreWithCalorieLimit(final int limit) { - return getMaximumScore(limit); - } - - private int getMaximumScore(final Integer limit) { - return generateMeasures() - .mapToInt(m -> calculateScore(m, limit)) - .max().orElseThrow(); - } - - private Stream generateMeasures() { - final Stream.Builder builder = Stream.builder(); - for (int i = 0; i <= 100; i++) { - if (this.ingredients.length == 2) { - builder.add(new int[] { i, 100 - i }); - continue; - } - for (int j = 0; j <= 100 - i; j++) { - for (int k = 0; k <= 100 - i - j; k++) { - final int m = 100 - i - j - k; - builder.add(new int[] { i, j, k, m }); + record Ingredients(int[][] ingredients) { + private enum Property { CAPACITY, DURABILITY, FLAVOR, TEXTURE, CALORIES } + + public static Ingredients fromInput(final List inputs) { + return new Ingredients(inputs.stream() + .map(Utils::integerNumbers) + .toArray(int[][]::new)); + } + + public int getHighestScore() { + return getMaximumScore(null); + } + + public int getHighestScoreWithCalorieLimit(final int limit) { + return getMaximumScore(limit); + } + + private int getMaximumScore(final Integer limit) { + return generateMeasures() + .mapToInt(m -> calculateScore(m, limit)) + .max().orElseThrow(); + } + + private Stream generateMeasures() { + final Stream.Builder builder = Stream.builder(); + for (int i = 0; i <= 100; i++) { + if (this.ingredients.length == 2) { + builder.add(new int[] { i, 100 - i }); + continue; + } + for (int j = 0; j <= 100 - i; j++) { + for (int k = 0; k <= 100 - i - j; k++) { + final int m = 100 - i - j - k; + builder.add(new int[] { i, j, k, m }); + } } } + return builder.build(); } - return builder.build(); - } - private int getPropertyScore(final int[] measures, final Property p) { - return IntStream.range(0, this.ingredients.length) - .map(i -> this.ingredients[i][p.ordinal()] * measures[i]) - .sum(); - } - - private int calculateScore(final int[] measures, final Integer caloriesTarget) { - if (caloriesTarget != null - && getPropertyScore(measures, Property.CALORIES) != caloriesTarget) { - return 0; + private int getPropertyScore(final int[] measures, final Property p) { + return IntStream.range(0, this.ingredients.length) + .map(i -> this.ingredients[i][p.ordinal()] * measures[i]) + .sum(); + } + + private int calculateScore(final int[] measures, final Integer caloriesTarget) { + if (caloriesTarget != null + && getPropertyScore(measures, Property.CALORIES) != caloriesTarget) { + return 0; + } + return Stream.of(Property.values()) + .filter(p -> p != Property.CALORIES) + .mapToInt(p -> Math.max(0, getPropertyScore(measures, p))) + .reduce(1, (a, b) -> a * b); } - return Stream.of(Property.values()) - .filter(p -> p != Property.CALORIES) - .mapToInt(p -> Math.max(0, getPropertyScore(measures, p))) - .reduce(1, (a, b) -> a * b); } -} +} \ No newline at end of file diff --git a/src/main/java/AoC2015_16.java b/src/main/java/AoC2015_16.java index 762f163c..9416c73e 100644 --- a/src/main/java/AoC2015_16.java +++ b/src/main/java/AoC2015_16.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Map.Entry; +import java.util.Objects; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -10,12 +11,7 @@ import com.github.pareronia.aoc.IntegerSequence.Range; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.AccessLevel; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -public class AoC2015_16 extends SolutionBase, Integer, Integer> { +public class AoC2015_16 extends SolutionBase, Integer, Integer> { private static final int[] VALUES = new int[] { /* Thing.CHILDREN */ 3, @@ -93,49 +89,78 @@ public boolean matches(final Integer lhs, final int rhs) { } } } -} -enum Thing { - CHILDREN("children"), - CATS("cats"), - SAMOYEDS("samoyeds"), - POMERANIANS("pomeranians"), - AKITAS("akitas"), - VIZSLAS("vizslas"), - GOLDFISH("goldfish"), - TREES("trees"), - CARS("cars"), - PERFUMES("perfumes"); - - private final String value; - - Thing(final String value) { - this.value = value; - } - - public static final Thing fromString(final String string) { - return Stream.of(values()) - .filter(v -> v.value.equals(string)) - .findFirst().orElseThrow(); + enum Thing { + CHILDREN("children"), + CATS("cats"), + SAMOYEDS("samoyeds"), + POMERANIANS("pomeranians"), + AKITAS("akitas"), + VIZSLAS("vizslas"), + GOLDFISH("goldfish"), + TREES("trees"), + CARS("cars"), + PERFUMES("perfumes"); + + private final String value; + + Thing(final String value) { + this.value = value; + } + + public static final Thing fromString(final String string) { + return Stream.of(values()) + .filter(v -> v.value.equals(string)) + .findFirst().orElseThrow(); + } } -} -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -@EqualsAndHashCode(onlyExplicitlyIncluded = true) -@Getter -final class AuntSue { - @EqualsAndHashCode.Include - private final int nbr; - private final Integer[] things; - - public static AuntSue fromInput(final String s) { - final String[] splits = s.replaceAll("[,:]", "").split(" "); - final Integer[] things = new Integer[Thing.values().length]; - Range.range(2, splits.length, 2).intStream() - .forEach(i -> { - final int idx = Thing.fromString(splits[i]).ordinal(); - things[idx] = Integer.valueOf(splits[i + 1]); - }); - return new AuntSue(Integer.parseInt(splits[1]), things); + static final class AuntSue { + private final int nbr; + private final Integer[] things; + + protected AuntSue(int nbr, Integer[] things) { + this.nbr = nbr; + this.things = things; + } + + public static AuntSue fromInput(final String s) { + final String[] splits = s.replaceAll("[,:]", "").split(" "); + final Integer[] things = new Integer[Thing.values().length]; + Range.range(2, splits.length, 2).intStream() + .forEach(i -> { + final int idx = Thing.fromString(splits[i]).ordinal(); + things[idx] = Integer.valueOf(splits[i + 1]); + }); + return new AuntSue(Integer.parseInt(splits[1]), things); + } + + public int getNbr() { + return nbr; + } + + public Integer[] getThings() { + return things; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AuntSue other = (AuntSue) obj; + return nbr == other.nbr; + } + + @Override + public int hashCode() { + return Objects.hash(nbr); + } } } diff --git a/src/main/java/AoC2015_17.java b/src/main/java/AoC2015_17.java index e11e20a0..c3c9a862 100644 --- a/src/main/java/AoC2015_17.java +++ b/src/main/java/AoC2015_17.java @@ -39,7 +39,7 @@ private List> getCoCos(final List containers, final int e } final List> cocos = new ArrayList<>(); for (int i = minimalContainers.size(); i < containers.size(); i++) { - combinations(containers.size(), i).forEach(c -> { + combinations(containers.size(), i).stream().forEach(c -> { if (Arrays.stream(c).map(containers::get).sum() == eggnogVolume) { cocos.add(Arrays.stream(c).mapToObj(containers::get).collect(toList())); } @@ -78,10 +78,11 @@ public static void main(final String[] args) throws Exception { } private static final List TEST = splitLines( - "20\r\n" - + "15\r\n" - + "10\r\n" - + "5\r\n" - + "5" + """ + 20\r + 15\r + 10\r + 5\r + 5""" ); } diff --git a/src/main/java/AoC2015_18.java b/src/main/java/AoC2015_18.java index 075adced..823d32b9 100644 --- a/src/main/java/AoC2015_18.java +++ b/src/main/java/AoC2015_18.java @@ -1,23 +1,22 @@ import static com.github.pareronia.aoc.StringOps.splitLines; +import static java.util.stream.Collectors.counting; +import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toSet; -import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.IntStream; -import com.github.pareronia.aoc.Grid; import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.SetUtils; +import com.github.pareronia.aoc.game_of_life.GameOfLife; +import com.github.pareronia.aoc.game_of_life.GameOfLife.Type; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; +public class AoC2015_18 extends SolutionBase { -public class AoC2015_18 extends SolutionBase { - private AoC2015_18(final boolean debug) { super(debug); } @@ -31,131 +30,119 @@ public static final AoC2015_18 createDebug() { } @Override - protected GameOfLife parseInput(final List inputs) { - return GameOfLife.fromInput(inputs); + protected FixedGrid parseInput(final List inputs) { + return FixedGrid.fromInput(inputs); } - private final Map> neighboursCache = new HashMap<>(); - private Set findNeighbours(final GameOfLife gameOfLife, final Cell c) { - if (neighboursCache.containsKey(c)) { - return neighboursCache.get(c); - } - final Set neighbours = c.allNeighbours() - .filter(n -> n.getRow() >= 0) - .filter(n -> n.getRow() < gameOfLife.height) - .filter(n -> n.getCol() >= 0) - .filter(n -> n.getCol() < gameOfLife.width) - .collect(toSet()); - neighboursCache.put(c, neighbours); - return neighbours; - } - - private void nextGen(final GameOfLife gameOfLife, final Set stuckPositions) { - final Set toOn = new HashSet<>(); - final Set toOff = new HashSet<>(); - for (final Cell cell : gameOfLife.grid) { - final Set neighbours = findNeighbours(gameOfLife, cell); - final int neighbours_on = (int) neighbours.stream().filter(gameOfLife.grid::contains).count(); - if (neighbours_on == 2 || neighbours_on == 3 || stuckPositions.contains(cell)) { - toOn.add(cell); - } else { - toOff.add(cell); - } - neighbours.stream() - .filter(n -> !gameOfLife.grid.contains(n)) - .filter(n -> (int) findNeighbours(gameOfLife, n).stream().filter(gameOfLife.grid::contains).count() == 3) - .forEach(toOn::add); - } - for (final Cell cell : toOn) { - gameOfLife.grid.add(cell); - } - for (final Cell cell : toOff) { - gameOfLife.grid.remove(cell); - } - } - - private int solve1(final GameOfLife gameOfLife, final int generations) { + @SuppressWarnings("unchecked") + private int solve1(final FixedGrid grid, final int generations) { + GameOfLife gol = new GameOfLife<>( + grid, + GameOfLife.classicRules, + grid.initialAlive); for (int i = 0; i < generations; i++) { - nextGen(gameOfLife, Set.of()); + gol = gol.nextGeneration(); } - return gameOfLife.grid.size(); + return gol.alive().size(); } @Override - public Integer solvePart1(final GameOfLife gameOfLife) { - return solve1(GameOfLife.clone(gameOfLife), 100); + public Integer solvePart1(final FixedGrid input) { + return solve1(input, 100); } - private int solve2(final GameOfLife gameOfLife, final int generations) { - final Set stuckPositions = Set.of( - Grid.ORIGIN, - Cell.at(gameOfLife.height - 1, 0), - Cell.at(0, gameOfLife.width - 1), - Cell.at(gameOfLife.height- 1, gameOfLife.width - 1)); - for (final Cell stuck : stuckPositions) { - gameOfLife.grid.add(stuck); - } + @SuppressWarnings("unchecked") + private int solve2(final FixedGrid grid, final int generations) { + GameOfLife gol = new GameOfLife<>( + grid, + GameOfLife.classicRules, + SetUtils.union(grid.initialAlive, grid.stuckPositions)); for (int i = 0; i < generations; i++) { - nextGen(gameOfLife, stuckPositions); + gol = gol.nextGeneration(); + gol = gol.withAlive(SetUtils.union(gol.alive(), grid.stuckPositions)); } - return gameOfLife.grid.size(); + return gol.alive().size(); } @Override - public Integer solvePart2(final GameOfLife gameOfLife) { - return solve2(GameOfLife.clone(gameOfLife), 100); + public Integer solvePart2(final FixedGrid input) { + return solve2(input, 100); } public static void main(final String[] args) throws Exception { final AoC2015_18 test = AoC2015_18.createDebug(); - final GameOfLife input = test.parseInput(TEST); - assert test.solve1(GameOfLife.clone(input), 4) == 4; - assert test.solve2(GameOfLife.clone(input), 5) == 17; + final FixedGrid input = test.parseInput(TEST); + assert test.solve1(input, 4) == 4; + assert test.solve2(input, 5) == 17; AoC2015_18.create().run(); } private static final List TEST = splitLines( - ".#.#.#\r\n" + - "...##.\r\n" + - "#....#\r\n" + - "..#...\r\n" + - "#.#..#\r\n" + - "####.." + """ + .#.#.#\r + ...##.\r + #....#\r + ..#...\r + #.#..#\r + ####..""" ); -} -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -final class GameOfLife implements Cloneable { - private static final char ON = '#'; - - final Set grid; - final int height; - final int width; - - public static GameOfLife fromInput(final List inputs) { - final int height = inputs.size(); - final int width = inputs.get(0).length(); - final Set grid = IntStream.range(0, height).boxed() - .flatMap(r -> IntStream.range(0, width).mapToObj(c -> Cell.at(r, c))) - .filter(c -> inputs.get(c.getRow()).charAt(c.getCol()) == ON) - .collect(toSet()); - return new GameOfLife(Collections.unmodifiableSet(grid), height, width); - } - - public static GameOfLife clone(final GameOfLife gameOfLife) { - try { - return gameOfLife.clone(); - } catch (final CloneNotSupportedException e) { - throw new RuntimeException(e); + public static final class FixedGrid implements Type { + + private static final char ON = '#'; + + private final Map> neighboursCache = new HashMap<>(); + private final int height; + private final int width; + private final Set initialAlive; + private final Set stuckPositions; + + private FixedGrid( + final int height, + final int width, + final Set initialAlive, + final Set stuckPositions + ) { + this.height = height; + this.width = width; + this.initialAlive = initialAlive; + this.stuckPositions = stuckPositions; } - } - @Override - protected GameOfLife clone() throws CloneNotSupportedException { - return new GameOfLife( - grid.stream().collect(toSet()), - height, - width); + public static FixedGrid fromInput(final List input) { + final int height = input.size(); + final int width = input.get(0).length(); + final Set alive = IntStream.range(0, height).boxed() + .flatMap(r -> IntStream.range(0, width).mapToObj(c -> Cell.at(r, c))) + .filter(c -> input.get(c.getRow()).charAt(c.getCol()) == ON) + .collect(toSet()); + final Set stuckPositions = Set.of( + Cell.at(0, 0), + Cell.at(height - 1, 0), + Cell.at(0, width - 1), + Cell.at(height- 1, width - 1)); + return new FixedGrid(height, width, alive, stuckPositions); + } + + @Override + public Map getNeighbourCounts(final Set alive) { + return alive.stream() + .flatMap(cell -> neighbours(cell).stream()) + .collect(groupingBy(cell -> cell, counting())); + } + + private Set neighbours(final Cell c) { + return this.neighboursCache.computeIfAbsent(c, this::getNeighbours); + } + + private Set getNeighbours(final Cell c) { + return c.allNeighbours() + .filter(n -> n.getRow() >= 0) + .filter(n -> n.getRow() < this.height) + .filter(n -> n.getCol() >= 0) + .filter(n -> n.getCol() < this.width) + .collect(toSet()); + } } } diff --git a/src/main/java/AoC2015_19.java b/src/main/java/AoC2015_19.java index d660761f..ca0aa114 100644 --- a/src/main/java/AoC2015_19.java +++ b/src/main/java/AoC2015_19.java @@ -4,6 +4,7 @@ import static java.util.stream.Collectors.toList; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -13,9 +14,8 @@ import com.github.pareronia.aoc.solution.Samples; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.RequiredArgsConstructor; - -public class AoC2015_19 extends SolutionBase { +public class AoC2015_19 + extends SolutionBase { private AoC2015_19(final boolean debug) { super(debug); @@ -48,13 +48,10 @@ private Set runReplacement( final String c = m.substring(i, i + 1); if (replacements.containsKey(c)) { key = c; - } else if (i + 2 <= m.length()) { - final String cc = m.substring(i, i + 2); - if (replacements.containsKey(cc)) { - key = cc; - } else { - continue; - } + } else if (replacements.containsKey(m.substring(i, Math.min(m.length(), i + 2)))) { + key = m.substring(i, Math.min(m.length(), i + 2)); + } else { + continue; } for (final String r : replacements.get(key)) { molecules.add(m.substring(0, i) + r + m.substring(i + key.length())); @@ -129,27 +126,30 @@ public static void main(final String[] args) throws Exception { assert test.solve2bis(test.parseInput(TEST4)) == 3; assert test.solve2bis(test.parseInput(TEST5)) == 6; - AoC2015_19.create().run(); + AoC2015_19.createDebug().run(); } private static final String TEST1 = - "H => HO\r\n" - + "H => OH\r\n" - + "O => HH\r\n" - + "\r\n" - + "HOH"; + """ + H => HO\r + H => OH\r + O => HH\r + \r + HOH"""; private static final String TEST2 = - "H => HO\r\n" - + "H => OH\r\n" - + "O => HH\r\n" - + "\r\n" - + "HOHOHO"; + """ + H => HO\r + H => OH\r + O => HH\r + \r + HOHOHO"""; private static final String TEST3 = - "H => HO\r\n" - + "H => OH\r\n" - + "Oo => HH\r\n" - + "\r\n" - + "HOHOHO"; + """ + H => HO\r + H => OH\r + Oo => HH\r + \r + HOHOoHO"""; private static final List TEST4 = splitLines( "e => H\r\n" + "e => O\r\n" @@ -168,21 +168,20 @@ public static void main(final String[] args) throws Exception { + "\r\n" + "HOHOHO" ); -} -@RequiredArgsConstructor -final class ReplacementsAndMolecule { - - final Map> replacements; - final String molecule; - - public static ReplacementsAndMolecule fromInput(final List inputs) { - final List> blocks = StringOps.toBlocks(inputs); - final Map> replacements = blocks.get(0).stream() - .map(s -> s.split(" => ")) - .collect(groupingBy(sp -> sp[0], mapping(s -> s[1], toList()))); - assert blocks.get(1).size() == 1; - final String molecule = blocks.get(1).get(0); - return new ReplacementsAndMolecule(replacements, molecule); + record ReplacementsAndMolecule( + Map> replacements, + String molecule + ) { + public static ReplacementsAndMolecule fromInput(final List inputs) { + final List> blocks = StringOps.toBlocks(inputs); + // replacement result depends on order of input -> using LinkedHashMap + final Map> replacements = blocks.get(0).stream() + .map(s -> s.split(" => ")) + .collect(groupingBy(sp -> sp[0], LinkedHashMap::new, mapping(s -> s[1], toList()))); + assert blocks.get(1).size() == 1; + final String molecule = blocks.get(1).get(0); + return new ReplacementsAndMolecule(replacements, molecule); + } } } diff --git a/src/main/java/AoC2015_21.java b/src/main/java/AoC2015_21.java index 1078070d..31f0abf1 100644 --- a/src/main/java/AoC2015_21.java +++ b/src/main/java/AoC2015_21.java @@ -9,17 +9,13 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.function.Predicate; import com.github.pareronia.aoc.SetUtils; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public class AoC2015_21 extends SolutionBase { protected AoC2015_21(final boolean debug) { @@ -47,14 +43,14 @@ private int solve( .sorted(comparator) .filter(filter) .findFirst() - .map(Game.PlayerConfig::getTotalCost) + .map(Game.PlayerConfig::totalCost) .orElseThrow(() -> new IllegalStateException("Unsolvable")); } @Override protected Integer solvePart1(final Game game) { return solve( - game.getPlayerConfigs(), + game.playerConfigs(), game.lowestCost(), game.winsFromBoss()); } @@ -62,7 +58,7 @@ protected Integer solvePart1(final Game game) { @Override protected Integer solvePart2(final Game game) { return solve( - game.getPlayerConfigs(), + game.playerConfigs(), game.lowestCost().reversed(), game.winsFromBoss().negate()); } @@ -71,11 +67,7 @@ public static void main(final String[] args) throws Exception { AoC2015_21.create().run(); } - @RequiredArgsConstructor - static final class Game { - private final Boss boss; - @Getter - private final List playerConfigs; + record Game(Boss boss, List playerConfigs) { public static Game fromInput(final List inputs) { return new Game(parse(inputs), setUpPlayerConfigs(setUpShop())); @@ -130,21 +122,10 @@ private static List setUpPlayerConfigs(final Shop shop) { return configs; } - @RequiredArgsConstructor - @Getter - @EqualsAndHashCode(onlyExplicitlyIncluded = true) - @ToString - private static final class ShopItem { + record ShopItem(Type type, String name, int cost, int damage, int armor) { + private enum Type { WEAPON, ARMOR, RING, NONE } - @EqualsAndHashCode.Include - private final Type type; - @EqualsAndHashCode.Include - private final String name; - private final Integer cost; - private final Integer damage; - private final Integer armor; - public static ShopItem weapon( final String name, final Integer cost, final Integer damage) { return new ShopItem(Type.WEAPON, name, cost, damage, 0); @@ -173,12 +154,29 @@ public boolean isArmor() { public boolean isRing() { return this.type == Type.RING; } + + @Override + public int hashCode() { + return Objects.hash(name, type); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ShopItem other = (ShopItem) obj; + return Objects.equals(name, other.name) && type == other.type; + } } - @RequiredArgsConstructor - @ToString - private static final class Shop { - private final Set items; + record Shop(Set items) { public Set getWeapons() { return this.items.stream() @@ -199,7 +197,7 @@ public Set> getRings() { .filter(ShopItem::isRing) .collect(toList()); final Set> ringCombinations = new HashSet<>(); - combinations(rings.size(), 2).forEach(indices -> { + combinations(rings.size(), 2).stream().forEach(indices -> { ringCombinations.add( Set.of(rings.get(indices[0]), rings.get(indices[1]))); }); @@ -214,44 +212,34 @@ public Set> getRings() { } } - @ToString - @Getter - static final class PlayerConfig { - private final int hitPoints; - private final int totalCost; - private final int totalDamage; - private final int totalArmor; + record PlayerConfig( + int hitPoints, int totalCost, int totalDamage, int totalArmor) { public PlayerConfig(final Set items) { - this.hitPoints = 100; - this.totalCost = items.stream().mapToInt(ShopItem::getCost).sum(); - this.totalDamage = items.stream().mapToInt(ShopItem::getDamage).sum(); - this.totalArmor = items.stream().mapToInt(ShopItem::getArmor).sum(); + this( + 100, + items.stream().mapToInt(ShopItem::cost).sum(), + items.stream().mapToInt(ShopItem::damage).sum(), + items.stream().mapToInt(ShopItem::armor).sum()); } } - @RequiredArgsConstructor - @Getter - private static final class Boss { - private final int hitPoints; - private final int damage; - private final int armor; - } + record Boss(int hitPoints, int damage, int armor) { } public Comparator lowestCost() { - return comparing(Game.PlayerConfig::getTotalCost); + return comparing(Game.PlayerConfig::totalCost); } public Predicate winsFromBoss() { return playerConfig -> { - int playerHP = playerConfig.getHitPoints(); - int bossHP = this.boss.getHitPoints(); + int playerHP = playerConfig.hitPoints(); + int bossHP = this.boss.hitPoints(); while (true) { - bossHP -= Math.max(playerConfig.getTotalDamage() - this.boss.getArmor(), 1); + bossHP -= Math.max(playerConfig.totalDamage() - this.boss.armor(), 1); if (bossHP <= 0) { return true; } - playerHP -= Math.max(this.boss.getDamage() - playerConfig.getTotalArmor(), 1); + playerHP -= Math.max(this.boss.damage() - playerConfig.totalArmor(), 1); if (playerHP <= 0) { return false; } diff --git a/src/main/java/AoC2015_22.java b/src/main/java/AoC2015_22.java index 67afc56c..c4cf5e52 100644 --- a/src/main/java/AoC2015_22.java +++ b/src/main/java/AoC2015_22.java @@ -11,16 +11,11 @@ import java.util.Map; import java.util.PriorityQueue; import java.util.Set; -import java.util.function.Supplier; +import com.github.pareronia.aoc.solution.Logger; +import com.github.pareronia.aoc.solution.LoggerEnabled; import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.Getter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import lombok.With; - public class AoC2015_22 extends SolutionBase { private AoC2015_22(final boolean debug) { @@ -42,23 +37,19 @@ protected Game parseInput(final List inputs) { @Override protected Long solvePart1(final Game game) { - return game.setUpEasyFight(this.debug).run(); + return game.setUpEasyFight(this.logger).run(); } @Override protected Long solvePart2(final Game game) { - return game.setUpHardFight(this.debug).run(); + return game.setUpHardFight(this.logger).run(); } public static void main(final String[] args) throws Exception { AoC2015_22.create().run(); } - @RequiredArgsConstructor - static final class Game { - private final Boss boss; - private final Player player; - private final Spells spells; + record Game(Boss boss, Player player, Spells spells) { public static Game fromInput(final List inputs) { return new Game(parse(inputs), setUpPlayer(), setUpSpells()); @@ -83,44 +74,44 @@ static Spells setUpSpells() { "Missile", 53, turn -> turn - .withManaSpent(turn.getManaSpent() + 53) - .withBoss(turn.getBoss() - .withHitpoints(turn.getBoss().getHitpoints() - 4)) - .withPlayer(turn.getPlayer() - .withMana(turn.getPlayer().getMana() - 53)), + .withManaSpent(turn.manaSpent() + 53) + .withBoss(turn.boss() + .withHitpoints(turn.boss().hitpoints() - 4)) + .withPlayer(turn.player() + .withMana(turn.player().mana() - 53)), turn -> turn )); spells.add(new Spell( "Drain", 73, turn -> turn - .withManaSpent(turn.getManaSpent() + 73) - .withBoss(turn.getBoss() - .withHitpoints(turn.getBoss().getHitpoints() - 2)) - .withPlayer(turn.getPlayer() - .withMana(turn.getPlayer().getMana() - 73) - .withHitpoints(turn.getPlayer().getHitpoints() + 2)), + .withManaSpent(turn.manaSpent() + 73) + .withBoss(turn.boss() + .withHitpoints(turn.boss().hitpoints() - 2)) + .withPlayer(turn.player() + .withMana(turn.player().mana() - 73) + .withHitpoints(turn.player().hitpoints() + 2)), turn -> turn )); spells.add(new Spell( "Shield", 113, turn -> turn - .withManaSpent(turn.getManaSpent() + 113) - .withPlayer(turn.getPlayer() + .withManaSpent(turn.manaSpent() + 113) + .withPlayer(turn.player() .withShieldTimer(6) .withArmor(7) - .withMana(turn.getPlayer().getMana() - 113)), + .withMana(turn.player().mana() - 113)), turn -> { - final Integer shieldTimer = turn.getPlayer().getShieldTimer(); + final Integer shieldTimer = turn.player().shieldTimer(); assert shieldTimer >= 0 && shieldTimer <= 6; if (shieldTimer > 1) { return turn - .withPlayer(turn.getPlayer() + .withPlayer(turn.player() .withShieldTimer(shieldTimer - 1)); } else { return turn - .withPlayer(turn.getPlayer() + .withPlayer(turn.player() .withArmor(0) .withShieldTimer(0)); } @@ -130,17 +121,17 @@ static Spells setUpSpells() { "Poison", 173, turn -> turn - .withManaSpent(turn.getManaSpent() + 173) - .withPlayer(turn.getPlayer() + .withManaSpent(turn.manaSpent() + 173) + .withPlayer(turn.player() .withPoisonTimer(6) - .withMana(turn.getPlayer().getMana() - 173)), + .withMana(turn.player().mana() - 173)), turn -> { - final Integer poisonTimer = turn.getPlayer().getPoisonTimer(); + final Integer poisonTimer = turn.player().poisonTimer(); assert poisonTimer >= 0 && poisonTimer <= 6; return turn - .withBoss(turn.getBoss() - .withHitpoints(turn.getBoss().getHitpoints() - 3)) - .withPlayer(turn.getPlayer() + .withBoss(turn.boss() + .withHitpoints(turn.boss().hitpoints() - 3)) + .withPlayer(turn.player() .withPoisonTimer(poisonTimer - 1)); } )); @@ -148,31 +139,31 @@ static Spells setUpSpells() { "Recharge", 229, turn -> turn - .withManaSpent(turn.getManaSpent() + 229) - .withPlayer(turn.getPlayer() + .withManaSpent(turn.manaSpent() + 229) + .withPlayer(turn.player() .withRechargeTimer(5) - .withMana(turn.getPlayer().getMana() - 229)), + .withMana(turn.player().mana() - 229)), turn -> { - final Integer rechargeTimer = turn.getPlayer().getRechargeTimer(); + final Integer rechargeTimer = turn.player().rechargeTimer(); assert rechargeTimer >= 0 && rechargeTimer <= 5; return turn - .withPlayer(turn.getPlayer() - .withMana(turn.getPlayer().getMana() + 101) + .withPlayer(turn.player() + .withMana(turn.player().mana() + 101) .withRechargeTimer(rechargeTimer - 1)); } )); return new Spells(spells); } - public Fight setUpEasyFight(final boolean debug) { - return setUpFight(false, debug); + public Fight setUpEasyFight(final Logger logger) { + return setUpFight(false, logger); } - public Fight setUpHardFight(final boolean debug) { - return setUpFight(true, debug); + public Fight setUpHardFight(final Logger logger) { + return setUpFight(true, logger); } - private Fight setUpFight(final boolean hard, final boolean debug) { + private Fight setUpFight(final boolean hard, final Logger logger) { return new Fight( this.spells, new Fight.LeastCostlyFirstTurnStorage(), @@ -180,18 +171,18 @@ private Fight setUpFight(final boolean hard, final boolean debug) { this.player, this.boss, hard, - debug); + logger); } - @RequiredArgsConstructor - static final class Fight { - private final Spells spells; - private final TurnStorage turnStorage; - private final SpellSelector spellSelector; - private final Player player; - private final Boss boss; - private final boolean difficultyHard; - private final boolean debug; + record Fight( + Spells spells, + TurnStorage turnStorage, + SpellSelector spellSelector, + Player player, + Boss boss, + boolean difficultyHard, + Logger logger + ) implements LoggerEnabled { public long run() { this.turnStorage.push(new Turn(0, this.boss, this.player)); @@ -200,32 +191,32 @@ public long run() { log(() -> "\n-- Player turn --"); logTurn(oldTurn); if (this.difficultyHard) { - oldTurn = oldTurn.withPlayer(oldTurn.getPlayer() - .withHitpoints(oldTurn.getPlayer().getHitpoints() - 1)); + oldTurn = oldTurn.withPlayer(oldTurn.player() + .withHitpoints(oldTurn.player().hitpoints() - 1)); if (isGameOver(oldTurn)) { continue; } } oldTurn = applyActiveSpellsEffect(oldTurn); if (isGameOver(oldTurn)) { - return oldTurn.getManaSpent(); + return oldTurn.manaSpent(); } final List available = new ArrayList<>(this.spellSelector.select(oldTurn)); - available.sort(comparing(Spell::getCost)); + available.sort(comparing(Spell::cost)); for (final Spell spell : available) { - log(() -> String.format("Player casts %s.", spell.getName())); + log(() -> String.format("Player casts %s.", spell.name())); Turn newTurn = spell.cast.apply(oldTurn); if (isGameOver(newTurn)) { - return newTurn.getManaSpent(); + return newTurn.manaSpent(); } log(() -> "\n-- Boss turn --"); logTurn(newTurn); newTurn = applyActiveSpellsEffect(newTurn); if (isGameOver(newTurn)) { - return newTurn.getManaSpent(); + return newTurn.manaSpent(); } newTurn = applyBossAttack(newTurn); - if (newTurn.getPlayer().getHitpoints() > 0) { + if (newTurn.player().hitpoints() > 0) { this.turnStorage.push(newTurn); } } @@ -233,20 +224,20 @@ public long run() { } private Turn applyActiveSpellsEffect(Turn turn) { - final Integer shieldTimer = turn.getPlayer().getShieldTimer(); + final Integer shieldTimer = turn.player().shieldTimer(); if (shieldTimer > 0) { turn = this.spells.getByName("Shield").effect.apply(turn); log(() -> String.format("Shield's timer is now %d.", shieldTimer)); } - final Integer poisonTimer = turn.getPlayer().getPoisonTimer(); + final Integer poisonTimer = turn.player().poisonTimer(); if (poisonTimer > 0) { - turn = this.spells.getByName("Poison").getEffect().apply(turn); + turn = this.spells.getByName("Poison").effect().apply(turn); log(() -> String.format("Poison deals 3 damage; its timer is now %d.", poisonTimer)); } - final Integer rechargeTimer = turn.getPlayer().getRechargeTimer(); + final Integer rechargeTimer = turn.player().rechargeTimer(); if (rechargeTimer > 0) { - turn = this.spells.getByName("Recharge").getEffect().apply(turn); + turn = this.spells.getByName("Recharge").effect().apply(turn); log(() -> String.format("Recharge provides 101 mana; its timer is now %d.", rechargeTimer)); } @@ -254,13 +245,13 @@ private Turn applyActiveSpellsEffect(Turn turn) { } private Turn applyBossAttack(final Turn turnIn) { - final int bossDamage = Math.max(turnIn.getBoss().getDamage() - - turnIn.getPlayer().getArmor(), + final int bossDamage = Math.max(turnIn.boss().damage() + - turnIn.player().armor(), 1); log(() -> String.format("Boss attacks for %d damage.", bossDamage)); return turnIn - .withPlayer(turnIn.getPlayer() - .withHitpoints(turnIn.getPlayer().getHitpoints() - bossDamage)); + .withPlayer(turnIn.player() + .withHitpoints(turnIn.player().hitpoints() - bossDamage)); } private boolean isGameOver(final Turn turn) { @@ -274,22 +265,20 @@ private boolean isGameOver(final Turn turn) { return FALSE; } - protected void log(final Supplier supplier) { - if (!debug) { - return; - } - System.out.println(supplier.get()); - } - private void logTurn(final Turn playerTurn) { log(() -> String.format("- Player has %d hit points, %d armor, %d mana", - playerTurn.getPlayer().getHitpoints(), - playerTurn.getPlayer().getArmor(), - playerTurn.getPlayer().getMana())); + playerTurn.player().hitpoints(), + playerTurn.player().armor(), + playerTurn.player().mana())); log(() -> String.format("- Boss has %d hit points", - playerTurn.getBoss().getHitpoints())); + playerTurn.boss().hitpoints())); } + @Override + public Logger getLogger() { + return this.logger; + } + interface TurnStorage { void push(Turn turn); Turn pop(); @@ -297,7 +286,7 @@ interface TurnStorage { static final class LeastCostlyFirstTurnStorage implements TurnStorage { private final PriorityQueue turns - = new PriorityQueue<>(comparing(Turn::getManaSpent)); + = new PriorityQueue<>(comparing(Turn::manaSpent)); @Override public void push(final Turn turn) { @@ -316,33 +305,30 @@ interface SpellSelector { Set select(Turn turn); } - @RequiredArgsConstructor static final class AvailableSpellSelector implements SpellSelector { private final Spells spells; + public AvailableSpellSelector(final Spells spells) { + this.spells = spells; + } + @Override public Set select(final Turn turn) { - final Player player = turn.getPlayer(); + final Player player = turn.player(); return spells.getAll().stream() - .filter(s -> !(s.getName().equals("Poison") - && player.getPoisonTimer() > 0)) - .filter(s -> !(s.getName().equals("Recharge") - && player.getRechargeTimer() > 0)) - .filter(s -> !(s.getName().equals("Shield") - && player.getShieldTimer() > 0)) - .filter(s -> s.getCost() <= player.getMana()) + .filter(s -> !(s.name().equals("Poison") + && player.poisonTimer() > 0)) + .filter(s -> !(s.name().equals("Recharge") + && player.rechargeTimer() > 0)) + .filter(s -> !(s.name().equals("Shield") + && player.shieldTimer() > 0)) + .filter(s -> s.cost() <= player.mana()) .collect(toSet()); } } } - @RequiredArgsConstructor - @Getter - @ToString(callSuper = true) - static final class Turn { - @With private final Integer manaSpent; - @With private final Boss boss; - @With private final Player player; + record Turn(Integer manaSpent, Boss boss, Player player) { public boolean isBossDead() { return this.boss.isDead(); @@ -351,30 +337,37 @@ public boolean isBossDead() { public boolean isPlayerDead() { return this.player.isDead(); } + + public Turn withManaSpent(final int manaSpent) { + return new Turn(manaSpent, this.boss, this.player); + } + + public Turn withBoss(final Boss boss) { + return new Turn(this.manaSpent, boss, this.player); + } + + public Turn withPlayer(final Player player) { + return new Turn(this.manaSpent, this.boss, player); + } } @FunctionalInterface interface SpellEffect { Turn apply(Turn turn); } - @RequiredArgsConstructor - @Getter - static final class Spell { - private final String name; - private final Integer cost; - private final SpellEffect cast; - private final SpellEffect effect; - } + record Spell( + String name, Integer cost, SpellEffect cast, SpellEffect effect + ) {} static final class Spells { private final Map spellsByName; public Spells(final Set spells) { this.spellsByName = spells.stream() - .collect(toMap(Spell::getName, identity())); + .collect(toMap(Spell::name, identity())); } - public Spell getByName(@NonNull final String name) { + public Spell getByName(final String name) { return this.spellsByName.get(name); } @@ -383,32 +376,58 @@ public Set getAll() { } } - @RequiredArgsConstructor - @Getter - @ToString(callSuper = true) - static final class Player { - @With private final Integer hitpoints; - @With private final Integer mana; - @With private final Integer armor; - @With private final Integer shieldTimer; - @With private final Integer poisonTimer; - @With private final Integer rechargeTimer; - + record Player( + Integer hitpoints, + Integer mana, + Integer armor, + Integer shieldTimer, + Integer poisonTimer, + Integer rechargeTimer + ) { public boolean isDead() { return this.hitpoints <= 0; } + + public Player withHitpoints(final int hitpoints) { + return new Player(hitpoints, this.mana, this.armor, + this.shieldTimer, this.poisonTimer, this.rechargeTimer); + } + + public Player withMana(final int mana) { + return new Player(this.hitpoints, mana, this.armor, + this.shieldTimer, this.poisonTimer, this.rechargeTimer); + } + + public Player withArmor(final int armor) { + return new Player(this.hitpoints, this.mana, armor, + this.shieldTimer, this.poisonTimer, this.rechargeTimer); + } + + public Player withShieldTimer(final int shieldTimer) { + return new Player(this.hitpoints, this.mana, this.armor, + shieldTimer, this.poisonTimer, this.rechargeTimer); + } + + public Player withPoisonTimer(final int poisonTimer) { + return new Player(this.hitpoints, this.mana, this.armor, + this.shieldTimer, poisonTimer, this.rechargeTimer); + } + + public Player withRechargeTimer(final int rechargeTimer) { + return new Player(this.hitpoints, this.mana, this.armor, + this.shieldTimer, this.poisonTimer, rechargeTimer); + } } - @RequiredArgsConstructor - @Getter - @ToString(callSuper = true) - static final class Boss { - @With private final Integer hitpoints; - private final Integer damage; + record Boss(Integer hitpoints, Integer damage) { public boolean isDead() { return this.hitpoints <= 0; } + + public Boss withHitpoints(final int hitpoints) { + return new Boss(hitpoints, this.damage); + } } } } \ No newline at end of file diff --git a/src/main/java/AoC2016_02.java b/src/main/java/AoC2016_02.java index 61644c1d..dcaaa5e5 100644 --- a/src/main/java/AoC2016_02.java +++ b/src/main/java/AoC2016_02.java @@ -14,8 +14,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public class AoC2016_02 extends AoCBase { private static final Map LAYOUT1 = Map.of( @@ -93,11 +91,18 @@ public static void main(final String[] args) throws Exception { "UUUUD" ); - @RequiredArgsConstructor(staticName = "fromLayout") private static final class Keypad { private final Map positions; private Position current = Position.ORIGIN; + private Keypad(final Map positions) { + this.positions = positions; + } + + public static Keypad fromLayout(final Map positions) { + return new Keypad(positions); + } + public String executeInstructions(final List> directions) { return directions.stream() .map(this::executeInstruction) diff --git a/src/main/java/AoC2016_03.java b/src/main/java/AoC2016_03.java index f35b28e7..f4dced26 100644 --- a/src/main/java/AoC2016_03.java +++ b/src/main/java/AoC2016_03.java @@ -1,70 +1,77 @@ -import static java.util.stream.Collectors.toList; +import static com.github.pareronia.aoc.IntegerSequence.Range.range; +import java.util.Arrays; import java.util.List; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.StringUtils; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2016_03 extends AoCBase { +public class AoC2016_03 extends SolutionBase { - private final List> triangles; - - private AoC2016_03(List input, boolean debug) { + private AoC2016_03(final boolean debug) { super(debug); - this.triangles = input.stream() - .map(s -> List.of(s.substring(0, 5), s.substring(5, 10), s.substring(10, 15)).stream() - .map(String::strip).map(Integer::valueOf).collect(toList()) - ) - .collect(toList()); - } - - private boolean valid(List t) { - assert t.size() == 3; - return t.get(0) + t.get(1) > t.get(2) - && t.get(0) + t.get(2) > t.get(1) - && t.get(1) + t.get(2) > t.get(0); } - public static final AoC2016_03 create(List input) { - return new AoC2016_03(input, false); + public static final AoC2016_03 create() { + return new AoC2016_03(false); } - public static final AoC2016_03 createDebug(List input) { - return new AoC2016_03(input, true); + public static final AoC2016_03 createDebug() { + return new AoC2016_03(true); } - + @Override - public Integer solvePart1() { - return (int) triangles.stream().filter(this::valid).count(); + protected int[][] parseInput(final List inputs) { + return inputs.stream() + .map(s -> Arrays.stream(s.split(" ")) + .filter(StringUtils::isNotBlank) + .mapToInt(Integer::parseInt) + .toArray()) + .toArray(int[][]::new); + } + + @Override + public Integer solvePart1(final int[][] nums) { + return (int) Arrays.stream(nums) + .map(t -> new Triangle(t[0], t[1], t[2])) + .filter(Triangle::valid) + .count(); } @Override - public Integer solvePart2() { - int valid = 0; - for (int i = 0; i < triangles.size(); i += 3) { - for (int j = 0; j < 3; j++) { - if (valid(List.of(triangles.get(i).get(j), triangles.get(i+1).get(j), triangles.get(i+2).get(j)))) { - valid++; - } - } - } - return valid; + public Integer solvePart2(final int[][] nums) { + return range(0, nums.length, 3).intStream() + .map(i -> (int) range(3).intStream() + .mapToObj(j -> new Triangle( + nums[i][j], nums[i + 1][j], nums[i + 2][j])) + .filter(Triangle::valid) + .count()) + .sum(); } - public static void main(String[] args) throws Exception { - assert AoC2016_03.createDebug(TEST1).solvePart1() == 0; - assert AoC2016_03.createDebug(TEST2).solvePart1() == 1; - assert AoC2016_03.createDebug(TEST3).solvePart2() == 2; - - final List input = Aocd.getData(2016, 3); - lap("Part 1", () -> AoC2016_03.create(input).solvePart1()); - lap("Part 2", () -> AoC2016_03.create(input).solvePart2()); + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "0"), + @Sample(method = "part1", input = TEST2, expected = "1"), + @Sample(method = "part2", input = TEST3, expected = "2"), + }) + public static void main(final String[] args) throws Exception { + AoC2016_03.create().run(); } - private static final List TEST1 = splitLines(" 5 10 25"); - private static final List TEST2 = splitLines(" 3 4 5"); - private static final List TEST3 = splitLines( - " 5 3 6\r\n" + - " 10 4 8\r\n" + - " 25 5 10" - ); + private static final String TEST1 = " 5 10 25"; + private static final String TEST2 = " 3 4 5"; + private static final String TEST3 = """ + 5 3 6 + 10 4 8 + 25 5 10 + """; + + record Triangle(int a, int b, int c) { + + public boolean valid() { + return a + b > c && a + c > b && b + c > a; + } + } } diff --git a/src/main/java/AoC2016_04.java b/src/main/java/AoC2016_04.java index a52dd609..1d54aac5 100644 --- a/src/main/java/AoC2016_04.java +++ b/src/main/java/AoC2016_04.java @@ -1,98 +1,88 @@ -import static com.github.pareronia.aoc.StringUtils.countMatches; +import static com.github.pareronia.aoc.AssertUtils.assertTrue; import static com.github.pareronia.aoc.Utils.toAString; import static java.util.Comparator.comparing; -import static java.util.stream.Collectors.summingInt; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; import java.util.List; -import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.github.pareronia.aoc.Counter; +import com.github.pareronia.aoc.Counter.Entry; import com.github.pareronia.aoc.StringOps; import com.github.pareronia.aoc.Utils; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.Value; - -public class AoC2016_04 extends AoCBase { +public class AoC2016_04 + extends SolutionBase, Integer, Integer> { private static final Pattern REGEXP = Pattern.compile("([-a-z]+)-([0-9]+)\\[([a-z]{5})\\]$"); private static final Pattern MATCH = Pattern.compile("[a-z]{9}-[a-z]{6}-[a-z]{7}$"); - private final List rooms; - - private AoC2016_04(final List inputs, final boolean debug) { + private AoC2016_04(final boolean debug) { super(debug); - this.rooms = inputs.stream() - .map(REGEXP::matcher) - .filter(Matcher::matches) - .map(m -> new Room(m.group(1), Integer.valueOf(m.group(2)), m.group(3))) - .collect(toList()); - log(rooms); } - public static final AoC2016_04 create(final List input) { - return new AoC2016_04(input, false); + public static final AoC2016_04 create() { + return new AoC2016_04(false); } - public static final AoC2016_04 createDebug(final List input) { - return new AoC2016_04(input, true); + public static final AoC2016_04 createDebug() { + return new AoC2016_04(true); } - + @Override - public Integer solvePart1() { - return this.rooms.stream() + protected List parseInput(final List inputs) { + return inputs.stream().map(Room::fromInput).toList(); + } + + @Override + public Integer solvePart1(final List rooms) { + return rooms.stream() .filter(Room::isReal) - .collect(summingInt(Room::getSectorId)); + .mapToInt(Room::sectorId) + .sum(); } @Override - public Integer solvePart2() { - final List matches = this.rooms.stream() - .filter(r -> MATCH.matcher(r.getName()).matches()) + public Integer solvePart2(final List rooms) { + return rooms.stream() + .filter(r -> MATCH.matcher(r.name()).matches()) .filter(r -> r.decrypt().equals("northpole object storage")) - .map(Room::getSectorId) - .collect(toList()); - assert matches.size() == 1; - return matches.get(0); + .map(Room::sectorId) + .findFirst().orElseThrow(); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "1514") + }) public static void main(final String[] args) throws Exception { - assert AoC2016_04.createDebug(TEST).solvePart1() == 1514; - - final Puzzle puzzle = Puzzle.create(2016, 4); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2016_04.create(input)::solvePart1), - () -> lap("Part 2", AoC2016_04.create(input)::solvePart2) - ); + AoC2016_04.create().run(); } - private static final List TEST = splitLines( - "aaaaa-bbb-z-y-x-123[abxyz]\r\n" + - "a-b-c-d-e-f-g-h-987[abcde]\r\n" + - "not-a-real-room-404[oarel]\r\n" + - "totally-real-room-200[decoy]" - ); + private static final String TEST = """ + aaaaa-bbb-z-y-x-123[abxyz] + a-b-c-d-e-f-g-h-987[abcde] + not-a-real-room-404[oarel] + totally-real-room-200[decoy] + """; - @Value - private static final class Room { - private final String name; - private final Integer sectorId; - private final String checkum; + record Room(String name, int sectorId, String checkum) { + + public static Room fromInput(final String line) { + final Matcher m = REGEXP.matcher(line); + assertTrue(m.matches(), () -> "Expected match"); + return new Room(m.group(1), Integer.parseInt(m.group(2)), m.group(3)); + } public boolean isReal() { - return Utils.asCharacterStream(this.name.replace("-", "")) - .collect(toSet()).stream() - .collect(toMap(c -> c, - c -> Integer.valueOf(countMatches(this.name, c)))) - .entrySet().stream() - .sorted(comparing(e -> e.getValue() * -100 + e.getKey().charValue())) - .map(Entry::getKey) + return new Counter<>( + Utils.asCharacterStream(this.name.replace("-", ""))) + .mostCommon().stream() + .sorted(comparing(e -> e.count() * -100 + e.value().charValue())) .limit(5) + .map(Entry::value) .collect(toAString()) .equals(this.checkum); } diff --git a/src/main/java/AoC2016_05.java b/src/main/java/AoC2016_05.java index 79d79e14..2d5b2dd3 100644 --- a/src/main/java/AoC2016_05.java +++ b/src/main/java/AoC2016_05.java @@ -7,8 +7,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public class AoC2016_05 extends AoCBase { private final String input; @@ -54,9 +52,9 @@ public String solvePart1() { @Override public String solvePart2() { - final char[] validPositions = new char[] { '0', '1', '2', '3', '4', '5', '6', '7' }; + final char[] validPositions = { '0', '1', '2', '3', '4', '5', '6', '7' }; Integer index = 0; - final char[] result = new char[] { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; + final char[] result = { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; final Set seen = new HashSet<>(); while (true) { final ValueAndIndex md5 = findMd5StartingWith5Zeroes(index); @@ -91,9 +89,9 @@ public static void main(final String[] args) throws Exception { private static final List TEST = splitLines("abc"); - @RequiredArgsConstructor(staticName = "of") - private static final class ValueAndIndex { - private final String value; - private final int index; + record ValueAndIndex(String value, int index) { + public static ValueAndIndex of(final String value, final int index) { + return new ValueAndIndex(value, index); + } } } diff --git a/src/main/java/AoC2016_06.java b/src/main/java/AoC2016_06.java index b800ef40..4ee17551 100644 --- a/src/main/java/AoC2016_06.java +++ b/src/main/java/AoC2016_06.java @@ -1,86 +1,75 @@ +import static com.github.pareronia.aoc.IntegerSequence.Range.range; +import static com.github.pareronia.aoc.Utils.last; import static com.github.pareronia.aoc.Utils.toAString; -import static java.util.Comparator.comparing; -import static java.util.stream.Collectors.counting; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Stream; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.Counter; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2016_06 extends AoCBase { +public class AoC2016_06 + extends SolutionBase>, String, String> { - private final List inputs; - - private AoC2016_06(List input, boolean debug) { + private AoC2016_06(final boolean debug) { super(debug); - this.inputs = input; - } - - private List> getCounters() { - return Stream.iterate(0, i -> i + 1) - .takeWhile(i -> i < inputs.get(0).length()) - .map(i -> inputs.stream() - .map(input -> input.charAt(i)) - .collect(groupingBy(c -> c, HashMap::new, counting()))) - .collect(toList()); } - public static final AoC2016_06 create(List input) { - return new AoC2016_06(input, false); + public static final AoC2016_06 create() { + return new AoC2016_06(false); } - public static final AoC2016_06 createDebug(List input) { - return new AoC2016_06(input, true); + public static final AoC2016_06 createDebug() { + return new AoC2016_06(true); } @Override - public String solvePart1() { - return getCounters().stream() - .map(c -> c.entrySet().stream() - .max(comparing(Entry::getValue)) - .map(Entry::getKey).orElseThrow()) - .collect(toAString()); + protected List> parseInput(final List inputs) { + return range(inputs.get(0).length()).intStream() + .mapToObj(i -> new Counter<>(inputs.stream() + .map(s -> s.charAt(i)))) + .toList(); + } + + @Override + public String solvePart1(final List> counters) { + return counters.stream() + .map(c -> c.mostCommon().get(0).value()) + .collect(toAString()); } @Override - public String solvePart2() { - return getCounters().stream() - .map(c -> c.entrySet().stream() - .min(comparing(Entry::getValue)) - .map(Entry::getKey).orElseThrow()) - .collect(toAString()); + public String solvePart2(final List> counters) { + return counters.stream() + .map(c -> last(c.mostCommon()).value()) + .collect(toAString()); } - public static void main(String[] args) throws Exception { - assert AoC2016_06.createDebug(TEST).solvePart1().equals("easter"); - assert AoC2016_06.createDebug(TEST).solvePart2().equals("advent"); - - final List input = Aocd.getData(2016, 6); - lap("Part 1", () -> AoC2016_06.create(input).solvePart1()); - lap("Part 2", () -> AoC2016_06.create(input).solvePart2()); + @Samples({ + @Sample(method = "part1", input = TEST, expected = "easter"), + @Sample(method = "part2", input = TEST, expected = "advent"), + }) + public static void main(final String[] args) throws Exception { + AoC2016_06.create().run(); } - private static final List TEST = splitLines( - "eedadn\r\n" + - "drvtee\r\n" + - "eandsr\r\n" + - "raavrd\r\n" + - "atevrs\r\n" + - "tsrnev\r\n" + - "sdttsa\r\n" + - "rasrtv\r\n" + - "nssdts\r\n" + - "ntnada\r\n" + - "svetve\r\n" + - "tesnvt\r\n" + - "vntsnd\r\n" + - "vrdear\r\n" + - "dvrsen\r\n" + - "enarar" - ); + private static final String TEST = """ + eedadn + drvtee + eandsr + raavrd + atevrs + tsrnev + sdttsa + rasrtv + nssdts + ntnada + svetve + tesnvt + vntsnd + vrdear + dvrsen + enarar + """; } diff --git a/src/main/java/AoC2016_07.java b/src/main/java/AoC2016_07.java index 7aef84d0..55345322 100644 --- a/src/main/java/AoC2016_07.java +++ b/src/main/java/AoC2016_07.java @@ -1,37 +1,40 @@ import static com.github.pareronia.aoc.Utils.toAString; -import static java.util.stream.Collectors.toList; import java.util.List; import java.util.regex.Pattern; import java.util.stream.Stream; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2016_07 extends AoCBase { +public class AoC2016_07 extends SolutionBase, Integer, Integer> { private static final Pattern ABBA = Pattern.compile("([a-z])(?!\\1)([a-z])\\2\\1"); private static final Pattern HYPERNET = Pattern.compile("\\[([a-z]*)\\]"); private static final Pattern ABA = Pattern.compile("([a-z])(?!\\1)[a-z]\\1"); - private final List inputs; - - private AoC2016_07(List inputs, boolean debug) { + private AoC2016_07(final boolean debug) { super(debug); - this.inputs = inputs; } - public static final AoC2016_07 create(List input) { - return new AoC2016_07(input, false); + public static final AoC2016_07 create() { + return new AoC2016_07(false); } - public static final AoC2016_07 createDebug(List input) { - return new AoC2016_07(input, true); + public static final AoC2016_07 createDebug() { + return new AoC2016_07(true); } - private boolean isTLS(String ip) { + @Override + protected List parseInput(final List inputs) { + return inputs; + } + + private boolean isTLS(final String ip) { final List mhs = HYPERNET.matcher(ip).results() .map(m -> m.group(1)) - .collect(toList()); + .toList(); String temp = ip; for (final String mh : mhs) { if (ABBA.matcher(mh).find()) { @@ -42,16 +45,16 @@ private boolean isTLS(String ip) { return ABBA.matcher(temp).find(); } - private String aba2bab(String string) { + private String aba2bab(final String string) { assert string.length() == 3; return Stream.of(string.charAt(1), string.charAt(0), string.charAt(1)) .collect(toAString()); } - private boolean isSLS(String ip) { + private boolean isSLS(final String ip) { final List mhs = HYPERNET.matcher(ip).results() .map(m -> m.group(1)) - .collect(toList()); + .toList(); String temp = ip; for (final String mh : mhs) { temp = temp.replace("[" + mh + "]", "/"); @@ -66,39 +69,34 @@ private boolean isSLS(String ip) { } @Override - public Integer solvePart1() { - return (int) this.inputs.stream() - .filter(this::isTLS) - .count(); + public Integer solvePart1(final List inputs) { + return (int) inputs.stream().filter(this::isTLS).count(); } @Override - public Integer solvePart2() { - return (int) this.inputs.stream() - .filter(this::isSLS) - .count(); + public Integer solvePart2(final List inputs) { + return (int) inputs.stream().filter(this::isSLS).count(); } - public static void main(String[] args) throws Exception { - assert AoC2016_07.createDebug(TEST1).solvePart1() == 2; - assert AoC2016_07.createDebug(TEST2).solvePart2() == 3; - - final List input = Aocd.getData(2016, 7); - lap("Part 1", () -> AoC2016_07.create(input).solvePart1()); - lap("Part 2", () -> AoC2016_07.create(input).solvePart2()); + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "2"), + @Sample(method = "part2", input = TEST2, expected = "3"), + }) + public static void main(final String[] args) throws Exception { + AoC2016_07.create().run(); } - private static final List TEST1 = splitLines( - "abba[mnop]qrst\r\n" + - "abcd[bddb]xyyx\r\n" + - "aaaa[qwer]tyui\r\n" + - "ioxxoj[asdfgh]zxcvbn\r\n" + - "abcox[ooooo]xocba" - ); - private static final List TEST2 = splitLines( - "aba[bab]xyz\r\n" + - "xyx[xyx]xyx\r\n" + - "aaa[kek]eke\r\n" + - "zazbz[bzb]cdb" - ); + private static final String TEST1 = """ + abba[mnop]qrst\r + abcd[bddb]xyyx\r + aaaa[qwer]tyui\r + ioxxoj[asdfgh]zxcvbn\r + abcox[ooooo]xocba + """; + private static final String TEST2 = """ + aba[bab]xyz\r + xyx[xyx]xyx\r + aaa[kek]eke\r + zazbz[bzb]cdb + """; } diff --git a/src/main/java/AoC2016_08.java b/src/main/java/AoC2016_08.java index 202cfb3a..ca30ae85 100644 --- a/src/main/java/AoC2016_08.java +++ b/src/main/java/AoC2016_08.java @@ -1,64 +1,71 @@ -import static java.util.stream.Collectors.toList; +import static com.github.pareronia.aoc.IntegerSequence.Range.range; +import static com.github.pareronia.aoc.StringOps.splitLines; +import static com.github.pareronia.aoc.StringOps.splitOnce; +import static java.util.stream.Collectors.toSet; -import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Stream; import com.github.pareronia.aoc.CharGrid; import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.IterTools; import com.github.pareronia.aoc.OCR; +import com.github.pareronia.aoc.StringOps.StringSplit; import com.github.pareronia.aoc.StringUtils; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2016_08 extends AoCBase { +public class AoC2016_08 extends SolutionBase, Integer, String> { private static final char ON = '#'; private static final char OFF = '.'; - private final List inputs; - - private AoC2016_08(final List inputs, final boolean debug) { + private AoC2016_08(final boolean debug) { super(debug); - this.inputs = inputs; } - public static final AoC2016_08 create(final List input) { - return new AoC2016_08(input, false); + public static final AoC2016_08 create() { + return new AoC2016_08(false); } - public static final AoC2016_08 createDebug(final List input) { - return new AoC2016_08(input, true); + public static final AoC2016_08 createDebug() { + return new AoC2016_08(true); } - - private CharGrid createGrid(final Integer rows, final Integer columns) { - return CharGrid.from(Stream.iterate(0, i -> i++) - .limit(rows) - .map(i -> StringUtils.repeat(OFF, columns)) - .collect(toList())); + + @Override + protected List parseInput(final List inputs) { + return inputs; } - private CharGrid solve(final Integer rows, final Integer columns) { - CharGrid grid = createGrid(rows, columns); - for (final String input : this.inputs) { + private CharGrid solve( + final List inputs, + final Integer rows, + final Integer columns + ) { + CharGrid grid = CharGrid.from( + range(rows).intStream() + .mapToObj(i -> StringUtils.repeat(OFF, columns)) + .toList()); + for (final String input : inputs) { if (input.startsWith("rect ")) { - final String[] coords = input.substring("rect ".length()).split("x"); - final Set cells = new HashSet<>(); - for (int r = 0; r < Integer.valueOf(coords[1]); r++) { - for (int c = 0; c < Integer.valueOf(coords[0]); c++) { - cells.add(Cell.at(r, c)); - } - } + final StringSplit coords = splitOnce( + input.substring("rect ".length()), "x"); + final Set cells = IterTools.product( + range(Integer.parseInt(coords.right())), + range(Integer.parseInt(coords.left()))).stream() + .map(lst -> Cell.at(lst.first(), lst.second())) + .collect(toSet()); grid = grid.update(cells, ON); } else if (input.startsWith("rotate row ")) { - final String[] coords = input.substring("rotate row ".length()).split(" by "); - final Integer row = Integer.valueOf(coords[0].substring("y=".length())); - final Integer amount = Integer.valueOf(coords[1]); + final StringSplit coords = splitOnce( + input.substring("rotate row ".length()), " by "); + final int row = Integer.parseInt(coords.left().substring("y=".length())); + final int amount = Integer.parseInt(coords.right()); grid = grid.rollRow(row, amount); } else { - final String[] coords = input.substring("rotate column ".length()).split(" by "); - final Integer column = Integer.valueOf(coords[0].substring("x=".length())); - final Integer amount = Integer.valueOf(coords[1]); + final StringSplit coords = splitOnce( + input.substring("rotate column ".length()), " by "); + final int column = Integer.parseInt(coords.left().substring("x=".length())); + final int amount = Integer.parseInt(coords.right()); grid = grid.rollColumn(column, amount); } log(""); @@ -68,30 +75,30 @@ private CharGrid solve(final Integer rows, final Integer columns) { } @Override - public Integer solvePart1() { - return (int) solve(6, 50).countAllEqualTo(ON); + public Integer solvePart1(final List inputs) { + return (int) solve(inputs, 6, 50).countAllEqualTo(ON); } @Override - public String solvePart2() { - return OCR.convert6(solve(6, 50), ON, OFF); + public String solvePart2(final List inputs) { + return OCR.convert6(solve(inputs, 6, 50), ON, OFF); } - public static void main(final String[] args) throws Exception { - assert AoC2016_08.createDebug(TEST).solve(3, 7).countAllEqualTo(ON) == 6; - - final Puzzle puzzle = Puzzle.create(2016, 8); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2016_08.create(input)::solvePart1), - () -> lap("Part 2", AoC2016_08.create(input)::solvePart2) - ); + @Override + public void samples() { + final AoC2016_08 test = AoC2016_08.createDebug(); + assert test.solve(test.parseInput(splitLines(TEST)), 3, 7) + .countAllEqualTo(ON) == 6; + } + + public static void main(final String[] args) throws Exception { + AoC2016_08.create().run(); } - private static final List TEST = splitLines( - "rect 3x2\r\n" + - "rotate column x=1 by 1\r\n" + - "rotate row y=0 by 4\r\n" + - "rotate column x=1 by 1" - ); + private static final String TEST = """ + rect 3x2 + rotate column x=1 by 1 + rotate row y=0 by 4 + rotate column x=1 by 1 + """; } diff --git a/src/main/java/AoC2016_09.java b/src/main/java/AoC2016_09.java index 83026333..c9b9fa9c 100644 --- a/src/main/java/AoC2016_09.java +++ b/src/main/java/AoC2016_09.java @@ -3,27 +3,30 @@ import java.util.List; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2016_09 extends AoCBase { +public class AoC2016_09 extends SolutionBase { - private final String input; - - private AoC2016_09(List inputs, boolean debug) { + private AoC2016_09(final boolean debug) { super(debug); - assert inputs.size() == 1; - this.input = inputs.get(0); } - public static final AoC2016_09 create(List input) { - return new AoC2016_09(input, false); + public static final AoC2016_09 create() { + return new AoC2016_09(false); } - public static final AoC2016_09 createDebug(List input) { - return new AoC2016_09(input, true); + public static final AoC2016_09 createDebug() { + return new AoC2016_09(true); } - - private Long decompressedLength(String input, boolean recursive) { + + @Override + protected String parseInput(final List inputs) { + return inputs.get(0); + } + + private Long decompressedLength(final String input, final boolean recursive) { if (!input.contains("(")) { return (long) input.length(); } @@ -60,40 +63,38 @@ private Long decompressedLength(String input, boolean recursive) { } @Override - public Long solvePart1() { - return decompressedLength(this.input, FALSE); + public Long solvePart1(final String input) { + return decompressedLength(input, FALSE); } @Override - public Long solvePart2() { - return decompressedLength(this.input, TRUE); + public Long solvePart2(final String input) { + return decompressedLength(input, TRUE); } - public static void main(String[] args) throws Exception { - assert AoC2016_09.createDebug(TEST1).solvePart1() == 6; - assert AoC2016_09.createDebug(TEST2).solvePart1() == 7; - assert AoC2016_09.createDebug(TEST3).solvePart1() == 9; - assert AoC2016_09.createDebug(TEST4).solvePart1() == 11; - assert AoC2016_09.createDebug(TEST5).solvePart1() == 6; - assert AoC2016_09.createDebug(TEST6).solvePart1() == 18; - assert AoC2016_09.createDebug(TEST3).solvePart2() == 9; - assert AoC2016_09.createDebug(TEST6).solvePart2() == 20; - assert AoC2016_09.createDebug(TEST7).solvePart2() == 241920; - assert AoC2016_09.createDebug(TEST8).solvePart2() == 445; - - final List input = Aocd.getData(2016, 9); - lap("Part 1", () -> AoC2016_09.create(input).solvePart1()); - lap("Part 2", () -> AoC2016_09.create(input).solvePart2()); + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "6"), + @Sample(method = "part1", input = TEST2, expected = "7"), + @Sample(method = "part1", input = TEST3, expected = "9"), + @Sample(method = "part1", input = TEST4, expected = "11"), + @Sample(method = "part1", input = TEST5, expected = "6"), + @Sample(method = "part1", input = TEST6, expected = "18"), + @Sample(method = "part2", input = TEST3, expected = "9"), + @Sample(method = "part2", input = TEST6, expected = "20"), + @Sample(method = "part2", input = TEST7, expected = "241920"), + @Sample(method = "part2", input = TEST8, expected = "445"), + }) + public static void main(final String[] args) throws Exception { + AoC2016_09.create().run(); } - private static final List TEST1 = splitLines("ADVENT"); - private static final List TEST2 = splitLines("A(1x5)BC"); - private static final List TEST3 = splitLines("(3x3)XYZ"); - private static final List TEST4 = splitLines("A(2x2)BCD(2x2)EFG"); - private static final List TEST5 = splitLines("(6x1)(1x3)A"); - private static final List TEST6 = splitLines("X(8x2)(3x3)ABCY"); - private static final List TEST7 - = splitLines("(27x12)(20x12)(13x14)(7x10)(1x12)A"); - private static final List TEST8 - = splitLines("(25x3)(3x3)ABC(2x3)XY(5x2)PQRSTX(18x9)(3x2)TWO(5x7)SEVEN"); + private static final String TEST1 = "ADVENT"; + private static final String TEST2 = "A(1x5)BC"; + private static final String TEST3 = "(3x3)XYZ"; + private static final String TEST4 = "A(2x2)BCD(2x2)EFG"; + private static final String TEST5 = "(6x1)(1x3)A"; + private static final String TEST6 = "X(8x2)(3x3)ABCY"; + private static final String TEST7 = "(27x12)(20x12)(13x14)(7x10)(1x12)A"; + private static final String TEST8 + = "(25x3)(3x3)ABC(2x3)XY(5x2)PQRSTX(18x9)(3x2)TWO(5x7)SEVEN"; } diff --git a/src/main/java/AoC2016_10.java b/src/main/java/AoC2016_10.java index 4f01009f..a7a012ec 100644 --- a/src/main/java/AoC2016_10.java +++ b/src/main/java/AoC2016_10.java @@ -1,4 +1,5 @@ import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -11,9 +12,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Data; -import lombok.Value; - public class AoC2016_10 extends AoCBase { private final Set bots; @@ -70,8 +68,8 @@ private void output(final Integer number, final Integer value) { private void run() { for (final Input input : this.inputs) { - findBot(input.getToBot()) - .receive(input.getValue(), this::findBot, this::output); + findBot(input.toBot()) + .receive(input.value(), this::findBot, this::output); } } @@ -119,22 +117,35 @@ public static void main(final String[] args) throws Exception { "value 2 goes to bot 2" ); - @Data private static final class Bot { private final Integer number; private final Integer lowTo; private final Integer highTo; - private List values = new ArrayList<>(); - private Set> compares = new HashSet<>(); + private final List values = new ArrayList<>(); + private final Set> compares = new HashSet<>(); - public void receive( + protected Bot(final Integer number, final Integer lowTo, final Integer highTo) { + this.number = number; + this.lowTo = lowTo; + this.highTo = highTo; + } + + public Integer getNumber() { + return number; + } + + public Set> getCompares() { + return compares; + } + + public void receive( final Integer value, final Function botLookup, final BiConsumer output ) { this.values.add(value); if (this.values.size() == 2) { - this.values.sort(null); + Collections.sort(this.values); final Integer lowValue = this.values.get(0); final Integer highValue = this.values.get(1); if (this.lowTo < 1000) { @@ -153,9 +164,5 @@ public void receive( } } - @Value - private static final class Input { - private final Integer value; - private final Integer toBot; - } + record Input(int value, int toBot) { } } diff --git a/src/main/java/AoC2016_11.java b/src/main/java/AoC2016_11.java index 8b4080cb..250b8500 100644 --- a/src/main/java/AoC2016_11.java +++ b/src/main/java/AoC2016_11.java @@ -1,87 +1,58 @@ +import static com.github.pareronia.aoc.AssertUtils.unreachable; import static com.github.pareronia.aoc.IterTools.combinations; import static java.util.Collections.emptyList; -import static java.util.Comparator.comparing; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.PriorityQueue; +import java.util.Objects; import java.util.Set; import com.github.pareronia.aoc.StringUtils; -import com.github.pareronia.aocd.Aocd; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.AccessLevel; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import lombok.Value; -import lombok.With; - -public final class AoC2016_11 extends AoCBase { +public final class AoC2016_11 + extends SolutionBase { - private final transient State initialState; - - private AoC2016_11(final List inputs, final boolean debug) { + private AoC2016_11(final boolean debug) { super(debug); - this.initialState = parse(inputs); - } - - private State parse(final List inputs) { - final Map chips = new HashMap<>(); - final Map generators = new HashMap<>(); - for (int i = 0; i < inputs.size(); i++) { - String floor = inputs.get(i); - floor = floor.replaceAll(",? and", ","); - floor = floor.replaceAll("\\.", ""); - final String contains = floor.split(" contains ")[1]; - final String[] contained = contains.split(", "); - for (final String containee : contained) { - final String[] s = containee.split(" "); - if ("nothing".equals(s[0])) { - continue; - } else if ("generator".equals(s[2])) { - generators.put(s[1], i + 1); - } else { - chips.put(StringUtils.substringBefore(s[1], "-"), i + 1); - } - } - } - return new State(1, chips, generators); } - public static AoC2016_11 create(final List input) { - return new AoC2016_11(input, false); + public static AoC2016_11 create() { + return new AoC2016_11(false); } - public static AoC2016_11 createDebug(final List input) { - return new AoC2016_11(input, true); + public static AoC2016_11 createDebug() { + return new AoC2016_11(true); } - private Integer solve(final State initialState) { - Integer numberOfSteps = 0; - final PriorityQueue steps - = new PriorityQueue<>(comparing(Step::score)); + @Override + protected State parseInput(final List inputs) { + return State.fromInput(inputs); + } + + private int solve(final State initialState) { + final Deque steps = new ArrayDeque<>(); steps.add(Step.of(0, initialState)); final Set seen = new HashSet<>(); seen.add(initialState.equivalentState()); - int cnt = 0; while (!steps.isEmpty()) { final Step step = steps.poll(); - cnt++; final State state = step.state; if (state.isDestination()) { - numberOfSteps = step.numberOfSteps; - break; + log("#steps: " + step.numberOfSteps); + return step.numberOfSteps; } state.moves().stream() .filter(m -> !seen.contains(m.equivalentState())) @@ -90,116 +61,107 @@ private Integer solve(final State initialState) { steps.add(Step.of(step.numberOfSteps + 1, m)); }); } - log(cnt); - log("#steps: " + numberOfSteps); - return numberOfSteps; + throw unreachable(); } @Override - public Integer solvePart1() { - log("=================="); - log(() -> "Initial state: " + this.initialState); - this.initialState.moves().forEach(this::log); - return solve(this.initialState); + public Integer solvePart1(final State initialState) { + return solve(initialState); } @Override - public Integer solvePart2() { - final Map chips = new HashMap<>(this.initialState.getChips()); + public Integer solvePart2(final State initialState) { + final Map chips = new HashMap<>(initialState.getChips()); chips.put("elerium", 1); chips.put("dilithium", 1); - final Map gennys = new HashMap<>(this.initialState.getGennys()); + final Map gennys = new HashMap<>(initialState.getGennys()); gennys.put("elerium", 1); gennys.put("dilithium", 1); - final State newState = this.initialState.withChips(chips).withGennys(gennys); - log("=================="); - log(() -> "Initial state: " + newState); - return solve(newState); + return solve(initialState.withChips(chips).withGennys(gennys)); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "11") + }) public static void main(final String[] args) throws Exception { - assert AoC2016_11.createDebug(TEST).solvePart1() == 11; - - final Puzzle puzzle = Aocd.puzzle(2016, 11); - final List inputData = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2016_11.create(inputData)::solvePart1), - () -> lap("Part 2", AoC2016_11.create(inputData)::solvePart2) - ); + AoC2016_11.create().run(); } - private static final List TEST = splitLines( - "The first floor contains a hydrogen-compatible microchip, and a lithium-compatible microchip.\n" + - "The second floor contains a hydrogen generator.\n" + - "The third floor contains a lithium generator.\n" + - "The fourth floor contains nothing relevant." - ); + private static final String TEST = """ + The first floor contains a hydrogen-compatible microchip, and a lithium-compatible microchip. + The second floor contains a hydrogen generator. + The third floor contains a lithium generator. + The fourth floor contains nothing relevant. + """; - @Value static final class State { private static final List FLOORS = List.of(1, 2, 3, 4); private static final int TOP = FLOORS.stream().mapToInt(Integer::intValue).max().getAsInt(); private static final int BOTTOM = FLOORS.stream().mapToInt(Integer::intValue).min().getAsInt(); private static final int MAX_ITEMS_PER_MOVE = 2; - @Getter(AccessLevel.PRIVATE) - @With(AccessLevel.PRIVATE) private final Integer elevator; - @Getter(AccessLevel.PRIVATE) - @With(AccessLevel.PRIVATE) private final Map chips; - @Getter(AccessLevel.PRIVATE) - @With(AccessLevel.PRIVATE) private final Map gennys; - @With(AccessLevel.PRIVATE) - @EqualsAndHashCode.Exclude - private final Integer diff; - @Getter(AccessLevel.PRIVATE) - @ToString.Exclude - @EqualsAndHashCode.Exclude private final Map> chipsPerFloor; - @Getter(AccessLevel.PRIVATE) - @ToString.Exclude - @EqualsAndHashCode.Exclude private final Map> gennysPerFloor; private State( final Integer elevator, final Map chips, final Map gennys, - final Integer diff, final Map> chipsPerFloor, final Map> gennysPerFloor ) { this.elevator = elevator; this.chips = chips; this.gennys = gennys; - this.diff = diff; this.chipsPerFloor = chips.keySet().stream().collect(groupingBy(chips::get)); this.gennysPerFloor = gennys.keySet().stream().collect(groupingBy(gennys::get)); } - private State( + State( final Integer elevator, final Map chips, - final Map gennys, - final Integer diff + final Map gennys ) { this( elevator, Collections.unmodifiableMap(chips), Collections.unmodifiableMap(gennys), - diff, null, null); + null, null); } - - public State( - final Integer elevator, - final Map chips, - final Map gennys - ) { - this(elevator, chips, gennys, 0); + + public static State fromInput(final List inputs) { + final Map chips = new HashMap<>(); + final Map generators = new HashMap<>(); + for (int i = 0; i < inputs.size(); i++) { + String floor = inputs.get(i); + floor = floor.replaceAll(",? and", ","); + floor = floor.replace(".", ""); + final String contains = floor.split(" contains ")[1]; + final String[] contained = contains.split(", "); + for (final String containee : contained) { + final String[] s = containee.split(" "); + if ("nothing".equals(s[0])) { + continue; + } else if ("generator".equals(s[2])) { + generators.put(s[1], i + 1); + } else { + chips.put(StringUtils.substringBefore(s[1], "-"), i + 1); + } + } + } + return new State(1, chips, generators); } - @ToString.Include + public Map getChips() { + return chips; + } + + public Map getGennys() { + return gennys; + } + boolean isSafe() { for (final Entry chip : this.chips.entrySet()) { final List gennysOnSameFloor @@ -252,8 +214,8 @@ private List moveMultipleChips( final List chipsOnFloor) { final List states = new ArrayList<>(); if (chipsOnFloor.size() >= MAX_ITEMS_PER_MOVE) { - combinations(chipsOnFloor.size(), MAX_ITEMS_PER_MOVE).forEach( - c -> { + combinations(chipsOnFloor.size(), MAX_ITEMS_PER_MOVE).stream() + .forEach(c -> { final List chipsToMove = Arrays.stream(c) .mapToObj(chipsOnFloor::get).collect(toList()); states.add(moveUpWithChips(chipsToMove)); @@ -283,8 +245,8 @@ private List moveMultipleGennys( final List gennysOnFloor) { final List states = new ArrayList<>(); if (gennysOnFloor.size() >= MAX_ITEMS_PER_MOVE) { - combinations(gennysOnFloor.size(), MAX_ITEMS_PER_MOVE).forEach( - c -> { + combinations(gennysOnFloor.size(), MAX_ITEMS_PER_MOVE).stream() + .forEach(c -> { final List gennysToMove = Arrays.stream(c) .mapToObj(gennysOnFloor::get).collect(toList()); states.add(moveUpWitGennys(gennysToMove)); @@ -319,40 +281,46 @@ private List moveChipAndGennyPairs( for (final String match : intersection) { states.add(withChipsTo(List.of(match), floor + 1) .withGennysTo(List.of(match), floor + 1) - .withElevator(floor + 1) - .withDiff(2)); + .withElevator(floor + 1)); if (!floorsBelowEmpty(floor)) { states.add(withChipsTo(List.of(match), floor - 1) .withGennysTo(List.of(match), floor - 1) - .withElevator(floor - 1) - .withDiff(-2)); + .withElevator(floor - 1)); } } return states; } + private State withElevator(final int elevator) { + return new State(elevator, this.chips, this.gennys); + } + + private State withChips(final Map chips) { + return new State(this.elevator, chips, this.gennys); + } + + private State withGennys(final Map gennys) { + return new State(this.elevator, this.chips, gennys); + } + private State moveUpWithChips(final List chips) { return withChipsTo(chips, this.elevator + 1) - .withElevator(this.elevator + 1) - .withDiff(chips.size()); + .withElevator(this.elevator + 1); } private State moveUpWitGennys(final List gennys) { return withGennysTo(gennys, this.elevator + 1) - .withElevator(this.elevator + 1) - .withDiff(gennys.size()); + .withElevator(this.elevator + 1); } private State moveDownWithChips(final List chips) { return withChipsTo(chips, this.elevator - 1) - .withElevator(this.elevator - 1) - .withDiff(-chips.size()); + .withElevator(this.elevator - 1); } private State moveDownWithGennys(final List gennys) { return withGennysTo(gennys, this.elevator - 1) - .withElevator(this.elevator - 1) - .withDiff(-gennys.size()); + .withElevator(this.elevator - 1); } private State withChipsTo(final List chips, final Integer floor) { @@ -378,15 +346,44 @@ private boolean floorsBelowEmpty(final Integer floor) { } return true; } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final State other = (State) obj; + return Objects.equals(elevator, other.elevator) + && Objects.equals(chips, other.chips) + && Objects.equals(gennys, other.gennys); + } + + @Override + public int hashCode() { + return Objects.hash(chips, elevator, gennys); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("State [elevator=").append(elevator) + .append(", chips=").append(chips) + .append(", gennys=").append(gennys) + .append(", isSafe=").append(isSafe()).append("]"); + return builder.toString(); + } } - @RequiredArgsConstructor(staticName = "of") - private static final class Step { - private final int numberOfSteps; - private final State state; + record Step(int numberOfSteps, State state) {; - public int score() { - return -state.getDiff() * numberOfSteps; + public static Step of(final int numberOfSteps, final State state) { + return new Step(numberOfSteps, state); } } } diff --git a/src/main/java/AoC2016_12.java b/src/main/java/AoC2016_12.java index 8edb17df..2b9c46f2 100644 --- a/src/main/java/AoC2016_12.java +++ b/src/main/java/AoC2016_12.java @@ -1,4 +1,4 @@ -import static java.util.stream.Collectors.toList; +import static com.github.pareronia.aoc.StringOps.splitLines; import java.util.List; @@ -6,30 +6,35 @@ import com.github.pareronia.aoc.StringUtils; import com.github.pareronia.aoc.assembunny.Assembunny; import com.github.pareronia.aoc.assembunny.Assembunny.AssembunnyInstruction; +import com.github.pareronia.aoc.solution.SolutionBase; import com.github.pareronia.aoc.vm.Program; import com.github.pareronia.aoc.vm.VirtualMachine; -import com.github.pareronia.aocd.Puzzle; -public final class AoC2016_12 extends AoCBase { +public final class AoC2016_12 + extends SolutionBase, Integer, Integer> { - private final transient List instructions; - - private AoC2016_12(final List inputs, final boolean debug) { + private AoC2016_12(final boolean debug) { super(debug); - log(inputs); - this.instructions = Assembunny.parse(inputs); } - public static AoC2016_12 create(final List input) { - return new AoC2016_12(input, false); + public static AoC2016_12 create() { + return new AoC2016_12(false); } - public static AoC2016_12 createDebug(final List input) { - return new AoC2016_12(input, true); + public static AoC2016_12 createDebug() { + return new AoC2016_12(true); } - - private Integer solveVM(final Integer initC) { - final Program program = new Program(Assembunny.translate(this.instructions)); + + @Override + protected List parseInput(final List inputs) { + return Assembunny.parse(inputs); + } + + private Integer solveVM( + final List instructions, + final Integer initC + ) { + final Program program = new Program(Assembunny.translate(instructions)); log(() -> program.getInstructions()); program.setRegisterValue("c", initC.longValue()); new VirtualMachine().runProgram(program); @@ -41,13 +46,16 @@ private int fibonacci(final int number) { return FibonacciUtil.binet(number).intValue(); } - private Integer solve(final Integer initC) { - final List values = this.instructions.stream() + private Integer solve( + final List instructions, + final Integer initC + ) { + final List values = instructions.stream() .filter(i -> "cpy".equals(i.getOperator())) .map(i -> i.getOperands().get(0)) .filter(StringUtils::isNumeric) .map(Integer::valueOf) - .collect(toList()); + .toList(); log(() -> values); final int number = values.get(0) + values.get(1) + values.get(2) + (initC == 0 ? 0 : 7); @@ -55,61 +63,60 @@ private Integer solve(final Integer initC) { } @Override - public Integer solvePart1() { - return solve(0); + public Integer solvePart1(final List instructions) { + return solve(instructions, 0); } @Override - public Integer solvePart2() { - return solve(1); + public Integer solvePart2(final List instructions) { + return solve(instructions, 1); } - + + @Override + public void samples() { + final AoC2016_12 test = AoC2016_12.createDebug(); + assert test.solveVM(test.parseInput(splitLines(TEST1)), 0) == 42; + assert test.solveVM(test.parseInput(splitLines(TEST2)), 0) == 318_003; + assert test.solveVM(test.parseInput(splitLines(TEST2)), 1) == 9_227_657; + assert test.solve(test.parseInput(splitLines(TEST2)), 0) == 318_003; + assert test.solve(test.parseInput(splitLines(TEST2)), 1) == 9_227_657; + } + public static void main(final String[] args) throws Exception { - assert AoC2016_12.createDebug(TEST1).solveVM(0) == 42; - assert AoC2016_12.createDebug(TEST2).solveVM(0) == 318_003; - assert AoC2016_12.createDebug(TEST2).solveVM(1) == 9_227_657; - assert AoC2016_12.createDebug(TEST2).solve(0) == 318_003; - assert AoC2016_12.createDebug(TEST2).solve(1) == 9_227_657; - - final Puzzle puzzle = Puzzle.create(2016, 12); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2016_12.create(input)::solvePart1), - () -> lap("Part 2", AoC2016_12.create(input)::solvePart2) - ); + AoC2016_12.create().run(); } - private static final List TEST1 = splitLines( - "cpy 41 a\n" + - "inc a\n" + - "inc a\n" + - "dec a\n" + - "jnz a 2\n" + - "dec a" - ); - private static final List TEST2 = splitLines( - "cpy 1 a\n" + - "cpy 1 b\n" + - "cpy 26 d\n" + - "jnz c 2\n" + - "jnz 1 5\n" + - "cpy 7 c\n" + - "inc d\n" + - "dec c\n" + - "jnz c -2\n" + - "cpy a c\n" + - "inc a\n" + - "dec b\n" + - "jnz b -2\n" + - "cpy c b\n" + - "dec d\n" + - "jnz d -6\n" + - "cpy 16 c\n" + - "cpy 12 d\n" + - "inc a\n" + - "dec d\n" + - "jnz d -2\n" + - "dec c\n" + - "jnz c -5" - ); + private static final String TEST1 = """ + cpy 41 a + inc a + inc a + dec a + jnz a 2 + dec a + """; + private static final String TEST2 = """ + cpy 1 a + cpy 1 b + cpy 26 d + jnz c 2 + jnz 1 5 + cpy 7 c + inc d + dec c + jnz c -2 + cpy a c + inc a + dec b + jnz b -2 + cpy c b + dec d + jnz d -6 + cpy 16 c + cpy 12 d + inc a + dec d + jnz d -2 + dec c + jnz c -5 + """; } \ No newline at end of file diff --git a/src/main/java/AoC2016_13.java b/src/main/java/AoC2016_13.java index 5aaa4531..c964db17 100644 --- a/src/main/java/AoC2016_13.java +++ b/src/main/java/AoC2016_13.java @@ -5,7 +5,7 @@ import com.github.pareronia.aoc.Grid.Cell; import com.github.pareronia.aoc.Utils; -import com.github.pareronia.aoc.graph.AStar; +import com.github.pareronia.aoc.graph.Dijkstra; import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; @@ -50,12 +50,12 @@ private Stream adjacent(final Cell c) { .filter(this::isOpenSpace); } - private AStar.Result runAStar() { - return AStar.execute( + private Dijkstra.Result runAStar() { + return Dijkstra.execute( START, cell -> false, this::adjacent, - cell -> 1); + (curr, next) -> 1); } private int getDistance(final Cell end) { diff --git a/src/main/java/AoC2016_15.java b/src/main/java/AoC2016_15.java index c4a4a80c..b864cbe5 100644 --- a/src/main/java/AoC2016_15.java +++ b/src/main/java/AoC2016_15.java @@ -7,8 +7,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Value; - public class AoC2016_15 extends AoCBase { private static final Pattern REGEX = Pattern.compile("[0-9]+"); @@ -79,11 +77,7 @@ public static void main(final String[] args) throws Exception { "Disc #3 has 3 positions; at time=0, it is at position 2." ); - @Value - private static final class Disc { - private final Integer period; - private final Integer offset; - private final Integer delay; + record Disc(int period, int offset, int delay) { public boolean alignedAt(final Integer time) { return (time + this.delay) % this.period == this.offset; diff --git a/src/main/java/AoC2016_16.java b/src/main/java/AoC2016_16.java index 307cbc1e..c1972352 100644 --- a/src/main/java/AoC2016_16.java +++ b/src/main/java/AoC2016_16.java @@ -1,76 +1,85 @@ import java.util.List; +import com.github.pareronia.aoc.StringOps; import com.github.pareronia.aoc.StringUtils; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2016_16 extends AoCBase { +public class AoC2016_16 + extends SolutionBase { - private final String initialState; - - private AoC2016_16(final List input, final boolean debug) { + private AoC2016_16(final boolean debug) { super(debug); - assert input.size() == 1; - this.initialState = input.get(0); } - public static AoC2016_16 create(final List input) { - return new AoC2016_16(input, false); + public static AoC2016_16 create() { + return new AoC2016_16(false); } - public static AoC2016_16 createDebug(final List input) { - return new AoC2016_16(input, true); - } - - private String dragonCurve(final String input) { - final String a = new String(input); - final String b = StringUtils.reverse(input).replace('0', '-').replace('1', '0').replace('-', '1'); - return new StringBuilder().append(a).append("0").append(b).toString(); - } - - private char[] checkSum(final char[] data) { - final char[] pairs = new char[data.length / 2]; - for (int i = 0; i < data.length - 1; i += 2) { - if (data[i] == data[i + 1]) { - pairs[i / 2] = '1'; - } else { - pairs[i / 2] = '0'; - } - } - if (pairs.length % 2 != 0) { - return pairs; - } else { - return checkSum(pairs); - } + public static AoC2016_16 createDebug() { + return new AoC2016_16(true); } - private String solve(final Integer size) { - String data = this.initialState; - while (data.length() < size) { - data = dragonCurve(data); - } - return String.valueOf(checkSum(data.substring(0, size).toCharArray())); + private String solve(final DragonCurve dragonCurve, final int size) { + return dragonCurve.checkSumForSize(size); } @Override - public String solvePart1() { - return solve(272); + protected DragonCurve parseInput(final List inputs) { + return new DragonCurve(inputs.get(0)); + } + + @Override + public String solvePart1(final DragonCurve dragonCurve) { + return solve(dragonCurve, 272); } @Override - public String solvePart2() { - return solve(35651584); + public String solvePart2(final DragonCurve dragonCurve) { + return solve(dragonCurve, 35651584); } - public static void main(final String[] args) throws Exception { - assert AoC2016_16.createDebug(TEST).solve(20).equals("01100"); + @Override + public void samples() { + final AoC2016_16 test = AoC2016_16.createDebug(); + assert test.solve(test.parseInput(List.of(TEST)), 20).equals("01100"); + } - final Puzzle puzzle = Puzzle.create(2016, 16); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2016_16.create(input)::solvePart1), - () -> lap("Part 2", AoC2016_16.create(input)::solvePart2) - ); + public static void main(final String[] args) throws Exception { + AoC2016_16.create().run(); } - private static final List TEST = splitLines("10000"); + private static final String TEST = "10000"; + + record DragonCurve(String initialState) { + private String dragonCurve(final String input) { + final String b = StringOps.translate( + StringUtils.reverse(input), "01", "10"); + return new StringBuilder().append(input) + .append("0").append(b).toString(); + } + + private char[] checkSum(final char[] data) { + final char[] pairs = new char[data.length / 2]; + for (int i = 0; i < data.length - 1; i += 2) { + if (data[i] == data[i + 1]) { + pairs[i / 2] = '1'; + } else { + pairs[i / 2] = '0'; + } + } + if (pairs.length % 2 != 0) { + return pairs; + } else { + return checkSum(pairs); + } + } + + public String checkSumForSize(final int size) { + String data = this.initialState; + while (data.length() < size) { + data = dragonCurve(data); + } + return String.valueOf(checkSum(data.substring(0, size).toCharArray())); + } + } } \ No newline at end of file diff --git a/src/main/java/AoC2016_17.java b/src/main/java/AoC2016_17.java index d67da624..07072f8e 100644 --- a/src/main/java/AoC2016_17.java +++ b/src/main/java/AoC2016_17.java @@ -13,11 +13,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public final class AoC2016_17 extends AoCBase { private static final Position START = Position.of(0, 0); @@ -51,7 +46,7 @@ public String solvePart1() { }); return paths.stream() .findFirst() - .map(Path::getPath) + .map(Path::path) .orElseThrow(); } @@ -86,14 +81,7 @@ public static void main(final String[] args) throws Exception { ); } - @RequiredArgsConstructor - @EqualsAndHashCode - @ToString - private static final class Path { - @Getter - private final String path; - @Getter - private final Position position; + record Path(String path, Position position) { public int length() { return this.path.length(); @@ -104,16 +92,11 @@ public boolean isAt(final Position position) { } } - @RequiredArgsConstructor - private static final class PathFinder { + record PathFinder(Position start, Position destination, String salt) { private static final List OPEN_CHARS = List.of('b', 'c', 'd', 'e', 'f'); private static final List DIRECTIONS = List.of( Direction.DOWN, Direction.UP, Direction.LEFT, Direction.RIGHT); private static final char[] DOORS = { 'U', 'D', 'L', 'R' }; - - private final Position start; - private final Position destination; - private final String salt; public void findPaths(final Function stop) { final Deque paths = new ArrayDeque<>(); @@ -128,7 +111,7 @@ public void findPaths(final Function stop) { for (final Direction direction : DIRECTIONS) { final Path newPath = buildNewPath(path, direction); if (doors[DIRECTIONS.indexOf(direction)] - && isInBounds(newPath.getPosition())) { + && isInBounds(newPath.position())) { paths.add(newPath); } } @@ -137,7 +120,7 @@ && isInBounds(newPath.getPosition())) { private boolean[] areDoorsOpen(final Path path) { final String data = new StringBuilder().append(this.salt) - .append(path.getPath()).toString(); + .append(path.path()).toString(); final String md5Hex = MD5.md5Hex(data); final boolean[] doors = new boolean[DOORS.length]; for (int d = 0; d < DIRECTIONS.size(); d++) { @@ -148,8 +131,8 @@ private boolean[] areDoorsOpen(final Path path) { private Path buildNewPath(final Path path, final Direction direction) { return new Path( - path.getPath() + DOORS[DIRECTIONS.indexOf(direction)], - path.getPosition().translate(direction)); + path.path() + DOORS[DIRECTIONS.indexOf(direction)], + path.position().translate(direction)); } private boolean isInBounds(final Point position) { diff --git a/src/main/java/AoC2016_18.java b/src/main/java/AoC2016_18.java index e4c11288..3d125a77 100644 --- a/src/main/java/AoC2016_18.java +++ b/src/main/java/AoC2016_18.java @@ -2,9 +2,9 @@ import java.util.Set; import com.github.pareronia.aoc.Utils; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2016_18 extends AoCBase { +public class AoC2016_18 extends SolutionBase { private static final char SAFE = '.'; private static final char TRAP = '^'; @@ -15,35 +15,36 @@ public class AoC2016_18 extends AoCBase { String.valueOf(new char[] { SAFE, SAFE, TRAP }) ); - private final String startingRow; - - private AoC2016_18(List input, boolean debug) { + private AoC2016_18(final boolean debug) { super(debug); - assert input.size() == 1; - this.startingRow = input.get(0); } - public static AoC2016_18 create(List input) { - return new AoC2016_18(input, false); + public static AoC2016_18 create() { + return new AoC2016_18(false); } - public static AoC2016_18 createDebug(List input) { - return new AoC2016_18(input, true); + public static AoC2016_18 createDebug() { + return new AoC2016_18(true); } - private char[] getPrevious(char[] s, Integer i) { + private char[] getPrevious(final char[] s, final Integer i) { final char first = (i - 1 < 0) ? SAFE : s[i - 1]; final char second = s[i]; final char third = (i + 1 == s.length) ? SAFE : s[i + 1]; return new char[] { first, second, third }; } - private Long solve(Integer rows) { - final int width = this.startingRow.length(); - long safeCount = Utils.asCharacterStream(this.startingRow) + @Override + protected String parseInput(final List inputs) { + return inputs.get(0); + } + + private Long solve(final String input, final Integer rows) { + final int width = input.length(); + long safeCount = Utils.asCharacterStream(input) .filter(c -> c == SAFE) .count(); - char[] previousRow = this.startingRow.toCharArray(); + char[] previousRow = input.toCharArray(); for (int row = 1; row < rows; row++) { final char[] newRow = new char[width]; for (int j = 0; j < width; j++) { @@ -61,24 +62,26 @@ private Long solve(Integer rows) { } @Override - public Long solvePart1() { - return solve(40); + public Long solvePart1(final String input) { + return solve(input, 40); } @Override - public Long solvePart2() { - return solve(400_000); + public Long solvePart2(final String input) { + return solve(input, 400_000); } - public static void main(String[] args) throws Exception { - assert AoC2016_18.createDebug(TEST1).solve(3) == 6; - assert AoC2016_18.createDebug(TEST2).solve(10) == 38; + @Override + public void samples() { + final AoC2016_18 test = AoC2016_18.createDebug(); + assert test.solve(TEST1, 3) == 6; + assert test.solve(TEST2, 10) == 38; + } - final List input = Aocd.getData(2016, 18); - lap("Part 1", () -> AoC2016_18.create(input).solvePart1()); - lap("Part 2", () -> AoC2016_18.create(input).solvePart2()); + public static void main(final String[] args) throws Exception { + AoC2016_18.create().run(); } - private static final List TEST1 = splitLines("..^^."); - private static final List TEST2 = splitLines(".^^.^.^^^^"); + private static final String TEST1 = "..^^."; + private static final String TEST2 = ".^^.^.^^^^"; } \ No newline at end of file diff --git a/src/main/java/AoC2016_19.java b/src/main/java/AoC2016_19.java index 1da1f51d..7aee64ef 100644 --- a/src/main/java/AoC2016_19.java +++ b/src/main/java/AoC2016_19.java @@ -1,33 +1,33 @@ import java.util.List; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2016_19 extends AoCBase { +public class AoC2016_19 extends SolutionBase { - private final DoublyLinkedList elves; - - private AoC2016_19(final List input, final boolean debug) { + private AoC2016_19(final boolean debug) { super(debug); - assert input.size() == 1; - this.elves = new DoublyLinkedList(); - for (int i = 1; i <= Integer.valueOf(input.get(0)); i++) { - elves.addTail(i); - } - this.elves.close(); } - public static AoC2016_19 create(final List input) { - return new AoC2016_19(input, false); + public static AoC2016_19 create() { + return new AoC2016_19(false); } - public static AoC2016_19 createDebug(final List input) { - return new AoC2016_19(input, true); + public static AoC2016_19 createDebug() { + return new AoC2016_19(true); } @Override - public Integer solvePart1() { - Node curr = this.elves.head; - while (this.elves.size > 1) { + protected String parseInput(final List inputs) { + return inputs.get(0); + } + + @Override + public Integer solvePart1(final String input) { + final Elves elves = Elves.fromInput(input); + Node curr = elves.head; + while (elves.size > 1) { final Node loser = curr.next; elves.remove(loser); curr = curr.next; @@ -36,16 +36,17 @@ public Integer solvePart1() { } @Override - public Integer solvePart2() { - Node curr = this.elves.head; - Node opposite = this.elves.head; - for (int i = 0; i < this.elves.size / 2; i++) { + public Integer solvePart2(final String input) { + final Elves elves = Elves.fromInput(input); + Node curr = elves.head; + Node opposite = elves.head; + for (int i = 0; i < elves.size / 2; i++) { opposite = opposite.next; } - while (this.elves.size > 1) { + while (elves.size > 1) { final Node loser = opposite; - this.elves.remove(loser); - if (this.elves.size % 2 == 1) { + elves.remove(loser); + if (elves.size % 2 == 1) { opposite = opposite.next; } else { opposite = opposite.next.next; @@ -55,46 +56,44 @@ public Integer solvePart2() { return curr.value; } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "3"), + @Sample(method = "part2", input = TEST, expected = "2"), + }) public static void main(final String[] args) throws Exception { - assert AoC2016_19.createDebug(TEST).solvePart1() == 3; - assert AoC2016_19.createDebug(TEST).solvePart2() == 2; - - final List input = Aocd.getData(2016, 19); - lap("Part 1", () -> AoC2016_19.create(input).solvePart1()); - lap("Part 2", () -> AoC2016_19.create(input).solvePart2()); + AoC2016_19.create().run(); } - private static final List TEST = splitLines("5"); + private static final String TEST = "5"; - public static final class DoublyLinkedList { + private static final class Elves { private Node head; - private Node tail; private int size; - public DoublyLinkedList() { + public Elves() { this.head = null; - this.tail = null; this.size = 0; } - public void close() { - head.prev = tail; - tail.next = head; - } - - public void addTail(final int value) { - final Node node = new Node(value); - node.next = null; - if (this.size == 0) { - this.tail = node; - this.head = node; - } else { - this.tail.next = node; - node.prev = this.tail; - this.tail = node; + public static Elves fromInput(final String input) { + final Elves elves = new Elves(); + Node node = null; + Node prev = null; + for (int i = 0; i < Integer.parseInt(input); i++) { + node = new Node(i + 1); + if (elves.size == 0) { + elves.head = node; + } else { + node.prev = prev; + prev.next = node; + } + prev = node; + elves.size++; } - this.size++; + elves.head.prev = node; + node.next = elves.head; + return elves; } public void remove(final Node node) { @@ -104,7 +103,7 @@ public void remove(final Node node) { } } - public static class Node { + private static class Node { public int value; public Node next; public Node prev; diff --git a/src/main/java/AoC2016_22.java b/src/main/java/AoC2016_22.java index 8f9182c5..f5babbeb 100644 --- a/src/main/java/AoC2016_22.java +++ b/src/main/java/AoC2016_22.java @@ -1,5 +1,6 @@ import static com.github.pareronia.aoc.AssertUtils.assertFalse; import static com.github.pareronia.aoc.AssertUtils.assertTrue; +import static com.github.pareronia.aoc.StringOps.splitLines; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.summingInt; @@ -13,119 +14,64 @@ import java.util.List; import java.util.Set; import java.util.function.Function; -import java.util.regex.Pattern; import java.util.stream.Stream; import com.github.pareronia.aoc.geometry.Direction; import com.github.pareronia.aoc.geometry.Point; import com.github.pareronia.aoc.geometry.Position; -import com.github.pareronia.aocd.Aocd; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; +public class AoC2016_22 + extends SolutionBase { -public class AoC2016_22 extends AoCBase { - - private static final Pattern REGEX = Pattern.compile( - "^\\/dev\\/grid\\/node-x([0-9]+)-y([0-9]+)\\s+[0-9]+T" + - "\\s+([0-9]+)T\\s+([0-9]+)T\\s+[0-9]+%$"); - - private final List nodes; - - private AoC2016_22(final List input, final boolean debug) { + private AoC2016_22(final boolean debug) { super(debug); - this.nodes = input.stream() - .skip(2) - .flatMap(s -> REGEX.matcher(s).results()) - .map(m -> { - final Integer x = Integer.valueOf(m.group(1)); - final Integer y = Integer.valueOf(m.group(2)); - final Integer used = Integer.valueOf(m.group(3)); - final Integer available = Integer.valueOf(m.group(4)); - return new Node(x, y, used, available); - }) - .collect(toList()); } - public static AoC2016_22 create(final List input) { - return new AoC2016_22(input, false); + public static AoC2016_22 create() { + return new AoC2016_22(false); } - public static AoC2016_22 createDebug(final List input) { - return new AoC2016_22(input, true); - } - - private Set getUnusableNodes() { - final Integer maxAvailable = this.nodes.stream() - .max(comparing(Node::getAvailable)) - .map(Node::getAvailable).orElseThrow(); - return this.nodes.stream() - .filter(n -> n.getUsed() > maxAvailable) - .collect(toSet()); - } - - private Node getEmptyNode() { - final List emptyNodes = nodes.stream() - .filter(n -> n.getUsed() == 0) - .collect(toList()); - assertTrue(emptyNodes.size() == 1, () -> "Expected 1 empty node"); - return emptyNodes.get(0); - } - - private Integer getMaxX() { - return this.nodes.stream() - .max(comparing(Node::getX)) - .map(Node::getX).orElseThrow(); - } - - private Integer getMaxY() { - return this.nodes.stream() - .max(comparing(Node::getY)) - .map(Node::getY).orElseThrow(); + public static AoC2016_22 createDebug() { + return new AoC2016_22(true); } - - private Node getGoalNode() { - return nodes.stream() - .filter(n -> n.getX() == getMaxX() && n.getY() == 0) - .findFirst().orElseThrow(); - } - - private Node getAccessibleNode() { - return nodes.stream() - .filter(n -> n.getX() == 0 && n.getY() == 0) - .findFirst().orElseThrow(); + + @Override + protected Cluster parseInput(final List inputs) { + return new Cluster(inputs.stream() + .skip(2) + .map(Node::fromInput) + .collect(toList())); } @Override - public Integer solvePart1() { - return (int) this.nodes.stream() + public Integer solvePart1(final Cluster cluster) { + return (int) cluster.nodes.stream() .filter(Node::isNotEmpty) - .flatMap(a -> this.nodes.stream() + .flatMap(a -> cluster.nodes.stream() .filter(b -> !a.equals(b)) - .filter(b -> a.getUsed() <= b.getAvailable())) + .filter(b -> a.used() <= b.available())) .count(); } - private void visualize() { - final Integer maxX = getMaxX(); - final Integer maxY = getMaxY(); - final List sorted = this.nodes.stream() - .sorted(comparing(n -> n.getX() * maxY + n.getY())) + private void visualize(final Cluster cluster) { + final Integer maxX = cluster.getMaxX(); + final Integer maxY = cluster.getMaxY(); + final List sorted = cluster.nodes.stream() + .sorted(comparing(n -> n.x() * maxY + n.y())) .collect(toList()); final List> grid = Stream.iterate(0, i -> i <= maxX, i -> i + 1) .map(i -> sorted.stream() .skip(i * (maxY + 1)) - .takeWhile(n -> n.getX() == i) + .takeWhile(n -> n.x() == i) .collect(toList())) .collect(toList()); - final Set unusableNodes = getUnusableNodes(); - final Node emptyNode = getEmptyNode(); - final Node goalNode = getGoalNode(); - final Node accessibleNode = getAccessibleNode(); + final Set unusableNodes = cluster.getUnusableNodes(); + final Node emptyNode = cluster.getEmptyNode(); + final Node goalNode = cluster.getGoalNode(); + final Node accessibleNode = cluster.getAccessibleNode(); for (final List row : grid) { final String line = row.stream() .map(n -> { @@ -147,7 +93,7 @@ private void visualize() { } private Position toPosition(final Node node) { - return Position.of(node.getX(), node.getY()); + return Position.of(node.x(), node.y()); } private Function stopAt( @@ -161,16 +107,16 @@ private Function stopAt( }; } - private Integer solve2() { - visualize(); - final Set unusableNodes = getUnusableNodes().stream() + private Integer solve2(final Cluster cluster) { + visualize(cluster); + final Set unusableNodes = cluster.getUnusableNodes().stream() .map(this::toPosition) .collect(toSet()); - final Position emptyNode = toPosition(getEmptyNode()); - final Position accessibleNode = toPosition(getAccessibleNode()); - final Integer maxX = getMaxX(); - final Integer maxY = getMaxY(); - final Position goalNode = toPosition(getGoalNode()); + final Position emptyNode = toPosition(cluster.getEmptyNode()); + final Position accessibleNode = toPosition(cluster.getAccessibleNode()); + final Integer maxX = cluster.getMaxX(); + final Integer maxY = cluster.getMaxY(); + final Position goalNode = toPosition(cluster.getGoalNode()); log("Unusable: "+ unusableNodes); log("Empty: " + emptyNode); log("Accessible: " + accessibleNode); @@ -191,18 +137,18 @@ private Integer solve2() { new PathFinder(goalNode, accessibleNode, max, unusableNodes) .findPaths(stopAt(accessibleNode, paths)); final Integer length = paths.stream() - .map(Path::getLength) + .map(Path::length) .collect(summingInt(Integer::valueOf)); log(length); return length + 1; } - private Integer solve2Cheat() { - visualize(); - final Set unusableNodes = getUnusableNodes(); + private Integer solve2Cheat(final Cluster cluster) { + visualize(cluster); + final Set unusableNodes = cluster.getUnusableNodes(); log(unusableNodes); final Set holeYs = unusableNodes.stream() - .map(Node::getY) + .map(Node::y) .collect(toSet()); assertTrue(holeYs.size() == 1, () -> "Expected all unusable nodes in 1 row"); final Integer holeY = holeYs.iterator().next(); @@ -210,18 +156,18 @@ private Integer solve2Cheat() { throw new IllegalStateException("Unsolvable"); } assertFalse(unusableNodes.stream() - .max(comparing(Node::getX)) - .map(Node::getX) - .orElseThrow() != getMaxX(), + .max(comparing(Node::x)) + .map(Node::x) + .orElseThrow() != cluster.getMaxX(), () -> "Expected unusable row to touch side"); final Integer holeX = unusableNodes.stream() - .min(comparing(Node::getX)) - .map(Node::getX) + .min(comparing(Node::x)) + .map(Node::x) .orElseThrow(); final Position hole = Position.of(holeX - 1, holeY); - final Position emptyNode = toPosition(getEmptyNode()); + final Position emptyNode = toPosition(cluster.getEmptyNode()); final int part1 = emptyNode.manhattanDistance(hole); - final Position goalNode = toPosition(getGoalNode()); + final Position goalNode = toPosition(cluster.getGoalNode()); final int part2 = hole.manhattanDistance( Position.of(goalNode.getX() - 1, goalNode.getY())); final int part3 = 5 * (goalNode.getX() - 1); @@ -229,43 +175,53 @@ private Integer solve2Cheat() { } @Override - public Integer solvePart2() { - return solve2Cheat(); + public Integer solvePart2(final Cluster cluster) { + return solve2Cheat(cluster); } - - public static void main(final String[] args) throws Exception { - assert AoC2016_22.createDebug(TEST).solvePart1() == 7; - assert AoC2016_22.createDebug(TEST).solve2() == 7; - final Puzzle puzzle = Aocd.puzzle(2016, 22); - final List inputData = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2016_22.createDebug(inputData)::solvePart1), - () -> lap("Part 2", AoC2016_22.createDebug(inputData)::solvePart2) - ); + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "7") + }) + public void samples() { + final AoC2016_22 test = AoC2016_22.createDebug(); + assert test.solve2(test.parseInput(splitLines(TEST))) == 7; } - private static final List TEST = splitLines( - "root@ebhq-gridcenter# df -h\r\n" + - "Filesystem Size Used Avail Use%\r\n" + - "/dev/grid/node-x0-y0 10T 8T 2T 80%\r\n" + - "/dev/grid/node-x0-y1 11T 6T 5T 54%\r\n" + - "/dev/grid/node-x0-y2 32T 28T 4T 87%\r\n" + - "/dev/grid/node-x1-y0 9T 7T 2T 77%\r\n" + - "/dev/grid/node-x1-y1 8T 0T 8T 0%\r\n" + - "/dev/grid/node-x1-y2 11T 7T 4T 63%\r\n" + - "/dev/grid/node-x2-y0 10T 6T 4T 60%\r\n" + - "/dev/grid/node-x2-y1 9T 8T 1T 88%\r\n" + - "/dev/grid/node-x2-y2 9T 6T 3T 66%" - ); + public static void main(final String[] args) throws Exception { + AoC2016_22.createDebug().run(); + } + + private static final String TEST = """ + root@ebhq-gridcenter# df -h\r + Filesystem Size Used Avail Use%\r + /dev/grid/node-x0-y0 10T 8T 2T 80%\r + /dev/grid/node-x0-y1 11T 6T 5T 54%\r + /dev/grid/node-x0-y2 32T 28T 4T 87%\r + /dev/grid/node-x1-y0 9T 7T 2T 77%\r + /dev/grid/node-x1-y1 8T 0T 8T 0%\r + /dev/grid/node-x1-y2 11T 7T 4T 63%\r + /dev/grid/node-x2-y0 10T 6T 4T 60%\r + /dev/grid/node-x2-y1 9T 8T 1T 88%\r + /dev/grid/node-x2-y2 9T 6T 3T 66% + """; - @RequiredArgsConstructor private static final class PathFinder { private final Position start; private final Position destination; private final Position max; private final Set unusable; + public PathFinder( + final Position start, final Position destination, + final Position max, final Set unusable + ) { + this.start = start; + this.destination = destination; + this.max = max; + this.unusable = unusable; + } + public void findPaths(final Function stop) { final Deque paths = new ArrayDeque<>(); Path path = new Path(0, this.start); @@ -278,19 +234,19 @@ public void findPaths(final Function stop) { } for (final Direction direction : Direction.CAPITAL) { final Path newPath = buildNewPath(path, direction); - if (isInBounds(newPath.getPosition()) - && isUsable(newPath.getPosition()) - && !seen.contains(newPath.getPosition())) { + if (isInBounds(newPath.position()) + && isUsable(newPath.position()) + && !seen.contains(newPath.position())) { paths.add(newPath); - seen.add(newPath.getPosition()); + seen.add(newPath.position()); } } } } private Path buildNewPath(final Path path, final Direction direction) { - return new Path(path.getLength() + 1, - path.getPosition().translate(direction)); + return new Path(path.length() + 1, + path.position().translate(direction)); } private boolean isInBounds(final Position position) { @@ -305,39 +261,70 @@ private boolean isUsable(final Point position) { } } - @RequiredArgsConstructor - @EqualsAndHashCode - @ToString - private static final class Path { - @Getter - private final Integer length; - @Getter - private final Position position; - + record Path(int length, Position position) { public boolean isAt(final Position position) { return this.position.equals(position); } } - - @AllArgsConstructor - @EqualsAndHashCode(onlyExplicitlyIncluded = true) - @ToString(onlyExplicitlyIncluded = true) - private static final class Node { - @Getter - @EqualsAndHashCode.Include - @ToString.Include - private final Integer x; - @Getter - @EqualsAndHashCode.Include - @ToString.Include - private final Integer y; - @Getter - private final Integer used; - @Getter - private final Integer available; + + record Node(int x, int y, int used, int available) { + public static Node fromInput(final String line) { + final String[] splits = line.split("\\s+"); + final String[] xy = splits[0].split("-"); + return new Node( + Integer.parseInt(xy[1].substring(1)), + Integer.parseInt(xy[2].substring(1)), + Integer.parseInt(splits[2].substring(0, splits[2].length() - 1)), + Integer.parseInt(splits[3].substring(0, splits[3].length() - 1)) + ); + } public boolean isNotEmpty() { return this.used != 0; } } + + record Cluster(List nodes) { + + public Set getUnusableNodes() { + final Integer maxAvailable = this.nodes.stream() + .max(comparing(Node::available)) + .map(Node::available).orElseThrow(); + return this.nodes.stream() + .filter(n -> n.used() > maxAvailable) + .collect(toSet()); + } + + public Node getEmptyNode() { + final List emptyNodes = this.nodes.stream() + .filter(n -> n.used() == 0) + .collect(toList()); + assertTrue(emptyNodes.size() == 1, () -> "Expected 1 empty node"); + return emptyNodes.get(0); + } + + public Integer getMaxX() { + return this.nodes.stream() + .max(comparing(Node::x)) + .map(Node::x).orElseThrow(); + } + + public Integer getMaxY() { + return this.nodes.stream() + .max(comparing(Node::y)) + .map(Node::y).orElseThrow(); + } + + public Node getGoalNode() { + return this.nodes.stream() + .filter(n -> n.x() == getMaxX() && n.y() == 0) + .findFirst().orElseThrow(); + } + + public Node getAccessibleNode() { + return this.nodes.stream() + .filter(n -> n.x() == 0 && n.y() == 0) + .findFirst().orElseThrow(); + } + } } \ No newline at end of file diff --git a/src/main/java/AoC2016_23.java b/src/main/java/AoC2016_23.java index 3bf56550..fc52d855 100644 --- a/src/main/java/AoC2016_23.java +++ b/src/main/java/AoC2016_23.java @@ -1,3 +1,4 @@ +import static com.github.pareronia.aoc.StringOps.splitLines; import static java.util.stream.Collectors.toList; import java.util.List; @@ -6,30 +7,30 @@ import com.github.pareronia.aoc.StringUtils; import com.github.pareronia.aoc.assembunny.Assembunny; import com.github.pareronia.aoc.assembunny.Assembunny.AssembunnyInstruction; +import com.github.pareronia.aoc.solution.SolutionBase; import com.github.pareronia.aoc.vm.Program; import com.github.pareronia.aoc.vm.VirtualMachine; -import com.github.pareronia.aocd.Puzzle; -public final class AoC2016_23 extends AoCBase { +public final class AoC2016_23 + extends SolutionBase, Integer, Integer> { - private final transient List instructions; - - private AoC2016_23(final List inputs, final boolean debug) { + private AoC2016_23(final boolean debug) { super(debug); - log(inputs); - this.instructions = Assembunny.parse(inputs); } - public static AoC2016_23 create(final List input) { - return new AoC2016_23(input, false); + public static AoC2016_23 create() { + return new AoC2016_23(false); } - public static AoC2016_23 createDebug(final List input) { - return new AoC2016_23(input, true); + public static AoC2016_23 createDebug() { + return new AoC2016_23(true); } - private Integer solveVM(final Integer initA) { - final Program program = new Program(Assembunny.translate(this.instructions)); + private Integer solveVM( + final List instructions, + final Integer initA + ) { + final Program program = new Program(Assembunny.translate(instructions)); log(() -> program.getInstructions()); program.setRegisterValue("a", initA.longValue()); new VirtualMachine().runProgram(program); @@ -37,8 +38,11 @@ private Integer solveVM(final Integer initA) { return program.getRegisters().get("a").intValue(); } - private Integer solve(final Integer initA) { - final List values = this.instructions.stream() + private Integer solve( + final List instructions, + final Integer initA + ) { + final List values = instructions.stream() .filter(i -> List.of("cpy", "jnz").contains(i.getOperator())) .map(i -> i.getOperands().get(0)) .filter(StringUtils::isNumeric) @@ -48,65 +52,69 @@ private Integer solve(final Integer initA) { + Stream.iterate(1, i -> i <= initA, i -> i + 1) .reduce((a, b) -> a * b).orElseThrow(); } - + @Override - public Integer solvePart1() { - return solve(7); + protected List parseInput(final List inputs) { + return Assembunny.parse(inputs); } - + @Override - public Integer solvePart2() { - return solve(12); + public Integer solvePart1(final List instructions) { + return solve(instructions, 7); } + @Override + public Integer solvePart2(final List instructions) { + return solve(instructions, 12); + } + + @Override + public void samples() { + final AoC2016_23 test = AoC2016_23.createDebug(); + assert test.solveVM(test.parseInput(splitLines(TEST1)), 7) == 3; + assert test.solve(test.parseInput(splitLines(TEST2)), 7) == 11_610; + assert test.solve(test.parseInput(splitLines(TEST2)), 12) == 479_008_170; + } + public static void main(final String[] args) throws Exception { - assert AoC2016_23.createDebug(TEST1).solveVM(7) == 3; - assert AoC2016_23.createDebug(TEST2).solve(7) == 11_610; - assert AoC2016_23.createDebug(TEST2).solve(12) == 479_008_170; - - final Puzzle puzzle = Puzzle.create(2016, 23); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2016_23.create(input)::solvePart1), - () -> lap("Part 2", AoC2016_23.create(input)::solvePart2) - ); + AoC2016_23.create().run(); } - private static final List TEST1 = splitLines( - "cpy 2 a\n" + - "tgl a\n" + - "tgl a\n" + - "tgl a\n" + - "cpy 1 a\n" + - "dec a\n" + - "dec a" - ); - private static final List TEST2 = splitLines( - "cpy a b\n" + - "dec b\n" + - "cpy a d\n" + - "cpy 0 a\n" + - "cpy b c\n" + - "inc a\n" + - "dec c\n" + - "jnz c -2\n" + - "dec d\n" + - "jnz d -5\n" + - "dec b\n" + - "cpy b c\n" + - "cpy c d\n" + - "dec d\n" + - "inc c\n" + - "jnz d -2\n" + - "tgl c\n" + - "cpy -16 c\n" + - "jnz 1 c\n" + - "cpy 90 c\n" + - "jnz 73 d\n" + - "inc a\n" + - "inc d\n" + - "jnz d -2\n" + - "inc c\n" + - "jnz c -5" - ); + private static final String TEST1 = """ + cpy 2 a + tgl a + tgl a + tgl a + cpy 1 a + dec a + dec a + """; + private static final String TEST2 = """ + cpy a b + dec b + cpy a d + cpy 0 a + cpy b c + inc a + dec c + jnz c -2 + dec d + jnz d -5 + dec b + cpy b c + cpy c d + dec d + inc c + jnz d -2 + tgl c + cpy -16 c + jnz 1 c + cpy 90 c + jnz 73 d + inc a + inc d + jnz d -2 + inc c + jnz c -5 + """; } \ No newline at end of file diff --git a/src/main/java/AoC2016_24.java b/src/main/java/AoC2016_24.java index 5a8a5d37..8829fd12 100644 --- a/src/main/java/AoC2016_24.java +++ b/src/main/java/AoC2016_24.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; import java.util.TreeSet; @@ -23,11 +24,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public final class AoC2016_24 extends AoCBase { private static final char WALL = '#'; @@ -64,7 +60,7 @@ private List neighbours(final Path path) { .filter(cell -> this.grid.getValue(cell) != WALL) .map(newPos -> { final char value = this.grid.getValue(newPos); - if (this.pois.keySet().contains(value)) { + if (this.pois.containsKey(value)) { return Path.from(path, newPos, value); } else { return Path.from(path, newPos); @@ -120,18 +116,16 @@ private Integer findDistance(final boolean backHome) { return dist; } - @RequiredArgsConstructor(staticName = "of") - @EqualsAndHashCode - @ToString - private static final class FromTo { - private final char from; - private final char to; + record FromTo(char from, char to) { + public static FromTo of(final char from, final char to) { + return new FromTo(from, to); + } } public Integer solveAlt() { final List poiKeys = List.copyOf(this.pois.keySet()); final Map distances = new HashMap<>(); - combinations(poiKeys.size(), 2).forEach(a -> { + combinations(poiKeys.size(), 2).stream().forEach(a -> { final Character from = poiKeys.get(a[0]); final Character to = poiKeys.get(a[1]); final Path path = findPath(from, to); @@ -178,22 +172,16 @@ public static void main(final String[] args) throws Exception { } private static final List TEST = splitLines( - "###########\n" + - "#0.1.....2#\n" + - "#.#######.#\n" + - "#4.......3#\n" + - "###########" + """ + ########### + #0.1.....2# + #.#######.# + #4.......3# + ###########""" ); - @ToString(onlyExplicitlyIncluded = true) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) private static final class Path { - @Getter - @ToString.Include private final int length; - @Getter - @ToString.Include - @EqualsAndHashCode.Include private final Cell position; private final List pois; @@ -214,13 +202,10 @@ public static Path from(final Path other, final Cell position, return new Path(other.length + 1, position, pois); } - @ToString.Include - @EqualsAndHashCode.Include public Set getPois() { return new TreeSet<>(this.pois); } - @ToString.Include public List getPoisList() { return this.pois; } @@ -231,9 +216,48 @@ public int calcScore(final Cell destination) { + Math.abs(this.position.getCol() - destination.getCol()); } - @ToString.Include public int score() { return this.length * 100 + MAX_POIS - this.pois.size(); } + + public int getLength() { + return length; + } + + public Cell getPosition() { + return position; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Path other = (Path) obj; + return Objects.equals(position, other.position) + && Objects.equals(getPois(), other.getPois()); + } + + @Override + public int hashCode() { + return Objects.hash(getPois(), position); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Path [length=").append(length) + .append(", position=").append(position).append(", getPois=") + .append(getPois()).append(", getPoisList=") + .append(getPoisList()).append(", score=").append(score()) + .append("]"); + return builder.toString(); + } } } diff --git a/src/main/java/AoC2016_25.java b/src/main/java/AoC2016_25.java index 982e0eea..150c548a 100644 --- a/src/main/java/AoC2016_25.java +++ b/src/main/java/AoC2016_25.java @@ -1,49 +1,37 @@ -import static java.util.stream.Collectors.toList; - import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; -import com.github.pareronia.aoc.StringUtils; import com.github.pareronia.aoc.assembunny.Assembunny; import com.github.pareronia.aoc.assembunny.Assembunny.AssembunnyInstruction; +import com.github.pareronia.aoc.solution.SolutionBase; +import com.github.pareronia.aoc.vm.Instruction; import com.github.pareronia.aoc.vm.Program; import com.github.pareronia.aoc.vm.VirtualMachine; import com.github.pareronia.aoc.vm.VirtualMachine.InfiniteLoopException; -import com.github.pareronia.aocd.Puzzle; -public final class AoC2016_25 extends AoCBase { - - private final transient List instructions; +public final class AoC2016_25 + extends SolutionBase, Integer, String> { - private AoC2016_25(final List inputs, final boolean debug) { + private AoC2016_25(final boolean debug) { super(debug); - log(inputs); - this.instructions = Assembunny.parse(inputs); } - public static AoC2016_25 create(final List input) { - return new AoC2016_25(input, false); + public static AoC2016_25 create() { + return new AoC2016_25(false); } - public static AoC2016_25 createDebug(final List input) { - return new AoC2016_25(input, true); + public static AoC2016_25 createDebug() { + return new AoC2016_25(true); } - private Integer solveCheat() { - final List values = this.instructions.stream() - .filter(i -> "cpy".equals(i.getOperator())) - .map(i -> i.getOperands().get(0)) - .filter(StringUtils::isNumeric) - .map(Integer::valueOf) - .collect(toList()); - return 2730 - values.get(0) * 182; - } - - private List runProgram(final Integer initA) { + private List runProgram( + final List vmInstructions, + final Integer initA + ) { final List output = new ArrayList<>(); final Program program = new Program( - Assembunny.translate(this.instructions), + vmInstructions, 6_000, output::add); program.setRegisterValue("a", initA.longValue()); @@ -56,11 +44,13 @@ private List runProgram(final Integer initA) { return output; } - private Integer solveVM() { - int n = 0; + private Integer solveVM(final List instructions) { + final List vmInstructions + = Assembunny.translate(instructions); + int n = 150; while(true) { log(n); - final List output = runProgram(n); + final List output = runProgram(vmInstructions, n); if (Stream.iterate(0, i -> i < output.size(), i -> i + 1) .allMatch(i -> i % 2 == output.get(i))) { return n; @@ -70,56 +60,21 @@ private Integer solveVM() { } @Override - public Integer solvePart1() { - return solveVM(); + protected List parseInput(final List inputs) { + return Assembunny.parse(inputs); + } + + @Override + public Integer solvePart1(final List instructions) { + return solveVM(instructions); } @Override - public Integer solvePart2() { - return 0; + public String solvePart2(final List instructions) { + return "🎄"; } public static void main(final String[] args) throws Exception { - assert AoC2016_25.createDebug(TEST).solveCheat() == 182; - - final Puzzle puzzle = Puzzle.create(2016, 25); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2016_25.create(input)::solvePart1), - () -> lap("Part 2", AoC2016_25.create(input)::solvePart2) - ); + AoC2016_25.create().run(); } - - private static final List TEST = splitLines( - "cpy a d\n" + - "cpy 14 c\n" + - "cpy 182 b\n" + - "inc d\n" + - "dec b\n" + - "jnz b -2\n" + - "dec c\n" + - "jnz c -5\n" + - "cpy d a\n" + - "jnz 0 0\n" + - "cpy a b\n" + - "cpy 0 a\n" + - "cpy 2 c\n" + - "jnz b 2\n" + - "jnz 1 6\n" + - "dec b\n" + - "dec c\n" + - "jnz c -4\n" + - "inc a\n" + - "jnz 1 -7\n" + - "cpy 2 b\n" + - "jnz c 2\n" + - "jnz 1 4\n" + - "dec b\n" + - "dec c\n" + - "jnz 1 -4\n" + - "jnz 0 0\n" + - "out b\n" + - "jnz a -19\n" + - "jnz 1 -21" - ); } \ No newline at end of file diff --git a/src/main/java/AoC2017_01.java b/src/main/java/AoC2017_01.java index 6ca14483..6367350a 100644 --- a/src/main/java/AoC2017_01.java +++ b/src/main/java/AoC2017_01.java @@ -1,61 +1,60 @@ -import static java.util.stream.Collectors.summingInt; +import static com.github.pareronia.aoc.IntegerSequence.Range.range; import java.util.List; -import java.util.stream.Stream; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public final class AoC2017_01 extends AoCBase { +public final class AoC2017_01 extends SolutionBase { - private final transient String input; - - private AoC2017_01(final List inputs, final boolean debug) { + private AoC2017_01(final boolean debug) { super(debug); - assert inputs.size() == 1; - this.input = inputs.get(0); } - public static AoC2017_01 create(final List input) { - return new AoC2017_01(input, false); + public static AoC2017_01 create() { + return new AoC2017_01(false); } - public static AoC2017_01 createDebug(final List input) { - return new AoC2017_01(input, true); + public static AoC2017_01 createDebug() { + return new AoC2017_01(true); } - private Integer sumSameCharsAt(final int distance) { - final String test = this.input + this.input.substring(0, distance); - return Stream.iterate(0, i -> i < this.input.length(), i -> i + 1) + private int sumSameCharsAt(final String input, final int distance) { + final String test = input + input.substring(0, distance); + return range(input.length()).intStream() .filter(i -> test.charAt(i) == test.charAt(i + distance)) - .map(test::charAt) - .map(c -> Character.digit(c, 10)) - .collect(summingInt(Integer::valueOf)); + .map(i -> Character.digit(test.charAt(i), 10)) + .sum(); } - + + @Override + protected String parseInput(final List inputs) { + return inputs.get(0); + } + @Override - public Integer solvePart1() { - return sumSameCharsAt(1); + public Integer solvePart1(final String input) { + return sumSameCharsAt(input, 1); } @Override - public Integer solvePart2() { - assert this.input.length() % 2 == 0; - return sumSameCharsAt(this.input.length() / 2); + public Integer solvePart2(final String input) { + return sumSameCharsAt(input, input.length() / 2); } + @Samples({ + @Sample(method = "part1", input = "1122", expected = "3"), + @Sample(method = "part1", input = "1111", expected = "4"), + @Sample(method = "part1", input = "1234", expected = "0"), + @Sample(method = "part1", input = "91212129", expected = "9"), + @Sample(method = "part2", input = "1212", expected = "6"), + @Sample(method = "part2", input = "1221", expected = "0"), + @Sample(method = "part2", input = "123425", expected = "4"), + @Sample(method = "part2", input = "123123", expected = "12"), + @Sample(method = "part2", input = "12131415", expected = "4"), + }) public static void main(final String[] args) throws Exception { - assert AoC2017_01.createDebug(splitLines("1122")).solvePart1() == 3; - assert AoC2017_01.createDebug(splitLines("1111")).solvePart1() == 4; - assert AoC2017_01.createDebug(splitLines("1234")).solvePart1() == 0; - assert AoC2017_01.createDebug(splitLines("91212129")).solvePart1() == 9; - assert AoC2017_01.createDebug(splitLines("1212")).solvePart2() == 6; - assert AoC2017_01.createDebug(splitLines("1221")).solvePart2() == 0; - assert AoC2017_01.createDebug(splitLines("123425")).solvePart2() == 4; - assert AoC2017_01.createDebug(splitLines("123123")).solvePart2() == 12; - assert AoC2017_01.createDebug(splitLines("12131415")).solvePart2() == 4; - - final List input = Aocd.getData(2017, 1); - lap("Part 1", () -> AoC2017_01.create(input).solvePart1()); - lap("Part 2", () -> AoC2017_01.create(input).solvePart2()); + AoC2017_01.create().run(); } } diff --git a/src/main/java/AoC2017_02.java b/src/main/java/AoC2017_02.java index 701f19de..bcd8c442 100644 --- a/src/main/java/AoC2017_02.java +++ b/src/main/java/AoC2017_02.java @@ -38,7 +38,7 @@ private int differenceHighestLowest(final List numbers) { } private int evenlyDivisibleQuotient(final List numbers) { - for (final int[] c : combinations(numbers.size(), 2)) { + for (final int[] c : combinations(numbers.size(), 2).iterable()) { final int n1 = numbers.get(c[0]); final int n2 = numbers.get(c[1]); if (n1 > n2) { @@ -79,13 +79,15 @@ public static void main(final String[] args) throws Exception { } private static final List TEST1 = splitLines( - "5 1 9 5\n" + - "7 5 3\n" + - "2 4 6 8" + """ + 5 1 9 5 + 7 5 3 + 2 4 6 8""" ); private static final List TEST2 = splitLines( - "5 9 2 8\n" + - "9 4 7 3\n" + - "3 8 6 5" + """ + 5 9 2 8 + 9 4 7 3 + 3 8 6 5""" ); } diff --git a/src/main/java/AoC2017_03.java b/src/main/java/AoC2017_03.java index 4f59e662..3b78cbd0 100644 --- a/src/main/java/AoC2017_03.java +++ b/src/main/java/AoC2017_03.java @@ -10,8 +10,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public final class AoC2017_03 extends AoCBase { private final transient Integer input; @@ -30,10 +28,10 @@ public static AoC2017_03 createDebug(final List input) { return new AoC2017_03(input, true); } - @RequiredArgsConstructor(staticName = "of") - private static final class DirectionAndPeriod { - private final Direction direction; - private final int period; + record DirectionAndPeriod(Direction direction, int period) { + public static DirectionAndPeriod of(final Direction direction, final int period) { + return new DirectionAndPeriod(direction, period); + } } private static class CoordinateSupplier implements Supplier { diff --git a/src/main/java/AoC2017_04.java b/src/main/java/AoC2017_04.java index 0cdceee1..ce058e00 100644 --- a/src/main/java/AoC2017_04.java +++ b/src/main/java/AoC2017_04.java @@ -1,85 +1,79 @@ -import static java.util.stream.Collectors.counting; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; - import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.function.Predicate; +import com.github.pareronia.aoc.Counter; import com.github.pareronia.aoc.Utils; -import com.github.pareronia.aocd.Aocd; - -public final class AoC2017_04 extends AoCBase { +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; - private final transient List input; +public final class AoC2017_04 + extends SolutionBase, Integer, Integer> { - private AoC2017_04(final List inputs, final boolean debug) { + private AoC2017_04(final boolean debug) { super(debug); - this.input = inputs; } - public static AoC2017_04 create(final List input) { - return new AoC2017_04(input, false); + public static AoC2017_04 create() { + return new AoC2017_04(false); } - public static AoC2017_04 createDebug(final List input) { - return new AoC2017_04(input, true); + public static AoC2017_04 createDebug() { + return new AoC2017_04(true); } - private boolean hasNoDuplicateWords(final String string) { - return Arrays.stream(string.split(" ")) - .collect(groupingBy(sp -> sp, counting())) - .values().stream() - .noneMatch(c -> c > 1); + @Override + protected List parseInput(final List inputs) { + return inputs; } - - private boolean hasNoAnagrams(final String string) { - final List words = Arrays.stream(string.split(" ")) - .collect(toList()); - final Set> letterCounts = words.stream() - .map(w -> Utils.asCharacterStream(w) - .collect(groupingBy(c -> c, counting()))) - .collect(toSet()); - return words.size() == letterCounts.size(); + + private int solve( + final List input, + final Predicate> strategy + ) { + return (int) input.stream() + .map(s -> Arrays.asList(s.split(" "))) + .filter(strategy::test) + .count(); } @Override - public Integer solvePart1() { - return (int) this.input.stream() - .filter(this::hasNoDuplicateWords) - .count(); + public Integer solvePart1(final List input) { + final Predicate> hasNoDuplicateWords = words -> + new Counter<>(words).values().stream().noneMatch(v -> v > 1L); + return solve(input, hasNoDuplicateWords); } @Override - public Integer solvePart2() { - return (int) this.input.stream() - .filter(this::hasNoAnagrams) - .count(); + public Integer solvePart2(final List input) { + final Predicate> hasNoAnagrams = words -> + words.size() == words.stream() + .map(w -> new Counter<>(Utils.asCharacterStream(w))) + .distinct().count(); + return solve(input, hasNoAnagrams); } + @Samples({ + @Sample(method = "part1", input=TEST1, expected = "1"), + @Sample(method = "part1", input=TEST2, expected = "0"), + @Sample(method = "part1", input=TEST3, expected = "1"), + @Sample(method = "part2", input=TEST4, expected = "1"), + @Sample(method = "part2", input=TEST5, expected = "0"), + @Sample(method = "part2", input=TEST6, expected = "1"), + @Sample(method = "part2", input=TEST7, expected = "1"), + @Sample(method = "part2", input=TEST8, expected = "0"), + }) public static void main(final String[] args) throws Exception { - assert AoC2017_04.createDebug(TEST1).solvePart1() == 1; - assert AoC2017_04.createDebug(TEST2).solvePart1() == 0; - assert AoC2017_04.createDebug(TEST3).solvePart1() == 1; - assert AoC2017_04.createDebug(TEST4).solvePart2() == 1; - assert AoC2017_04.createDebug(TEST5).solvePart2() == 0; - assert AoC2017_04.createDebug(TEST6).solvePart2() == 1; - assert AoC2017_04.createDebug(TEST7).solvePart2() == 1; - assert AoC2017_04.createDebug(TEST8).solvePart2() == 0; - - final List input = Aocd.getData(2017, 4); - lap("Part 1", () -> AoC2017_04.create(input).solvePart1()); - lap("Part 2", () -> AoC2017_04.create(input).solvePart2()); + AoC2017_04.create().run(); } - private static final List TEST1 = splitLines("aa bb cc dd ee"); - private static final List TEST2 = splitLines("aa bb cc dd aa"); - private static final List TEST3 = splitLines("aa bb cc dd aaa"); - private static final List TEST4 = splitLines("abcde fghij"); - private static final List TEST5 = splitLines("abcde xyz ecdab"); - private static final List TEST6 = splitLines("a ab abc abd abf abj"); - private static final List TEST7 = splitLines("iiii oiii ooii oooi oooo"); - private static final List TEST8 = splitLines("oiii ioii iioi iiio"); + private static final String TEST1 = "aa bb cc dd ee"; + private static final String TEST2 = "aa bb cc dd aa"; + private static final String TEST3 = "aa bb cc dd aaa"; + private static final String TEST4 = "abcde fghij"; + private static final String TEST5 = "abcde xyz ecdab"; + private static final String TEST6 = "a ab abc abd abf abj"; + private static final String TEST7 = "iiii oiii ooii oooi oooo"; + private static final String TEST8 = "oiii ioii iioi iiio"; } diff --git a/src/main/java/AoC2017_05.java b/src/main/java/AoC2017_05.java index 260a9ef9..0f302b53 100644 --- a/src/main/java/AoC2017_05.java +++ b/src/main/java/AoC2017_05.java @@ -1,33 +1,40 @@ -import static java.util.stream.Collectors.toList; - +import java.util.Arrays; import java.util.List; -import java.util.function.Function; - -import com.github.pareronia.aocd.Aocd; +import java.util.function.IntUnaryOperator; -public final class AoC2017_05 extends AoCBase { +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; - private final transient List input; +public final class AoC2017_05 extends SolutionBase { - private AoC2017_05(final List inputs, final boolean debug) { + private AoC2017_05(final boolean debug) { super(debug); - this.input = inputs.stream().map(Integer::valueOf).collect(toList()); } - public static AoC2017_05 create(final List input) { - return new AoC2017_05(input, false); + public static AoC2017_05 create() { + return new AoC2017_05(false); } - public static AoC2017_05 createDebug(final List input) { - return new AoC2017_05(input, true); + public static AoC2017_05 createDebug() { + return new AoC2017_05(true); } - - private Integer countJumps(final Function jumpCalculator) { + + @Override + protected int[] parseInput(final List inputs) { + return inputs.stream().mapToInt(Integer::parseInt).toArray(); + } + + private int countJumps( + final int[] input, + final IntUnaryOperator jumpCalculator + ) { + final int[] offsets = Arrays.copyOf(input, input.length); int cnt = 0; int i = 0; - while (i < this.input.size()) { - final int jump = this.input.get(i); - this.input.set(i, jumpCalculator.apply(jump)); + while (i < offsets.length) { + final int jump = offsets[i]; + offsets[i] = jumpCalculator.applyAsInt(jump); i += jump; cnt++; } @@ -35,29 +42,28 @@ private Integer countJumps(final Function jumpCalculator) { } @Override - public Integer solvePart1() { - return countJumps(jump -> jump + 1); + public Integer solvePart1(final int[] input) { + return countJumps(input, jump -> jump + 1); } @Override - public Integer solvePart2() { - return countJumps(jump -> jump >= 3 ? jump - 1 : jump + 1); + public Integer solvePart2(final int[] input) { + return countJumps(input, jump -> jump >= 3 ? jump - 1 : jump + 1); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "5"), + @Sample(method = "part2", input = TEST, expected = "10"), + }) public static void main(final String[] args) throws Exception { - assert AoC2017_05.createDebug(TEST).solvePart1() == 5; - assert AoC2017_05.createDebug(TEST).solvePart2() == 10; - - final List input = Aocd.getData(2017, 5); - lap("Part 1", () -> AoC2017_05.create(input).solvePart1()); - lap("Part 2", () -> AoC2017_05.create(input).solvePart2()); + AoC2017_05.create().run(); } - private static final List TEST = splitLines( - "0\n" + - "3\n" + - "0\n" + - "1\n" + - "-3" - ); + private static final String TEST = """ + 0 + 3 + 0 + 1 + -3 + """; } diff --git a/src/main/java/AoC2017_06.java b/src/main/java/AoC2017_06.java index 80db86d5..d106e0ba 100644 --- a/src/main/java/AoC2017_06.java +++ b/src/main/java/AoC2017_06.java @@ -9,8 +9,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public final class AoC2017_06 extends AoCBase { private final transient List input; @@ -75,9 +73,5 @@ public static void main(final String[] args) throws Exception { ); } - @RequiredArgsConstructor - private static final class Result { - private final Map, Integer> map; - private final List last; - } + record Result(Map, Integer> map, List last) { } } diff --git a/src/main/java/AoC2017_07.java b/src/main/java/AoC2017_07.java index 236e090b..fab110f1 100644 --- a/src/main/java/AoC2017_07.java +++ b/src/main/java/AoC2017_07.java @@ -1,7 +1,6 @@ +import static com.github.pareronia.aoc.AssertUtils.unreachable; import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.summingInt; -import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; @@ -18,128 +17,44 @@ import java.util.Set; import com.github.pareronia.aoc.StringUtils; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.ToString; +public final class AoC2017_07 + extends SolutionBase { -public final class AoC2017_07 extends AoCBase { - - private final transient Map input; - - private AoC2017_07(final List inputs, final boolean debug) { + private AoC2017_07(final boolean debug) { super(debug); - this.input = parse(inputs); - buildTree(); - log(this.input); - } - - private Map parse(final List inputs) { - return inputs.stream() - .map(s -> { - final String[] sp = s.split(" -> "); - final String[] spsp = sp[0].split(" "); - final String name = spsp[0]; - final Integer weight = Integer.valueOf( - StringUtils.strip(spsp[1], "()")); - if (sp.length == 1) { - return new Node(name, weight, Set.of()); - } else { - final Set children = - Arrays.stream(sp[1].split(", ")).collect(toSet()); - return new Node(name, weight, children); - } - }) - .collect(toMap(n -> n.getName(), n -> n)); - } - - private void buildTree() { - this.input.values().forEach(n -> - n.setChildren(n.getChildKeys().stream() - .map(this.input::get) - .collect(toSet()))); - this.input.values().forEach(n -> { - this.input.values().stream() - .filter(p -> p.getChildren().contains(n)) - .findFirst() - .ifPresent(n::setParent); - n.setFullWeight(findFullWeight(n)); - }); - } - - private Integer findFullWeight(final Node node) { - if (node.getChildren().isEmpty()) { - return node.getWeight(); - } - return node.getWeight() - + node.getChildren().stream() - .map(this::findFullWeight) - .collect(summingInt(Integer::valueOf)); } - public static AoC2017_07 create(final List input) { - return new AoC2017_07(input, false); + public static AoC2017_07 create() { + return new AoC2017_07(false); } - public static AoC2017_07 createDebug(final List input) { - return new AoC2017_07(input, true); + public static AoC2017_07 createDebug() { + return new AoC2017_07(true); } - - private Node findRoot() { - return this.input.values().stream() - .filter(n -> !n.getParent().isPresent()) - .findFirst().orElseThrow(); - } - - private List getSiblings(final Node node) { - final Optional parent = node.getParent(); - if (parent.isEmpty()) { - return Collections.emptyList(); - } - return this.input.values().stream() - .filter(e -> e.getParent() - .map(p -> p.equals(parent.get())).orElse(false)) - .collect(toList()); - } - - private Node findUnbalancedNode() { - final Deque queue = new ArrayDeque<>(); - queue.add(findRoot().getChildren().iterator().next()); - while (!queue.isEmpty()) { - final Node node = queue.poll(); - final List siblings = getSiblings(node); - final HashMap weights = siblings.stream() - .collect(groupingBy(Node::getFullWeight, HashMap::new, counting())); - if (weights.size() == 1) { - return node.getParent().get(); - } - final int unbalancedWeight = weights.entrySet().stream() - .min(Comparator.comparing(Entry::getValue)) - .map(Entry::getKey).orElseThrow(); - final Node unbalanced = siblings.stream() - .filter(c -> c.getFullWeight() == unbalancedWeight) - .findFirst().orElseThrow(); - unbalanced.getChildren().forEach(queue::add); - } - throw new IllegalStateException("Unsolvable"); + + @Override + protected Tower parseInput(final List inputs) { + return Tower.fromInput(inputs); } @Override - public String solvePart1() { - return findRoot().getName(); + public String solvePart1(final Tower tower) { + return tower.findRoot().getName(); } @Override - public Integer solvePart2() { - final Node unbalanced = findUnbalancedNode(); + public Integer solvePart2(final Tower tower) { + final Node unbalanced = tower.findUnbalancedNode(); log("Unbalanced: " + unbalanced.getName()); - final Integer combinedWeight = unbalanced.getChildren().stream() - .map(Node::getFullWeight) - .collect(summingInt(Integer::valueOf)); + final int combinedWeight = unbalanced.getChildren().stream() + .mapToInt(Node::getFullWeight) + .sum(); log("Weight: " + unbalanced.getWeight() + " + " + combinedWeight); - final Integer desiredWeigth = getSiblings(unbalanced).stream() + final int desiredWeigth = tower.getSiblings(unbalanced).stream() .filter(n -> !n.equals(unbalanced)) .findFirst() .map(Node::getFullWeight) @@ -148,57 +63,175 @@ public Integer solvePart2() { return desiredWeigth - combinedWeight; } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "tknk"), + @Sample(method = "part2", input = TEST, expected = "60"), + }) public static void main(final String[] args) throws Exception { - assert AoC2017_07.createDebug(TEST).solvePart1().equals("tknk"); - assert AoC2017_07.createDebug(TEST).solvePart2() == 60; - - final Puzzle puzzle = Puzzle.create(2017, 7); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2017_07.create(input)::solvePart1), - () -> lap("Part 2", AoC2017_07.create(input)::solvePart2) - ); + AoC2017_07.create().run(); } - public static final List TEST = splitLines( - "pbga (66)\n" + - "xhth (57)\n" + - "ebii (61)\n" + - "havc (66)\n" + - "ktlj (57)\n" + - "fwft (72) -> ktlj, cntj, xhth\n" + - "qoyq (66)\n" + - "padx (45) -> pbga, havc, qoyq\n" + - "tknk (41) -> ugml, padx, fwft\n" + - "jptl (61)\n" + - "ugml (68) -> gyxo, ebii, jptl\n" + - "gyxo (61)\n" + - "cntj (57)" - ); + public static final String TEST = """ + pbga (66) + xhth (57) + ebii (61) + havc (66) + ktlj (57) + fwft (72) -> ktlj, cntj, xhth + qoyq (66) + padx (45) -> pbga, havc, qoyq + tknk (41) -> ugml, padx, fwft + jptl (61) + ugml (68) -> gyxo, ebii, jptl + gyxo (61) + cntj (57) + """; - @RequiredArgsConstructor - @ToString private static final class Node { - @Getter private final String name; - @Getter private final int weight; - @Getter private final Set childKeys; - @Setter - @ToString.Exclude private Node parent; - @Getter - @Setter private int fullWeight; - @Getter - @Setter - @ToString.Exclude private Set children; - @ToString.Include(name = "parent") + protected Node(final String name, final int weight, final Set childKeys) { + this.name = name; + this.weight = weight; + this.childKeys = childKeys; + } + + public static Node fromInput(final String line) { + final String[] sp = line.split(" -> "); + final String[] spsp = sp[0].split(" "); + final String name = spsp[0]; + final int weight = Integer.parseInt( + StringUtils.strip(spsp[1], "()")); + if (sp.length == 1) { + return new Node(name, weight, Set.of()); + } else { + final Set children = + Arrays.stream(sp[1].split(", ")).collect(toSet()); + return new Node(name, weight, children); + } + } + + private int findFullWeight() { + if (getChildren().isEmpty()) { + return getWeight(); + } + return getWeight() + + getChildren().stream() + .mapToInt(Node::findFullWeight) + .sum(); + } + + public int getFullWeight() { + return fullWeight; + } + + public void setFullWeight(final int fullWeight) { + this.fullWeight = fullWeight; + } + + public String getName() { + return name; + } + + public int getWeight() { + return weight; + } + + public Set getChildKeys() { + return childKeys; + } + + public Set getChildren() { + return children; + } + + public void setChildren(final Set children) { + this.children = children; + } + + public void setParent(final Node parent) { + this.parent = parent; + } + public Optional getParent() { return Optional.ofNullable(this.parent); } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Node [name=").append(name) + .append(", weight=").append(weight) + .append(", childKeys=").append(childKeys) + .append(", fullWeight=").append(fullWeight) + .append(", parent=").append(getParent()) + .append("]"); + return builder.toString(); + } + } + + record Tower(Map nodes) { + + public static Tower fromInput(final List inputs) { + final Map nodes = inputs.stream() + .map(Node::fromInput) + .collect(toMap(Node::getName, n -> n)); + nodes.values().forEach(n -> + n.setChildren(n.getChildKeys().stream() + .map(nodes::get) + .collect(toSet()))); + nodes.values().forEach(n -> { + nodes.values().stream() + .filter(p -> p.getChildren().contains(n)) + .findFirst() + .ifPresent(n::setParent); + n.setFullWeight(n.findFullWeight()); + }); + return new Tower(nodes); + } + + private Node findRoot() { + return this.nodes.values().stream() + .filter(n -> !n.getParent().isPresent()) + .findFirst().orElseThrow(); + } + + public List getSiblings(final Node node) { + final Optional parent = node.getParent(); + if (parent.isEmpty()) { + return Collections.emptyList(); + } + return nodes.values().stream() + .filter(e -> e.getParent() + .map(p -> p.equals(parent.get())).orElse(false)) + .toList(); + } + + public Node findUnbalancedNode() { + final Deque queue = new ArrayDeque<>(); + queue.add(findRoot().getChildren().iterator().next()); + while (!queue.isEmpty()) { + final Node node = queue.poll(); + final List siblings = getSiblings(node); + final Map weights = siblings.stream() + .collect(groupingBy(Node::getFullWeight, HashMap::new, counting())); + if (weights.size() == 1) { + return node.getParent().get(); + } + final int unbalancedWeight = weights.entrySet().stream() + .min(Comparator.comparing(Entry::getValue)) + .map(Entry::getKey).orElseThrow(); + final Node unbalanced = siblings.stream() + .filter(c -> c.getFullWeight() == unbalancedWeight) + .findFirst().orElseThrow(); + unbalanced.getChildren().forEach(queue::add); + } + throw unreachable(); + } } } diff --git a/src/main/java/AoC2017_09.java b/src/main/java/AoC2017_09.java index bdd36a62..09423c50 100644 --- a/src/main/java/AoC2017_09.java +++ b/src/main/java/AoC2017_09.java @@ -6,8 +6,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public final class AoC2017_09 extends AoCBase { private static final char ESCAPE = '!'; @@ -32,11 +30,7 @@ public static AoC2017_09 createDebug(final List input) { return new AoC2017_09(input, true); } - @RequiredArgsConstructor - private static final class Result { - private final int totalScore; - private final int nonCancelledChars; - } + record Result(int totalScore, int nonCancelledChars) { } private Result solve() { int open = 0; diff --git a/src/main/java/AoC2017_10.java b/src/main/java/AoC2017_10.java index 20968d02..13eac9ed 100644 --- a/src/main/java/AoC2017_10.java +++ b/src/main/java/AoC2017_10.java @@ -32,10 +32,9 @@ private Integer solve1(final List elements) { final List lengths = Arrays.stream(this.input.split(",")) .map(Integer::valueOf) .collect(toList()); - final State state = State.builder() - .elements(elements).lengths(lengths).cur(0).skip(0).build(); + final State state = new State(elements, lengths, 0, 0); final State ans = KnotHash.round(state); - return ans.getElements().get(0) * ans.getElements().get(1); + return ans.elements().get(0) * ans.elements().get(1); } @Override diff --git a/src/main/java/AoC2017_13.java b/src/main/java/AoC2017_13.java index 7617d305..0012cd0b 100644 --- a/src/main/java/AoC2017_13.java +++ b/src/main/java/AoC2017_13.java @@ -6,8 +6,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public final class AoC2017_13 extends AoCBase { private final List layers; @@ -71,9 +69,5 @@ public static void main(final String[] args) throws Exception { "6: 4" ); - @RequiredArgsConstructor - private static final class Layer { - private final int depth; - private final int range; - } + record Layer(int depth, int range) { } } diff --git a/src/main/java/AoC2017_15.java b/src/main/java/AoC2017_15.java index d423d41c..a26a22a8 100644 --- a/src/main/java/AoC2017_15.java +++ b/src/main/java/AoC2017_15.java @@ -1,70 +1,51 @@ +import static com.github.pareronia.aoc.IntegerSequence.Range.range; + +import java.util.Iterator; import java.util.List; import java.util.function.Predicate; -import com.github.pareronia.aoc.StringUtils; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public final class AoC2017_15 extends AoCBase { +public final class AoC2017_15 + extends SolutionBase { private static final long FACTOR_A = 16807; private static final long FACTOR_B = 48271; private static final long MOD = 2147483647; private static final Predicate NONE = x -> true; - private final transient long startA; - private final transient long startB; - - private AoC2017_15(final List inputs, final boolean debug) { + private AoC2017_15(final boolean debug) { super(debug); - assert inputs.size() == 2; - this.startA = Long.parseLong(inputs.get(0).split(" ")[4]); - this.startB = Long.parseLong(inputs.get(1).split(" ")[4]); } - public static AoC2017_15 create(final List input) { - return new AoC2017_15(input, false); + public static AoC2017_15 create() { + return new AoC2017_15(false); } - public static AoC2017_15 createDebug(final List input) { - return new AoC2017_15(input, true); - } - - private long next(final long prev, final long factor, final Predicate condition) { - long val = prev; - while (true) { - val = (val * factor) % MOD; - if (condition.test(val)) { - return val; - } - } + public static AoC2017_15 createDebug() { + return new AoC2017_15(true); } - - private void logValues(final int i, final long prevA, final long prevB) { - final String a = StringUtils.leftPad(String.valueOf(prevA), 10, ' '); - final String b = StringUtils.leftPad(String.valueOf(prevB), 10, ' '); - log("A" + (i + 1) + ": " + a + "\t" + "B" + (i + 1) + ": " + b); + + @Override + protected AoC2017_15.Generators parseInput(final List inputs) { + return new Generators( + Long.parseLong(inputs.get(0).split(" ")[4]), + Long.parseLong(inputs.get(1).split(" ")[4])); } private Integer solve( + final Generators generators, final int reps, final Predicate conditionA, final Predicate conditionB ) { - int cnt = 0; - long prevA = this.startA; - long prevB = this.startB; - for (int i = 0; i < reps; i++) { - prevA = next(prevA, FACTOR_A, conditionA); - prevB = next(prevB, FACTOR_B, conditionB); - if (i < 5) { - logValues(i, prevA, prevB); - } - if ((short) prevA == (short) prevB) { - cnt++; - } - } - log(cnt); - return cnt; + final Iterator iteratorA = generators.iteratorA(conditionA); + final Iterator iteratorB = generators.iteratorB(conditionB); + return (int) range(reps).intStream() + .filter(i -> iteratorA.next().shortValue() == iteratorB.next().shortValue()) + .count(); } private Predicate isMultipleOf(final int d) { @@ -72,29 +53,63 @@ private Predicate isMultipleOf(final int d) { } @Override - public Integer solvePart1() { - return solve(40_000_000, NONE, NONE); + public Integer solvePart1(final Generators generators) { + return solve(generators, 40_000_000, NONE, NONE); } @Override - public Integer solvePart2() { - return solve(5_000_000, isMultipleOf(4), isMultipleOf(8)); + public Integer solvePart2(final Generators generators) { + return solve(generators, 5_000_000, isMultipleOf(4), isMultipleOf(8)); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "588"), + @Sample(method = "part2", input = TEST, expected = "309"), + }) public static void main(final String[] args) throws Exception { - assert AoC2017_15.createDebug(TEST).solvePart1().equals(588); - assert AoC2017_15.createDebug(TEST).solvePart2().equals(309); - - final Puzzle puzzle = Puzzle.create(2017, 15); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2017_15.create(input)::solvePart1), - () -> lap("Part 2", AoC2017_15.create(input)::solvePart2) - ); + AoC2017_15.create().run(); } - private static final List TEST = splitLines( - "Generator A starts with 65\n" + - "Generator B starts with 8921" - ); + private static final String TEST = """ + Generator A starts with 65 + Generator B starts with 8921 + """; + + record Generators(long a, long b) { + + public Iterator iteratorA(final Predicate condition) { + return createIterator(a, FACTOR_A, condition); + } + + public Iterator iteratorB(final Predicate condition) { + return createIterator(b, FACTOR_B, condition); + } + + private Iterator createIterator( + final long seed, + final long factor, + final Predicate condition + ) { + return new Iterator<>() { + private long prev = seed; + + @Override + public Long next() { + long val = prev; + while (true) { + val = (val * factor) % MOD; + if (condition.test(val)) { + prev = val; + return val; + } + } + } + + @Override + public boolean hasNext() { + return true; + } + }; + } + } } diff --git a/src/main/java/AoC2017_16.java b/src/main/java/AoC2017_16.java index f9e9f89e..06d7cd81 100644 --- a/src/main/java/AoC2017_16.java +++ b/src/main/java/AoC2017_16.java @@ -10,8 +10,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public final class AoC2017_16 extends AoCBase { private static final String PROGRAMS = "abcdefghijklmnop"; @@ -140,23 +138,11 @@ public static void main(final String[] args) throws Exception { "s1,x3/4,pe/b" ); - private static abstract class Move { - } + private interface Move { } - @RequiredArgsConstructor - private static final class Spin extends Move { - private final int amount; - } + record Spin(int amount) implements Move { } - @RequiredArgsConstructor - private static final class Exchange extends Move { - private final int pos1; - private final int pos2; - } + record Exchange(int pos1, int pos2) implements Move { } - @RequiredArgsConstructor - private static final class Partner extends Move { - private final Character program1; - private final Character program2; - } + record Partner(Character program1, Character program2) implements Move { } } diff --git a/src/main/java/AoC2017_18.java b/src/main/java/AoC2017_18.java index bb356d09..1cf43368 100644 --- a/src/main/java/AoC2017_18.java +++ b/src/main/java/AoC2017_18.java @@ -41,29 +41,21 @@ private List buildInstructions( for (final String s : inputs) { final String[] splits = s.split(" "); switch (splits[0]) { - case "snd": + case "snd" -> instructions.add(sndBuilder.apply(getValue.apply(splits[1]))); - break; - case "set": + case "set" -> instructions.add(Instruction.SET(splits[1], getValue.apply(splits[2]))); - break; - case "add": + case "add" -> instructions.add(Instruction.ADD(splits[1], getValue.apply(splits[2]))); - break; - case "mul": + case "mul" -> instructions.add(Instruction.MUL(splits[1], getValue.apply(splits[2]))); - break; - case "mod": + case "mod" -> instructions.add(Instruction.MOD(splits[1], getValue.apply(splits[2]))); - break; - case "rcv": + case "rcv" -> instructions.add(rcvBuilder.apply(splits[1])); - break; - case "jgz": + case "jgz" -> instructions.add(Instruction.JG0(getValue.apply(splits[1]), getValue.apply(splits[2]))); - break; - default: - throw new IllegalStateException(); + default -> throw new IllegalStateException(); } } return instructions; @@ -98,8 +90,8 @@ public Integer solvePart2() { final long EMPTY = -1L; final List instructions = buildInstructions( this.input, - op -> Instruction.OUT(op), - op -> Instruction.INP(op)); + Instruction::OUT, + Instruction::INP); final Deque q0 = new ArrayDeque<>(); final Deque q1 = new ArrayDeque<>(); final MutableBoolean waiting0 = new MutableBoolean(false); @@ -176,24 +168,26 @@ public static void main(final String[] args) throws Exception { } private static final List TEST1 = splitLines( - "set a 1\r\n" + - "add a 2\r\n" + - "mul a a\r\n" + - "mod a 5\r\n" + - "snd a\r\n" + - "set a 0\r\n" + - "rcv a\r\n" + - "jgz a -1\r\n" + - "set a 1\r\n" + - "jgz a -2" + """ + set a 1\r + add a 2\r + mul a a\r + mod a 5\r + snd a\r + set a 0\r + rcv a\r + jgz a -1\r + set a 1\r + jgz a -2""" ); private static final List TEST2 = splitLines( - "snd 1\r\n" + - "snd 2\r\n" + - "snd p\r\n" + - "rcv a\r\n" + - "rcv b\r\n" + - "rcv c\r\n" + - "rcv d" + """ + snd 1\r + snd 2\r + snd p\r + rcv a\r + rcv b\r + rcv c\r + rcv d""" ); } diff --git a/src/main/java/AoC2017_20.java b/src/main/java/AoC2017_20.java index d94ddc4d..0eba94ed 100644 --- a/src/main/java/AoC2017_20.java +++ b/src/main/java/AoC2017_20.java @@ -15,11 +15,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import lombok.With; - public final class AoC2017_20 extends AoCBase { private static final int TICKS = 400; @@ -40,7 +35,7 @@ private Particle parse(final int number, final String input) { while (matcher.find()) { numbers.add(Integer.valueOf(matcher.group())); } - return new Particle(number, numbers.toArray(Integer[]::new)); + return Particle.create(number, numbers.toArray(Integer[]::new)); } public static AoC2017_20 create(final List input) { @@ -60,7 +55,7 @@ public Integer solvePart1() { return b.stream() .sorted(Particle.byDistance()) .findFirst() - .map(Particle::getNumber) + .map(Particle::number) .orElseThrow(); } @@ -102,26 +97,28 @@ public static void main(final String[] args) throws Exception { "p=< 3,0,0>, v=<-1,0,0>, a=<0,0,0>" ); - @RequiredArgsConstructor - @ToString - private static final class Particle { + record Particle( + int number, + Position3D position, + Position3D velocity, + Position3D acceleration + ) { private static final Position3D ORIGIN = Position3D.of(0, 0, 0); - - @Getter - private final int number; - @Getter - @With - private final Position3D position; - @With - private final Position3D velocity; - private final Position3D acceleration; - public Particle(final int number, final Integer[] numbers) { - this.number = number; + public static Particle create(final int number, final Integer[] numbers) { assert numbers.length == 9; - this.position = Position3D.of(numbers[0], numbers[1], numbers[2]); - this.velocity = Position3D.of(numbers[3], numbers[4], numbers[5]); - this.acceleration = Position3D.of(numbers[6], numbers[7], numbers[8]); + final Position3D position = Position3D.of(numbers[0], numbers[1], numbers[2]); + final Position3D velocity = Position3D.of(numbers[3], numbers[4], numbers[5]); + final Position3D acceleration = Position3D.of(numbers[6], numbers[7], numbers[8]); + return new Particle(number, position, velocity, acceleration); + } + + public Particle withPosition(final Position3D position) { + return new Particle(this.number, position, this.velocity, this.acceleration); + } + + public Particle withVelocity(final Position3D velocity) { + return new Particle(this.number, this.position, velocity, this.acceleration); } public Particle next() { @@ -141,7 +138,7 @@ public static Comparator byDistance() { public static Collector>> groupingByPosition() { - return groupingBy(Particle::getPosition, toList()); + return groupingBy(Particle::position, toList()); } } } diff --git a/src/main/java/AoC2017_21.java b/src/main/java/AoC2017_21.java index ca4f18be..1a602918 100644 --- a/src/main/java/AoC2017_21.java +++ b/src/main/java/AoC2017_21.java @@ -8,8 +8,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Getter; - public final class AoC2017_21 extends AoCBase { private static final CharGrid START = CharGrid.from(List.of(".#.", "..#", "###")); @@ -38,8 +36,8 @@ public static AoC2017_21 createDebug(final List input) { private CharGrid enhance(final CharGrid grid) { return this.rules.stream() - .filter(r -> r.getFrom().contains(grid)) - .map(Rule::getTo) + .filter(r -> r.from().contains(grid)) + .map(Rule::to) .findFirst().orElseThrow(); } @@ -83,14 +81,10 @@ public static void main(final String[] args) throws Exception { ".#./..#/### => #..#/..../..../#..#" ); - @Getter - private static final class Rule { - private final List from; - private final CharGrid to; + record Rule(List from, CharGrid to) { public Rule(final CharGrid from, final CharGrid to) { - this.from = Utils.stream(from.getPermutations()).collect(toList()); - this.to = to; + this(Utils.stream(from.getPermutations()).collect(toList()), to); } } } diff --git a/src/main/java/AoC2017_22.java b/src/main/java/AoC2017_22.java index c5e88660..86a66099 100644 --- a/src/main/java/AoC2017_22.java +++ b/src/main/java/AoC2017_22.java @@ -15,8 +15,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public final class AoC2017_22 extends AoCBase { private final Map input; @@ -117,10 +115,13 @@ public static void main(final String[] args) throws Exception { "..." ); - @RequiredArgsConstructor private enum State { CLEAN('.'), WEAKENED('W'), INFECTED('#'), FLAGGED('F'); + State(final char value) { + this.value = value; + } + private final char value; public static State fromValue(final char value) { diff --git a/src/main/java/AoC2017_23.java b/src/main/java/AoC2017_23.java index 3144f1c9..ea757eeb 100644 --- a/src/main/java/AoC2017_23.java +++ b/src/main/java/AoC2017_23.java @@ -37,20 +37,15 @@ private List buildInstructions(final List inputs) { for (final String s : inputs) { final String[] splits = s.split(" "); switch (splits[0]) { - case "set": + case "set" -> instructions.add(Instruction.SET(splits[1], getValue.apply(splits[2]))); - break; - case "sub": + case "sub" -> instructions.add(Instruction.SUB(splits[1], getValue.apply(splits[2]))); - break; - case "mul": + case "mul" -> instructions.add(Instruction.MUL(splits[1], getValue.apply(splits[2]))); - break; - case "jnz": + case "jnz" -> instructions.add(Instruction.JN0(getValue.apply(splits[1]), getValue.apply(splits[2]))); - break; - default: - throw new IllegalStateException(); + default -> throw new IllegalStateException(); } } return instructions; @@ -102,7 +97,7 @@ public Long solvePart2() { final long from = program.getRegisters().get("b"); final long to = program.getRegisters().get("c"); final long step = -1L * Long.parseLong((String) - instructions.get(instructions.size() - 2).getOperands().get(1)); + instructions.get(instructions.size() - 2).operands().get(1)); return LongStream.iterate(from, i -> i <= to, i -> i + step) .filter(i -> !isPrime(i)) .count(); diff --git a/src/main/java/AoC2017_24.java b/src/main/java/AoC2017_24.java index 6469842a..04a60f31 100644 --- a/src/main/java/AoC2017_24.java +++ b/src/main/java/AoC2017_24.java @@ -13,10 +13,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - public final class AoC2017_24 extends AoCBase { private final Set components; @@ -44,7 +40,7 @@ public static AoC2017_24 createDebug(final List input) { private Set getBridges() { final Function> adjacent = bridge -> this.components.stream() - .filter(c -> c.hasPort(bridge.getLast())) + .filter(c -> c.hasPort(bridge.last())) .filter(c -> !bridge.contains(c)) .map(c -> bridge.extend(c)); return BFS.floodFill(new Bridge(Set.of(), 0, 0), adjacent); @@ -54,7 +50,7 @@ private Set getBridges() { public Integer solvePart1() { return getBridges().stream() .peek(this::log) - .mapToInt(Bridge::getStrength) + .mapToInt(Bridge::strength) .max().getAsInt(); } @@ -64,7 +60,7 @@ public Integer solvePart2() { .sorted(Bridge.byLength().reversed() .thenComparing(Bridge.byStrength().reversed())) .findFirst() - .map(Bridge::getStrength).orElseThrow(); + .map(Bridge::strength).orElseThrow(); } public static void main(final String[] args) throws Exception { @@ -90,11 +86,7 @@ public static void main(final String[] args) throws Exception { "9/10" ); - @RequiredArgsConstructor - @EqualsAndHashCode - private static final class Component { - private final Integer leftPort; - private final Integer rightPort; + record Component(int leftPort, int rightPort) { public boolean hasPort(final int port) { return leftPort == port || rightPort == port; @@ -106,23 +98,7 @@ public String toString() { } } - @EqualsAndHashCode() - private static final class Bridge { - private final Set components; - @Getter - private final int strength; - @Getter - private final Integer last; - - private Bridge( - final Set components, - final int strength, - final Integer last - ) { - this.components = components; - this.strength = strength; - this.last = last; - } + record Bridge(Set components, int strength, int last) { public Bridge extend(final Component component) { final Set newComponents = new HashSet<>(this.components); @@ -144,7 +120,7 @@ public static Comparator byLength() { } public static Comparator byStrength() { - return (b1, b2) -> Integer.compare(b1.strength, b2.strength); + return Comparator.comparing(b1 -> b1.strength); } @Override diff --git a/src/main/java/AoC2017_25.java b/src/main/java/AoC2017_25.java index 7136a080..55b13ad8 100644 --- a/src/main/java/AoC2017_25.java +++ b/src/main/java/AoC2017_25.java @@ -5,9 +5,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public final class AoC2017_25 extends AoCBase { private final String start; @@ -114,17 +111,7 @@ public static void main(final String[] args) throws Exception { + " - Continue with state A." ); - @RequiredArgsConstructor - @ToString - private static final class Step { - private final int write; - private final int move; - private final String goTo; - } + record Step(int write, int move, String goTo) { } - @RequiredArgsConstructor - @ToString - private static final class State { - private final Step[] steps; - } + record State(Step[] steps) { } } \ No newline at end of file diff --git a/src/main/java/AoC2019_01.java b/src/main/java/AoC2019_01.java index 23ded37a..9f4a2d7f 100644 --- a/src/main/java/AoC2019_01.java +++ b/src/main/java/AoC2019_01.java @@ -1,39 +1,35 @@ -import static java.util.stream.Collectors.summingInt; -import static java.util.stream.Collectors.toList; - +import java.util.Arrays; import java.util.List; +import java.util.function.IntUnaryOperator; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2019_01 extends AoCBase { +public class AoC2019_01 extends SolutionBase { - private final List modules; - - private AoC2019_01(List input, boolean debug) { + private AoC2019_01(final boolean debug) { super(debug); - this.modules = input.stream().map(Integer::valueOf).collect(toList()); } - public static AoC2019_01 create(List input) { - return new AoC2019_01(input, false); + public static AoC2019_01 create() { + return new AoC2019_01(false); } - public static AoC2019_01 createDebug(List input) { - return new AoC2019_01(input, true); + public static AoC2019_01 createDebug() { + return new AoC2019_01(true); } - - private Integer fuelForMass(Integer m) { - return m / 3 - 2; + + @Override + protected int[] parseInput(final List inputs) { + return inputs.stream().mapToInt(Integer::parseInt).toArray(); } - @Override - public Integer solvePart1() { - return this.modules.stream() - .map(this::fuelForMass) - .collect(summingInt(Integer::intValue)); + private int fuelForMass(final int m) { + return m / 3 - 2; } - private Integer rocketEquation(Integer mass) { + private int rocketEquation(final int mass) { int totalFuel = 0; int fuel = fuelForMass(mass); while (fuel > 0) { @@ -42,26 +38,31 @@ private Integer rocketEquation(Integer mass) { } return totalFuel; } - + + private int sum(final int[] modules, final IntUnaryOperator strategy) { + return Arrays.stream(modules).map(strategy).sum(); + } + @Override - public Integer solvePart2() { - return this.modules.stream() - .map(this::rocketEquation) - .collect(summingInt(Integer::intValue)); + public Integer solvePart1(final int[] modules) { + return sum(modules, this::fuelForMass); } - public static void main(String[] args) throws Exception { - assert AoC2019_01.createDebug(splitLines("12")).solvePart1() == 2; - assert AoC2019_01.createDebug(splitLines("14")).solvePart1() == 2; - assert AoC2019_01.createDebug(splitLines("1969")).solvePart1() == 654; - assert AoC2019_01.createDebug(splitLines("100756")).solvePart1() == 33583; - assert AoC2019_01.createDebug(splitLines("12")).solvePart2() == 2; - assert AoC2019_01.createDebug(splitLines("1969")).solvePart2() == 966; - assert AoC2019_01.createDebug(splitLines("100756")).solvePart2() == 50346; - - final List input = Aocd.getData(2019, 1); - lap("Part 1", () -> AoC2019_01.create(input).solvePart1()); - lap("Part 2", () -> AoC2019_01.create(input).solvePart2()); + @Override + public Integer solvePart2(final int[] modules) { + return sum(modules, this::rocketEquation); } -} \ No newline at end of file + @Samples({ + @Sample(method = "part1", input = "12", expected = "2"), + @Sample(method = "part1", input = "14", expected = "2"), + @Sample(method = "part1", input = "1969", expected = "654"), + @Sample(method = "part1", input = "100756", expected = "33583"), + @Sample(method = "part2", input = "12", expected = "2"), + @Sample(method = "part2", input = "1969", expected = "966"), + @Sample(method = "part2", input = "100756", expected = "50346"), + }) + public static void main(final String[] args) throws Exception { + AoC2019_01.create().run(); + } +} diff --git a/src/main/java/AoC2019_02.java b/src/main/java/AoC2019_02.java index f1d65b00..1ebd6a25 100644 --- a/src/main/java/AoC2019_02.java +++ b/src/main/java/AoC2019_02.java @@ -5,54 +5,54 @@ import java.util.List; import com.github.pareronia.aoc.intcode.IntCode; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2019_02 extends AoCBase { +public class AoC2019_02 extends SolutionBase, Long, Integer> { - private final List program; - - private AoC2019_02(final List input, final boolean debug) { + private AoC2019_02(final boolean debug) { super(debug); - assert input.size() == 1; - this.program = IntCode.parse(input.get(0)); } - public static AoC2019_02 create(final List input) { - return new AoC2019_02(input, false); + public static AoC2019_02 create() { + return new AoC2019_02(false); } - public static AoC2019_02 createDebug(final List input) { - return new AoC2019_02(input, true); + public static AoC2019_02 createDebug() { + return new AoC2019_02(true); } - private Long runProgram(final long noun, final long verb) { - final List theProgram = new ArrayList<>(this.program); + private Long runProgram( + final List program, + final long noun, + final long verb + ) { + final List theProgram = new ArrayList<>(program); theProgram.set(1, noun); theProgram.set(2, verb); final IntCode intCode = new IntCode(theProgram, this.debug); intCode.run(theProgram); return intCode.getProgram().get(0); } - + + @Override + protected List parseInput(final List inputs) { + return IntCode.parse(inputs.get(0)); + } + @Override - public Long solvePart1() { - return runProgram(12, 2); + public Long solvePart1(final List program) { + return runProgram(program, 12, 2); } @Override - public Integer solvePart2() { + public Integer solvePart2(final List program) { return product(range(100), range(100)).stream() - .filter(p -> runProgram(p.get(0), p.get(1)) == 19_690_720) - .map(p -> 100 * p.get(0) + p.get(1)) + .filter(p -> runProgram(program, p.first(), p.second()) == 19_690_720) + .map(p -> 100 * p.first() + p.second()) .findFirst().orElseThrow(); } public static void main(final String[] args) throws Exception { - final Puzzle puzzle = Puzzle.create(2019, 2); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2019_02.create(input)::solvePart1), - () -> lap("Part 2", AoC2019_02.create(input)::solvePart2) - ); + AoC2019_02.create().run(); } } \ No newline at end of file diff --git a/src/main/java/AoC2019_03.java b/src/main/java/AoC2019_03.java index f6a76b9c..5d649509 100644 --- a/src/main/java/AoC2019_03.java +++ b/src/main/java/AoC2019_03.java @@ -11,9 +11,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - public class AoC2019_03 extends AoCBase { private static final Position ORIGIN = Position.of(0, 0); @@ -38,8 +35,8 @@ public static AoC2019_03 createDebug(final List input) { @Override public Integer solvePart1() { - final Set coords1 = new HashSet<>(wire1.getCoordinates()); - return wire2.getCoordinates().stream() + final Set coords1 = new HashSet<>(wire1.coordinates()); + return wire2.coordinates().stream() .filter(coords1::contains) .mapToInt(c -> c.manhattanDistance(ORIGIN)) .min().getAsInt(); @@ -47,8 +44,8 @@ public Integer solvePart1() { @Override public Integer solvePart2() { - final Set coords1 = new HashSet<>(wire1.getCoordinates()); - return wire2.getCoordinates().stream() + final Set coords1 = new HashSet<>(wire1.coordinates()); + return wire2.coordinates().stream() .filter(coords1::contains) .mapToInt(c -> wire1.steps(c) + wire2.steps(c)) .min().getAsInt(); @@ -83,10 +80,7 @@ public static void main(final String[] args) throws Exception { "U98,R91,D20,R16,D67,R40,U7,R15,U6,R7" ); - @RequiredArgsConstructor - private static final class Wire { - @Getter - private final List coordinates; + record Wire(List coordinates) { public static Wire fromString(final String string) { final List wireCoordinates = new ArrayList<>(); @@ -103,7 +97,7 @@ public static Wire fromString(final String string) { } public Integer steps(final Position coord) { - return this.getCoordinates().indexOf(coord) + 1; + return this.coordinates.indexOf(coord) + 1; } private static List toInstructions(final String input) { @@ -113,10 +107,7 @@ private static List toInstructions(final String input) { } } - @RequiredArgsConstructor - private static final class Instruction { - private final Direction direction; - private final int amount; + record Instruction(Direction direction, int amount) { public static Instruction fromString(final String str) { final Direction direction = Direction.fromString(str.substring(0, 1)); diff --git a/src/main/java/AoC2019_04.java b/src/main/java/AoC2019_04.java index bd1c910e..58206bf5 100644 --- a/src/main/java/AoC2019_04.java +++ b/src/main/java/AoC2019_04.java @@ -1,86 +1,86 @@ import static com.github.pareronia.aoc.Utils.asCharacterStream; -import static java.util.stream.Collectors.counting; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; +import java.util.Arrays; import java.util.List; import java.util.function.Predicate; -import java.util.stream.Stream; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.Counter; +import com.github.pareronia.aoc.IntegerSequence.Range; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2019_04 extends AoCBase { +public class AoC2019_04 extends SolutionBase { - private final Integer min; - private final Integer max; - - private AoC2019_04(List input, boolean debug) { + private AoC2019_04(final boolean debug) { super(debug); - assert input.size() == 1; - final String[] split = input.get(0).split("-"); - this.min = Integer.valueOf(split[0]); - this.max = Integer.valueOf(split[1]); } - public static AoC2019_04 create(List input) { - return new AoC2019_04(input, false); + public static AoC2019_04 create() { + return new AoC2019_04(false); } - public static AoC2019_04 createDebug(List input) { - return new AoC2019_04(input, true); + public static AoC2019_04 createDebug() { + return new AoC2019_04(true); } - - private boolean doesNotDecrease(String passw) { - final List chars = asCharacterStream(passw).collect(toList()); - final List sorted = asCharacterStream(passw).sorted().collect(toList()); - return chars.equals(sorted); + + @Override + protected Range parseInput(final List inputs) { + final String[] split = inputs.get(0).split("-"); + return Range.between(Integer.parseInt(split[0]), Integer.parseInt(split[1])); + } + + private boolean doesNotDecrease(final String passw) { + final char[] sorted = passw.toCharArray(); + Arrays.sort(sorted); + for (int i = 0; i < passw.length(); i++) { + if (passw.charAt(i) != sorted[i]) { + return false; + } + } + return true; } - private boolean isValid1(String string) { + private boolean isValid1(final String string) { return doesNotDecrease(string) && - asCharacterStream(string).collect(toSet()).size() != string.length(); + asCharacterStream(string).collect(toSet()).size() != string.length(); } - private boolean isValid2(String string) { + private boolean isValid2(final String string) { return doesNotDecrease(string) && - asCharacterStream(string).collect(groupingBy(c -> c, counting())) - .values().contains(2L); + new Counter<>(asCharacterStream(string)).containsValue(2L); } - private long countValid(Predicate isValid) { - return Stream.iterate(min, i -> i + 1).limit(max - min + 1) - .map(i -> i.toString()) + private long countValid(final Range range, final Predicate isValid) { + return range.intStream() + .mapToObj(String::valueOf) .filter(isValid::test) .count(); } @Override - public Long solvePart1() { - return countValid(this::isValid1); + public Long solvePart1(final Range range) { + return countValid(range, this::isValid1); } @Override - public Long solvePart2() { - return countValid(this::isValid2); + public Long solvePart2(final Range range) { + return countValid(range, this::isValid2); } - public static void main(String[] args) throws Exception { - assert AoC2019_04.createDebug(TEST).isValid1("122345") == true; - assert AoC2019_04.createDebug(TEST).isValid1("111123") == true; - assert AoC2019_04.createDebug(TEST).isValid1("111111") == true; - assert AoC2019_04.createDebug(TEST).isValid1("223450") == false; - assert AoC2019_04.createDebug(TEST).isValid1("123789") == false; - assert AoC2019_04.createDebug(TEST).isValid2("112233") == true; - assert AoC2019_04.createDebug(TEST).isValid2("123444") == false; - assert AoC2019_04.createDebug(TEST).isValid2("111122") == true; - - final List input = Aocd.getData(2019, 4); - lap("Part 1", () -> AoC2019_04.create(input).solvePart1()); - lap("Part 2", () -> AoC2019_04.create(input).solvePart2()); + @Override + public void samples() { + final AoC2019_04 test = AoC2019_04.createDebug(); + assert test.isValid1("122345"); + assert test.isValid1("111123"); + assert test.isValid1("111111"); + assert !test.isValid1("223450"); + assert !test.isValid1("123789"); + assert test.isValid2("112233"); + assert !test.isValid2("123444"); + assert test.isValid2("111122"); } - private static final List TEST = splitLines( - "0-0" - ); + public static void main(final String[] args) throws Exception { + AoC2019_04.create().run(); + } } \ No newline at end of file diff --git a/src/main/java/AoC2019_06.java b/src/main/java/AoC2019_06.java index 04c30b66..26ed9f3e 100644 --- a/src/main/java/AoC2019_06.java +++ b/src/main/java/AoC2019_06.java @@ -13,10 +13,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Value; - public class AoC2019_06 extends AoCBase { private static final String CENTER_OF_MASS = "COM"; @@ -51,8 +47,8 @@ private Graph parse(final List inputs) { @Override public Integer solvePart1() { - return this.graph.getEdges().stream() - .map(Edge::getDst) + return this.graph.edges().stream() + .map(Edge::dst) .map(dst -> this.graph.pathToRoot(dst).size() - 1) .collect(summingInt(Integer::intValue)); } @@ -108,28 +104,23 @@ public static void main(final String[] args) throws Exception { "I)SAN" ); - @Value - private static final class Edge { - private final String src; - private final String dst; - } + record Edge(String src, String dst) { } - @RequiredArgsConstructor - @Getter - private static final class Graph { - private final Set edges; - private final String root = CENTER_OF_MASS; - private final Map> paths = new HashMap<>(); + record Graph(Set edges, String root, Map> paths) { + + public Graph(final Set edges) { + this(edges, CENTER_OF_MASS, new HashMap<>()); + } public List pathToRoot(final String from) { if (!paths.containsKey(from)) { final Edge edge = findEdgeWithDst(from); final List path = new ArrayList<>(); path.add(from); - if (edge.getSrc().equals(root)) { + if (edge.src().equals(root)) { path.add(root); } else { - path.addAll(pathToRoot(edge.getSrc())); + path.addAll(pathToRoot(edge.src())); } paths.put(from, path); } @@ -138,7 +129,7 @@ public List pathToRoot(final String from) { private Edge findEdgeWithDst(final String dst) { final List found = this.edges.stream() - .filter(e -> e.getDst().equals(dst)) + .filter(e -> e.dst().equals(dst)) .collect(toList()); assert found.size() == 1; return found.get(0); diff --git a/src/main/java/AoC2019_08.java b/src/main/java/AoC2019_08.java index 2259101c..2136bdeb 100644 --- a/src/main/java/AoC2019_08.java +++ b/src/main/java/AoC2019_08.java @@ -7,72 +7,75 @@ import com.github.pareronia.aoc.CharGrid; import com.github.pareronia.aoc.OCR; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2019_08 extends AoCBase { +public class AoC2019_08 extends SolutionBase { private static final Integer WIDTH = 25; private static final Integer HEIGHT = 6; - private final List input; - - private AoC2019_08(final List input, final boolean debug) { + private AoC2019_08(final boolean debug) { super(debug); - this.input = input; } - public static AoC2019_08 create(final List input) { - return new AoC2019_08(input, false); + public static AoC2019_08 create() { + return new AoC2019_08(false); } - public static AoC2019_08 createDebug(final List input) { - return new AoC2019_08(input, true); + public static AoC2019_08 createDebug() { + return new AoC2019_08(true); } + + @Override + protected String parseInput(final List inputs) { + return inputs.get(0); + } - private CharGrid parse(final Integer width, final Integer height) { - assert this.input.size() == 1; - return CharGrid.from(this.input.get(0), width * height); + private CharGrid parse( + final String input, final int width, final int height + ) { + return CharGrid.from(input, width * height); } - - @Override - public Integer solvePart1() { - final CharGrid layers = parse(WIDTH, HEIGHT); + + @Override + public Integer solvePart1(final String input) { + final CharGrid layers = parse(input, WIDTH, HEIGHT); return layers.getRowsAsStringList().stream() .min(comparing(s -> countMatches(s, '0'))) .map(s -> countMatches(s, '1') * countMatches(s, '2')) - .orElseThrow(() -> new RuntimeException()); + .orElseThrow(); } - private String getImage(final Integer width, final Integer height) { - final CharGrid layers = parse(width, height); + private String getImage( + final String input, final int width, final int height + ) { + final CharGrid layers = parse(input, width, height); return Stream.iterate(0, i -> i + 1).limit(layers.getWidth()) .map(i -> layers.getRowsAsStringList().stream() .map(row -> row.charAt(i)) .filter(ch -> ch != '2') .findFirst() - .orElseThrow(() -> new RuntimeException())) + .orElseThrow()) .collect(toAString()); } @Override - public String solvePart2() { - final CharGrid image = CharGrid.from(getImage(WIDTH, HEIGHT), WIDTH); + public String solvePart2(final String input) { + final CharGrid image + = CharGrid.from(getImage(input, WIDTH, HEIGHT), WIDTH); log(image.replace('1', '\u2592').replace('0', ' ')); return OCR.convert6(image, '1', '0'); } - public static void main(final String[] args) throws Exception { - assert AoC2019_08.createDebug(TEST).getImage(2, 2).equals("0110"); + @Override + public void samples() { + final AoC2019_08 test = AoC2019_08.createDebug(); + assert test.getImage(TEST, 2, 2).equals("0110"); + } - final Puzzle puzzle = Puzzle.create(2019, 8); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2019_08.create(input)::solvePart1), - () -> lap("Part 2", AoC2019_08.create(input)::solvePart2) - ); + public static void main(final String[] args) throws Exception { + AoC2019_08.create().run(); } - private static final List TEST = splitLines( - "0222112222120000" - ); + private static final String TEST = "0222112222120000"; } \ No newline at end of file diff --git a/src/main/java/AoC2019_10.java b/src/main/java/AoC2019_10.java index 03250fa4..927f6241 100644 --- a/src/main/java/AoC2019_10.java +++ b/src/main/java/AoC2019_10.java @@ -10,79 +10,49 @@ import com.github.pareronia.aoc.CharGrid; import com.github.pareronia.aoc.Grid.Cell; import com.github.pareronia.aoc.geometry.Position; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -public class AoC2019_10 extends AoCBase { +public class AoC2019_10 + extends SolutionBase, Integer, Integer> { private static final char ASTEROID = '#'; - private final CharGrid grid; - - private AoC2019_10(final List input, final boolean debug) { + private AoC2019_10(final boolean debug) { super(debug); - this.grid = CharGrid.from(input); } - public static AoC2019_10 create(final List input) { - return new AoC2019_10(input, false); + public static AoC2019_10 create() { + return new AoC2019_10(false); } - public static AoC2019_10 createDebug(final List input) { - return new AoC2019_10(input, true); - } - - private Position asPosition(final Cell cell) { - return Position.of(cell.getCol(), cell.getRow()); - } - - private int distanceSquared(final Position a, final Position b) { - final int dx = a.getX() - b.getX(); - final int dy = a.getY() - b.getY(); - return dx * dx + dy * dy; - } - - private BinaryOperator closestTo(final Position pos) { - return (a, b) -> distanceSquared(a, pos) < distanceSquared(b, pos) ? a : b; + public static AoC2019_10 createDebug() { + return new AoC2019_10(true); } - - private double angle(final Position asteroid, final Position other) { - final double angle = Math.atan2( - other.getY() - asteroid.getY(), - other.getX() - asteroid.getX()) - + Math.PI / 2; - return angle < 0 ? angle + 2 * Math.PI : angle; + + @Override + protected List parseInput(final List inputs) { + final CharGrid grid = CharGrid.from(inputs); + return grid.getAllEqualTo(ASTEROID) + .map(asteroid -> Asteroid.from(asteroid, grid)) + .toList(); } - - private Map angles(final Position asteroid) { - return this.grid.getAllEqualTo(ASTEROID) - .map(this::asPosition) - .filter(other -> !other.equals(asteroid)) - .collect(toMap( - other -> angle(asteroid, other), - Function.identity(), - closestTo(asteroid), - TreeMap::new)); - } - - private Asteroid best() { - return this.grid.getAllEqualTo(ASTEROID) - .map(this::asPosition) - .map(asteroid -> new Asteroid(asteroid, angles(asteroid))) + + private Asteroid best(final List asteroids) { + return asteroids.stream() .sorted(Asteroid.byOthersCountDescending()) .findFirst().orElseThrow(); } @Override - public Integer solvePart1() { - return best().getOthers().size(); + public Integer solvePart1(final List asteroids) { + return best(asteroids).others().size(); } @Override - public Integer solvePart2() { - return best().getOthers().values().stream() + public Integer solvePart2(final List asteroids) { + return best(asteroids).others().values().stream() .skip(199) .limit(1) .findFirst() @@ -90,96 +60,128 @@ public Integer solvePart2() { .orElseThrow(); } + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "8"), + @Sample(method = "part1", input = TEST2, expected = "33"), + @Sample(method = "part1", input = TEST3, expected = "35"), + @Sample(method = "part1", input = TEST4, expected = "41"), + @Sample(method = "part1", input = TEST5, expected = "210"), + @Sample(method = "part2", input = TEST5, expected = "802"), + }) public static void main(final String[] args) throws Exception { - assert AoC2019_10.createDebug(TEST1).solvePart1() == 8; - assert AoC2019_10.createDebug(TEST2).solvePart1() == 33; - assert AoC2019_10.createDebug(TEST3).solvePart1() == 35; - assert AoC2019_10.createDebug(TEST4).solvePart1() == 41; - assert AoC2019_10.createDebug(TEST5).solvePart1() == 210; - assert AoC2019_10.createDebug(TEST5).solvePart2() == 802; - - final Puzzle puzzle = Puzzle.create(2019, 10); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2019_10.create(input)::solvePart1), - () -> lap("Part 2", AoC2019_10.create(input)::solvePart2) - ); + AoC2019_10.create().run(); } - private static final List TEST1 = splitLines( - ".#..#\r\n" + - ".....\r\n" + - "#####\r\n" + - "....#\r\n" + - "...##" - ); - private static final List TEST2 = splitLines( - "......#.#.\r\n" + - "#..#.#....\r\n" + - "..#######.\r\n" + - ".#.#.###..\r\n" + - ".#..#.....\r\n" + - "..#....#.#\r\n" + - "#..#....#.\r\n" + - ".##.#..###\r\n" + - "##...#..#.\r\n" + - ".#....####" - ); - private static final List TEST3 = splitLines( - "#.#...#.#.\r\n" + - ".###....#.\r\n" + - ".#....#...\r\n" + - "##.#.#.#.#\r\n" + - "....#.#.#.\r\n" + - ".##..###.#\r\n" + - "..#...##..\r\n" + - "..##....##\r\n" + - "......#...\r\n" + - ".####.###." - ); - private static final List TEST4 = splitLines( - ".#..#..###\r\n" + - "####.###.#\r\n" + - "....###.#.\r\n" + - "..###.##.#\r\n" + - "##.##.#.#.\r\n" + - "....###..#\r\n" + - "..#.#..#.#\r\n" + - "#..#.#.###\r\n" + - ".##...##.#\r\n" + - ".....#.#.." - ); - private static final List TEST5 = splitLines( - ".#..##.###...#######\r\n" + - "##.############..##.\r\n" + - ".#.######.########.#\r\n" + - ".###.#######.####.#.\r\n" + - "#####.##.#.##.###.##\r\n" + - "..#####..#.#########\r\n" + - "####################\r\n" + - "#.####....###.#.#.##\r\n" + - "##.#################\r\n" + - "#####.##.###..####..\r\n" + - "..######..##.#######\r\n" + - "####.##.####...##..#\r\n" + - ".#####..#.######.###\r\n" + - "##...#.##########...\r\n" + - "#.##########.#######\r\n" + - ".####.#.###.###.#.##\r\n" + - "....##.##.###..#####\r\n" + - ".#.#.###########.###\r\n" + - "#.#.#.#####.####.###\r\n" + - "###.##.####.##.#..##" - ); + private static final String TEST1 = """ + .#..# + ..... + ##### + ....# + ...## + """; + private static final String TEST2 = """ + ......#.#. + #..#.#.... + ..#######. + .#.#.###.. + .#..#..... + ..#....#.# + #..#....#. + .##.#..### + ##...#..#. + .#....#### + """; + private static final String TEST3 = """ + #.#...#.#. + .###....#. + .#....#... + ##.#.#.#.# + ....#.#.#. + .##..###.# + ..#...##.. + ..##....## + ......#... + .####.###. + """; + private static final String TEST4 = """ + .#..#..### + ####.###.# + ....###.#. + ..###.##.# + ##.##.#.#. + ....###..# + ..#.#..#.# + #..#.#.### + .##...##.# + .....#.#.. + """; + private static final String TEST5 = """ + .#..##.###...####### + ##.############..##. + .#.######.########.# + .###.#######.####.#. + #####.##.#.##.###.## + ..#####..#.######### + #################### + #.####....###.#.#.## + ##.################# + #####.##.###..####.. + ..######..##.####### + ####.##.####...##..# + .#####..#.######.### + ##...#.##########... + #.##########.####### + .####.#.###.###.#.## + ....##.##.###..##### + .#.#.###########.### + #.#.#.#####.####.### + ###.##.####.##.#..## + """; - @RequiredArgsConstructor - @Getter - private static final class Asteroid { - private final Position position; - private final Map others; + record Asteroid(Position position, Map others) { + + public static Asteroid from(final Cell cell, final CharGrid grid) { + final Position asteroid = Asteroid.asPosition(cell); + return new Asteroid(asteroid, angles(grid, asteroid)); + } public static Comparator byOthersCountDescending() { - return (a, b) -> Integer.compare(b.getOthers().size(), a.getOthers().size()); + return (a, b) -> Integer.compare(b.others().size(), a.others().size()); + } + + private static Position asPosition(final Cell cell) { + return Position.of(cell.getCol(), cell.getRow()); + } + + private static int distanceSquared(final Position a, final Position b) { + final int dx = a.getX() - b.getX(); + final int dy = a.getY() - b.getY(); + return dx * dx + dy * dy; + } + + private static BinaryOperator closestTo(final Position pos) { + return (a, b) -> distanceSquared(a, pos) < distanceSquared(b, pos) ? a : b; + } + + private static double angle(final Position asteroid, final Position other) { + final double angle = Math.atan2( + other.getY() - asteroid.getY(), + other.getX() - asteroid.getX()) + + Math.PI / 2; + return angle < 0 ? angle + 2 * Math.PI : angle; + } + + private static Map angles( + final CharGrid grid, final Position asteroid + ) { + return grid.getAllEqualTo(ASTEROID) + .map(Asteroid::asPosition) + .filter(other -> !other.equals(asteroid)) + .collect(toMap( + other -> angle(asteroid, other), + Function.identity(), + closestTo(asteroid), + TreeMap::new)); } } } \ No newline at end of file diff --git a/src/main/java/AoC2019_11.java b/src/main/java/AoC2019_11.java index 8a2a6d1b..47dd48a1 100644 --- a/src/main/java/AoC2019_11.java +++ b/src/main/java/AoC2019_11.java @@ -17,8 +17,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public class AoC2019_11 extends AoCBase { private static final long BLACK = 0; @@ -88,9 +86,5 @@ public static void main(final String[] args) throws Exception { ); } - @RequiredArgsConstructor - private static final class PaintJob { - private final Set visited; - private final Set white; - } + record PaintJob(Set visited, Set white) { } } \ No newline at end of file diff --git a/src/main/java/AoC2019_12.java b/src/main/java/AoC2019_12.java index c4e0656b..5d18f2ce 100644 --- a/src/main/java/AoC2019_12.java +++ b/src/main/java/AoC2019_12.java @@ -16,10 +16,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.ToString; - public class AoC2019_12 extends AoCBase { private final Position3D[] initialPositions; @@ -50,7 +46,7 @@ private int gravity(final Position3D a, final Position3D b, final GetAxis f) { } private void step(final Moon[] moons) { - combinations(moons.length, 2).forEach(idxs -> { + combinations(moons.length, 2).stream().forEach(idxs -> { final Moon a = moons[idxs[0]]; final Moon b = moons[idxs[1]]; final int dx = gravity(a.position, b.position, Position3D::getX); @@ -88,7 +84,7 @@ private boolean allOriginalPositions(final Moon[] moons, final GetAxis f) { public Long solvePart2() { final Moon[] moons = Arrays.stream(this.initialPositions) .map(Moon::create).toArray(Moon[]::new); - final GetAxis[] axes = new GetAxis[] { + final GetAxis[] axes = { Position3D::getX, Position3D::getY, Position3D::getZ }; final Map periods = new HashMap<>(); @@ -123,24 +119,29 @@ public static void main(final String[] args) throws Exception { } private static final List TEST1 = splitLines( - "\r\n" + - "\r\n" + - "\r\n" + - "" + """ + \r + \r + \r + """ ); private static final List TEST2 = splitLines( - "\r\n" + - "\r\n" + - "\r\n" + - "" + """ + \r + \r + \r + """ ); - @AllArgsConstructor(access = AccessLevel.PRIVATE) - @ToString private static final class Moon { private Position3D position; private Vector3D velocity; + protected Moon(final Position3D position, final Vector3D velocity) { + this.position = position; + this.velocity = velocity; + } + public static Moon create(final Position3D initialPosition) { return new Moon(initialPosition, Vector3D.of(0, 0, 0)); } @@ -156,6 +157,14 @@ public int totalEnergy() { public void adjustVelocity(final int dx, final int dy, final int dz) { this.velocity = this.velocity.add(Vector3D.of(dx, dy, dz), 1); } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Moon [position=").append(position) + .append(", velocity=").append(velocity).append("]"); + return builder.toString(); + } } private interface GetAxis extends Function { diff --git a/src/main/java/AoC2019_14.java b/src/main/java/AoC2019_14.java new file mode 100644 index 00000000..bd17bb0e --- /dev/null +++ b/src/main/java/AoC2019_14.java @@ -0,0 +1,193 @@ +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public class AoC2019_14 + extends SolutionBase, Long, Long> { + + private static final String ORE = "ORE"; + private static final String FUEL = "FUEL"; + private static final long ONE_TRILLION = 1_000_000_000_000L; + + private AoC2019_14(final boolean debug) { + super(debug); + } + + public static AoC2019_14 create() { + return new AoC2019_14(false); + } + + public static AoC2019_14 createDebug() { + return new AoC2019_14(true); + } + + @Override + protected Map parseInput(final List inputs) { + return inputs.stream() + .map(Reaction::fromString) + .collect(toMap(r -> r.material.name, r -> r)); + } + + @Override + public Long solvePart1(final Map input) { + return oreNeededFor(FUEL, 1, input, new HashMap<>()); + } + + @Override + public Long solvePart2(final Map input) { + final long part1 = oreNeededFor(FUEL, 1, input, new HashMap<>()); + long min = ONE_TRILLION / part1 / 10; + long max = ONE_TRILLION / part1 * 10; + while (min <= max) { + final long mid = (min + max) / 2; + final long ans = oreNeededFor(FUEL, mid, input, new HashMap<>()); + if (ans == ONE_TRILLION) { + return mid; + } else if (ans < ONE_TRILLION) { + min = mid + 1; + } else { + max = mid - 1; + } + } + return min - 1; + } + + private long oreNeededFor( + final String material, + final long amount, + final Map reactions, + final Map inventory + ) { + if (ORE.equals(material)) { + return amount; + } + final long available = inventory.getOrDefault(material, 0L); + if (amount <= available) { + inventory.put(material, available - amount); + return 0; + } else { + inventory.put(material, 0L); + } + + final Reaction reaction = reactions.get(material); + final long produced = reaction.material.amount; + final long needed = amount - available; + final long runs = (long) Math.ceil((double) needed / produced); + if (needed < produced * runs) { + inventory.merge(material, produced * runs - needed, Long::sum); + } + return reaction.reactants.stream() + .mapToLong(r -> oreNeededFor(r.name, r.amount * runs, reactions, inventory)) + .sum(); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "31"), + @Sample(method = "part1", input = TEST2, expected = "165"), + @Sample(method = "part1", input = TEST3, expected = "13312"), + @Sample(method = "part1", input = TEST4, expected = "180697"), + @Sample(method = "part1", input = TEST5, expected = "2210736"), + @Sample(method = "part2", input = TEST3, expected = "82892753"), + @Sample(method = "part2", input = TEST4, expected = "5586022"), + @Sample(method = "part2", input = TEST5, expected = "460664"), + }) + public void samples() {} + + public static void main(final String[] args) throws Exception { + AoC2019_14.create().run(); + } + + private static final String TEST1 = """ + 10 ORE => 10 A + 1 ORE => 1 B + 7 A, 1 B => 1 C + 7 A, 1 C => 1 D + 7 A, 1 D => 1 E + 7 A, 1 E => 1 FUEL + """; + private static final String TEST2 = """ + 9 ORE => 2 A + 8 ORE => 3 B + 7 ORE => 5 C + 3 A, 4 B => 1 AB + 5 B, 7 C => 1 BC + 4 C, 1 A => 1 CA + 2 AB, 3 BC, 4 CA => 1 FUEL + """; + private static final String TEST3 = """ + 157 ORE => 5 NZVS + 165 ORE => 6 DCFZ + 44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL + 12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ + 179 ORE => 7 PSHF + 177 ORE => 5 HKGWZ + 7 DCFZ, 7 PSHF => 2 XJWVT + 165 ORE => 2 GPVTF + 3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT + """; + private static final String TEST4 = """ + 2 VPVL, 7 FWMGM, 2 CXFTF, 11 MNCFX => 1 STKFG + 17 NVRVD, 3 JNWZP => 8 VPVL + 53 STKFG, 6 MNCFX, 46 VJHF, 81 HVMC, 68 CXFTF, 25 GNMV => 1 FUEL + 22 VJHF, 37 MNCFX => 5 FWMGM + 139 ORE => 4 NVRVD + 144 ORE => 7 JNWZP + 5 MNCFX, 7 RFSQX, 2 FWMGM, 2 VPVL, 19 CXFTF => 3 HVMC + 5 VJHF, 7 MNCFX, 9 VPVL, 37 CXFTF => 6 GNMV + 145 ORE => 6 MNCFX + 1 NVRVD => 8 CXFTF + 1 VJHF, 6 MNCFX => 4 RFSQX + 176 ORE => 6 VJHF + """; + private static final String TEST5 = """ + 171 ORE => 8 CNZTR + 7 ZLQW, 3 BMBT, 9 XCVML, 26 XMNCP, 1 WPTQ, 2 MZWV, 1 RJRHP => 4 PLWSL + 114 ORE => 4 BHXH + 14 VRPVC => 6 BMBT + 6 BHXH, 18 KTJDG, 12 WPTQ, 7 PLWSL, 31 FHTLT, 37 ZDVW => 1 FUEL + 6 WPTQ, 2 BMBT, 8 ZLQW, 18 KTJDG, 1 XMNCP, 6 MZWV, 1 RJRHP => 6 FHTLT + 15 XDBXC, 2 LTCX, 1 VRPVC => 6 ZLQW + 13 WPTQ, 10 LTCX, 3 RJRHP, 14 XMNCP, 2 MZWV, 1 ZLQW => 1 ZDVW + 5 BMBT => 4 WPTQ + 189 ORE => 9 KTJDG + 1 MZWV, 17 XDBXC, 3 XCVML => 2 XMNCP + 12 VRPVC, 27 CNZTR => 2 XDBXC + 15 KTJDG, 12 BHXH => 5 XCVML + 3 BHXH, 2 VRPVC => 7 MZWV + 121 ORE => 7 VRPVC + 7 XCVML => 6 RJRHP + 5 BHXH, 4 VRPVC => 5 LTCX + """; + + record Material(String name, int amount) { + + public static Material fromString(final String string) { + final StringSplit splitR = StringOps.splitOnce(string, " "); + return new Material(splitR.right(), Integer.parseInt(splitR.left())); + } + } + + record Reaction(Material material, Set reactants) { + + public static Reaction fromString(final String string) { + final StringSplit split = StringOps.splitOnce(string, " => "); + final Material target = Material.fromString(split.right()); + final Set reactants = Arrays.stream(split.left().split(", ")) + .map(Material::fromString) + .collect(toSet()); + return new Reaction(target, reactants); + } + } +} \ No newline at end of file diff --git a/src/main/java/AoC2019_15.java b/src/main/java/AoC2019_15.java index f78ea942..821b1b56 100644 --- a/src/main/java/AoC2019_15.java +++ b/src/main/java/AoC2019_15.java @@ -6,6 +6,7 @@ import java.util.Deque; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; @@ -18,11 +19,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public class AoC2019_15 extends AoCBase { private final List program; @@ -89,12 +85,15 @@ public Set floodFill(final Location start) { } } - @RequiredArgsConstructor private class DFS { private final Set positions; private int max = 0; private final Set seen = new HashSet<>(); + protected DFS(final Set positions) { + this.positions = positions; + } + public int dfs(final Position start) { if (seen.size() > max) { max = seen.size(); @@ -121,7 +120,7 @@ private Set adjacent(final Position position) { public Integer solvePart1() { return new FloodFill().floodFill(Location.START).stream() .filter(l -> l.isO2) - .map(Location::getMoves) + .map(Location::moves) .map(List::size) .findFirst().orElseThrow(); } @@ -131,10 +130,10 @@ public Integer solvePart2() { final Set locations = new FloodFill().floodFill(Location.START); final Set positions = locations.stream() - .map(Location::getPosition) + .map(Location::position) .collect(toSet()); final Position posO2 = locations.stream() - .filter(Location::isO2).map(Location::getPosition) + .filter(Location::isO2).map(Location::position) .findFirst().orElseThrow(); return new DFS(positions).dfs(posO2); } @@ -154,8 +153,8 @@ private enum Move { WEST(Direction.LEFT, 3), EAST(Direction.RIGHT, 4); - private long value; - private Direction direction; + private final long value; + private final Direction direction; Move(final Direction direction, final int value) { this.value = value; @@ -176,7 +175,7 @@ public Move reverse() { private enum Status { WALL(0), MOVED(1), FOUND_O2(2); - private long value; + private final long value; Status(final int value) { this.value = value; @@ -189,19 +188,27 @@ public static Status fromValue(final long value) { } } - @RequiredArgsConstructor - @ToString - @EqualsAndHashCode(onlyExplicitlyIncluded = true) - private static final class Location { + record Location(Position position, boolean isO2, List moves) { public static final Location START = new Location(Position.of(0, 0), false, List.of()); - - @Getter - @EqualsAndHashCode.Include - private final Position position; - @Getter - private final boolean isO2; - @Getter - private final List moves; + + @Override + public int hashCode() { + return Objects.hash(position); + } + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Location other = (Location) obj; + return Objects.equals(position, other.position); + } } } \ No newline at end of file diff --git a/src/main/java/AoC2019_16.java b/src/main/java/AoC2019_16.java index a04bd739..f207e8f1 100644 --- a/src/main/java/AoC2019_16.java +++ b/src/main/java/AoC2019_16.java @@ -11,11 +11,9 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public class AoC2019_16 extends AoCBase { - private static final int[] ELEMENTS = new int[] { 0, 1, 0, -1 }; + private static final int[] ELEMENTS = { 0, 1, 0, -1 }; private static final int PHASES = 100; private final List numbers; @@ -106,12 +104,15 @@ public static void main(final String[] args) throws Exception { private static final List TEST6 = splitLines( "03081770884921959731165446850517"); - @RequiredArgsConstructor private static final class Pattern implements Iterator { private final int repeat; private int i = 0; private int j = 0; + protected Pattern(final int repeat) { + this.repeat = repeat; + } + @Override public boolean hasNext() { return true; diff --git a/src/main/java/AoC2019_17.java b/src/main/java/AoC2019_17.java index 67a3e7e4..92dae385 100644 --- a/src/main/java/AoC2019_17.java +++ b/src/main/java/AoC2019_17.java @@ -1,55 +1,59 @@ +import static com.github.pareronia.aoc.AssertUtils.assertTrue; import static com.github.pareronia.aoc.IntegerSequence.Range.range; +import static com.github.pareronia.aoc.StringOps.splitLines; +import static com.github.pareronia.aoc.Utils.concatAll; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collections; import java.util.Deque; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.function.Predicate; import com.github.pareronia.aoc.CharGrid; import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.ListUtils; import com.github.pareronia.aoc.geometry.Direction; import com.github.pareronia.aoc.geometry.Turn; import com.github.pareronia.aoc.intcode.IntCode; -import com.github.pareronia.aocd.Aocd; -import com.github.pareronia.aocd.Puzzle; -import com.github.pareronia.aocd.SystemUtils; -import com.github.pareronia.aocd.User; +import com.github.pareronia.aoc.solution.Logger; +import com.github.pareronia.aoc.solution.LoggerEnabled; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.EqualsAndHashCode; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -public class AoC2019_17 extends AoCBase { +public class AoC2019_17 extends SolutionBase, Integer, Integer> { private static final char SCAFFOLD = '#'; private static final char NEWLINE = '\n'; - private final List program; - - private AoC2019_17(final List input, final boolean debug) { + private AoC2019_17(final boolean debug) { super(debug); - assert input.size() == 1; - this.program = IntCode.parse(input.get(0)); } - public static AoC2019_17 create(final List input) { - return new AoC2019_17(input, false); + public static AoC2019_17 create() { + return new AoC2019_17(false); } - public static AoC2019_17 createDebug(final List input) { - return new AoC2019_17(input, true); + public static AoC2019_17 createDebug() { + return new AoC2019_17(true); + } + + @Override + protected List parseInput(final List inputs) { + return IntCode.parse(inputs.get(0)); } @Override - public Integer solvePart1() { + public Integer solvePart1(final List program) { final CharGrid grid = new GridBuilder().build( - new IntCodeComputer(this.program, this.debug).runCamera()); + new IntCodeComputer(program, this.debug).runCamera()); log(grid); return grid.getAllEqualTo(SCAFFOLD) .filter(cell -> grid.getCapitalNeighbours(cell) @@ -58,63 +62,44 @@ public Integer solvePart1() { .sum(); } - private List findPath(final CharGrid grid) { - final PathFinder pathFinder = new PathFinder(grid); - final List path = pathFinder.findPath(); - log("Path: " + path); - final List moves = pathFinder.toMoves(path); - log("Moves: " + moves); - final List> commands = pathFinder.toCommands(moves); - log("Commands: " + commands); - final List compressed = pathFinder.compressCommands(commands); - log("Compressed: " + compressed.stream().map(Command::toString).collect(joining(","))); - return compressed; - } - @Override - public Integer solvePart2() { - final IntCodeComputer computer = new IntCodeComputer(this.program, this.debug); + public Integer solvePart2(final List program) { + final IntCodeComputer computer = new IntCodeComputer(program, this.debug); final CharGrid grid = new GridBuilder().build(computer.runCamera()); - findPath(grid); - final SystemUtils systemUtils = new SystemUtils(); - final List input = systemUtils.readAllLines( - User.getDefaultUser().getMemoDir().resolve("2019_17_input_extra.txt")); + final List input = new PathFinder(grid, this.logger).createAsciiInput(5, 3); return computer.runRobot(input).getLast().intValue(); } + @Override + protected void samples() { + assert new PathFinder(CharGrid.from(splitLines(TEST)), new Logger(true)) + .createAsciiInput(3, 2) + .equals(List.of("A,B,C,B,A,C", "R,8,R,8", "R,4,R,4,R,8", "L,6,L,2", "n")); + } + public static void main(final String[] args) throws Exception { - assert AoC2019_17.createDebug(List.of("1")).findPath(CharGrid.from(TEST)) - .stream().map(Command::toString).collect(joining(",")) - .equals("R,8,R,8,R,4,R,4,R,8,L,6,L,2,R,4,R,4,R,8,R,8,R,8,L,6,L,2"); - - final Puzzle puzzle = Aocd.puzzle(2019, 17); - final List inputData = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2019_17.create(inputData)::solvePart1), - () -> lap("Part 2", AoC2019_17.create(inputData)::solvePart2) - ); + AoC2019_17.create().run(); } - private static final List TEST = splitLines( - "#######...#####\r\n" + - "#.....#...#...#\r\n" + - "#.....#...#...#\r\n" + - "......#...#...#\r\n" + - "......#...###.#\r\n" + - "......#.....#.#\r\n" + - "^########...#.#\r\n" + - "......#.#...#.#\r\n" + - "......#########\r\n" + - "........#...#..\r\n" + - "....#########..\r\n" + - "....#...#......\r\n" + - "....#...#......\r\n" + - "....#...#......\r\n" + - "....#####......" - ); + private static final String TEST = """ + #######...##### + #.....#...#...# + #.....#...#...# + ......#...#...# + ......#...###.# + ......#.....#.# + ^########...#.# + ......#.#...#.# + ......######### + ........#...#.. + ....#########.. + ....#...#...... + ....#...#...... + ....#...#...... + ....#####...... + """; - @RequiredArgsConstructor - private final class GridBuilder { + private static final class GridBuilder { public CharGrid build(final Deque output) { return CharGrid.from(asStrings(output)); @@ -136,36 +121,31 @@ private List asStrings(final Deque output) { } } - @RequiredArgsConstructor - private static final class Move { - private final Cell from; - private final Cell to; - private final Direction direction; - - @Override - public String toString() { - return String.format("%s -> %s : %s", this.from, this.to, this.direction); - } - } - - @RequiredArgsConstructor - private static final class Command { - private final char letter; - private final int count; + record Command(char letter, int count) { @Override public String toString() { if (this.count == 1) { return String.valueOf(this.letter); } else { - return String.format("%s,%d", this.letter, this.count); + return String.format("%s(%d)", this.letter, this.count); } } } - @RequiredArgsConstructor - private static final class PathFinder { + private static final class PathFinder implements LoggerEnabled { private final CharGrid grid; + private final Logger logger; + + public PathFinder(final CharGrid grid, final Logger logger) { + this.grid = grid; + this.logger = logger; + } + + @Override + public Logger getLogger() { + return this.logger; + } public List findPath() { final Cell robot = grid.findAllMatching(Direction.CAPITAL_ARROWS::contains) @@ -182,27 +162,16 @@ public List findPath() { return path.stream().collect(toList()); } - public List toMoves(final List path) { - final Cell start = path.get(0); - final List moves = new ArrayList<>(); - moves.add(new Move(start, start, Direction.fromChar(grid.getValue(start)))); - range(path.size() - 1).forEach(i -> { - final Direction move = path.get(i).to(path.get(i + 1)); - moves.add(new Move(path.get(i), path.get(i + 1), move)); - }); - return moves; - } - - public List> toCommands(final List moves) { + public List toCommands(final List moves) { final List> commands = new ArrayList<>(); final Deque curr = new ArrayDeque<>(); range(moves.size() - 1).forEach(i -> { final Command last = curr.peekLast(); - if (moves.get(i).direction == moves.get(i + 1).direction){ + if (moves.get(i) == moves.get(i + 1)){ curr.addLast(new Command(last.letter, 1)); } else { final Turn turn = Turn.fromDirections( - moves.get(i).direction, moves.get(i + 1).direction); + moves.get(i), moves.get(i + 1)); if (last != null) { commands.add(curr.stream().collect(toList())); curr.clear(); @@ -211,16 +180,70 @@ public List> toCommands(final List moves) { } }); commands.add(curr.stream().collect(toList())); - return commands; + return commands.stream() + .map(lst -> new Command(lst.get(0).letter, lst.size())) + .toList(); } - public List compressCommands(final List> commands) { - final List compressed = new ArrayList<>(); - for (final List list : commands) { - assert list.stream().map(c -> c.letter).distinct().count() == 1; - compressed.add(new Command(list.get(0).letter, list.size())); + public List createAsciiInput( + final int maxSize, final int minRepeats + ) { + final List path = this.findPath(); + log("Path: " + path); + final List moves = concatAll( + List.of(Direction.fromChar(grid.getValue(path.get(0)))), + range(path.size() - 1).intStream() + .mapToObj(i1 -> path.get(i1).to(path.get(i1 + 1))) + .toList()); + log("Moves: " + moves); + final List commands = this.toCommands(moves); + log("Commands: " + commands); + List lst = new ArrayList<>(commands); + final Map> map = new HashMap<>(); + for (final String x : List.of("A", "B", "C")) { + for (int i = maxSize; i >= 2; i--) { + if (i > lst.size()) { + continue; + } + final List idxs = ListUtils.indexesOfSubList( + lst, lst.subList(0, i)); + if (idxs.size() < minRepeats) { + continue; + } else { + map.put(x, new ArrayList<>(lst.subList(0, i))); + lst = ListUtils.subtractAll(lst, lst.subList(0, i)); + break; + } + } } - return compressed; + log(map); + assertTrue(map.size() == 3, () -> "could not find functions"); + final List main = new ArrayList<>(); + lst = new ArrayList<>(commands); + int cnt = 0; + while (!lst.isEmpty()) { + assertTrue(cnt < 100, () -> "infinite loop"); + for (final Entry> func : map.entrySet()) { + if (Collections.indexOfSubList(lst, func.getValue()) == 0) { + main.add(func.getKey()); + lst = lst.subList(func.getValue().size(), lst.size()); + log(lst); + break; + } + } + cnt++; + } + log(main); + final List input = new ArrayList<>(); + input.add(main.stream().collect(joining(","))); + List.of("A", "B", "C").stream() + .map(x -> map.get(x).stream() + .map(c -> "%s,%d".formatted(c.letter, c.count)) + .collect(joining(","))) + .forEach(input::add); + input.add("n"); + log(input); + return input; } private static final class DFS { @@ -280,20 +303,11 @@ public boolean dfs() { return false; } - @RequiredArgsConstructor - @EqualsAndHashCode - @ToString - private static final class State { - private final Cell prev; - private final Cell curr; - } + record State(Cell prev, Cell curr) {} } } - @RequiredArgsConstructor - private static final class IntCodeComputer { - private final List program; - private final boolean debug; + record IntCodeComputer(List program, boolean debug) { public Deque runCamera() { final IntCode intCode = new IntCode(this.program, this.debug); @@ -304,9 +318,9 @@ public Deque runCamera() { } public Deque runRobot(final List commands) { - final List newProgram = new ArrayList<>(); - newProgram.add(2L); - newProgram.addAll(this.program.subList(1, this.program.size())); + final List newProgram = concatAll( + List.of(2L), + this.program.subList(1, this.program.size())); final IntCode intCode = new IntCode(newProgram, this.debug); final Deque input = new ArrayDeque<>(); final Deque output = new ArrayDeque<>(); diff --git a/src/main/java/AoC2019_19.java b/src/main/java/AoC2019_19.java new file mode 100644 index 00000000..34734b76 --- /dev/null +++ b/src/main/java/AoC2019_19.java @@ -0,0 +1,145 @@ +import static com.github.pareronia.aoc.StringOps.splitLines; +import static java.util.stream.Stream.iterate; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; + +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.intcode.IntCode; +import com.github.pareronia.aoc.solution.SolutionBase; + +public class AoC2019_19 extends SolutionBase { + + private AoC2019_19(final boolean debug) { + super(debug); + } + + public static AoC2019_19 create() { + return new AoC2019_19(false); + } + + public static AoC2019_19 createDebug() { + return new AoC2019_19(true); + } + + @Override + protected Beam parseInput(final List inputs) { + return Beam.fromString(inputs.get(0)); + } + + @Override + public Integer solvePart1(final Beam beam) { + return solve1(beam, 50); + } + + private int solve1(final Beam beam, final int size) { + return (int) iterate(0, r -> r < size, r -> r + 1) + .flatMap(r -> + iterate(0, c -> c < size, c -> c + 1).map(c -> Cell.at(r, c)) + ) + .filter(beam::contains) + .count(); + } + + @Override + public Integer solvePart2(final Beam beam) { + return solve2(beam, 100); + } + + private int solve2(final Beam beam, final int size) { + int r = size - 1, c = 0; + while (true) { + while (!beam.contains(Cell.at(r, c))) { + c++; + } + if (beam.contains(Cell.at(r - (size - 1), c + (size - 1)))) { + return (r - (size - 1)) * 10_000 + c; + } + r++; + } + } + + @Override + public void samples() { + assert solve1(parseInput(splitLines(TEST1)), 10) == 27; + assert solve2(parseInput(splitLines(TEST2)), 10) == 250020; + } + + public static void main(final String[] args) throws Exception { + AoC2019_19.create().run(); + } + + record Beam(List program) { + + public static Beam fromString(final String string) { + return new Beam(IntCode.parse(string)); + } + + public boolean contains(final Cell cell) { + final Deque input = new ArrayDeque<>(); + input.add((long) cell.getRow()); + input.add((long) cell.getCol()); + final Deque output = new ArrayDeque<>(); + new IntCode(program, false).runTillHasOutput(input, output); + return output.getFirst() == 1; + } + } + + private static final String TEST1 = """ + 3,20,3,21,2,21,22,24,1,24,20,24,9,24,204,25,99,0,0,0,0,0,10,10,\ + 0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,\ + 0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,\ + 0,0,0,1,1,1,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,\ + 0,0,0,1,1 + """; + private static final String TEST2 = """ + 3,20,3,21,2,21,22,24,1,24,20,24,9,24,204,25,99,0,0,0,0,0,40,35,\ + 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,\ + 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,\ + 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,\ + 1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,\ + 1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,\ + 1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,\ + 1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,\ + 1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,\ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,\ + 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,\ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,\ + 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,\ + 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,\ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,\ + 1,1,1,1,1,1 + """; +} \ No newline at end of file diff --git a/src/main/java/AoC2020_01.java b/src/main/java/AoC2020_01.java index f22f40e6..9fdcfda4 100644 --- a/src/main/java/AoC2020_01.java +++ b/src/main/java/AoC2020_01.java @@ -1,70 +1,75 @@ -import static java.util.stream.Collectors.toList; +import static com.github.pareronia.aoc.AssertUtils.unreachable; import java.util.HashSet; import java.util.List; import java.util.Set; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2020_01 extends AoCBase { +public class AoC2020_01 extends SolutionBase, Long, Long> { - private final List numbers; - - private AoC2020_01(List input, boolean debug) { + private AoC2020_01(final boolean debug) { super(debug); - this.numbers = input.stream().map(Integer::valueOf).collect(toList()); } - public static AoC2020_01 create(List input) { - return new AoC2020_01(input, false); + public static AoC2020_01 create() { + return new AoC2020_01(false); } - + + public static AoC2020_01 createDebug() { + return new AoC2020_01(true); + } + @Override - public Long solvePart1() { - final Set seen = new HashSet<>(); - for (final Integer n1 : this.numbers) { + protected List parseInput(final List inputs) { + return inputs.stream().map(Integer::valueOf).toList(); + } + + @Override + public Long solvePart1(final List numbers) { + final Set seen = new HashSet<>(numbers.size()); + for (final Integer n1 : numbers) { seen.add(n1); final Integer n2 = 2020 - n1; if (seen.contains(n2)) { return (long) (n1 * n2); } } - return 0L; + throw unreachable(); } @Override - public Long solvePart2() { - final Set seen = new HashSet<>(); - for (int i = 0; i < this.numbers.size(); i++) { - final Integer n1 = this.numbers.get(i); + public Long solvePart2(final List numbers) { + final Set seen = new HashSet<>(numbers.size()); + for (int i = 0; i < numbers.size(); i++) { + final Integer n1 = numbers.get(i); seen.add(n1); - for (int j = i; j < this.numbers.size(); j++) { - final Integer n2 = this.numbers.get(j); + for (final Integer n2 : numbers.subList(i, numbers.size())) { final Integer n3 = 2020 - n1 - n2; if (seen.contains(n3)) { return (long) (n1 * n2 * n3); } } } - - return 0L; + throw unreachable(); } - public static void main(String[] args) throws Exception { - assert AoC2020_01.create(TEST).solvePart1() == 514579; - assert AoC2020_01.create(TEST).solvePart2() == 241861950; - - final List input = Aocd.getData(2020, 1); - lap("Part 1", () -> AoC2020_01.create(input).solvePart1()); - lap("Part 2", () -> AoC2020_01.create(input).solvePart2()); + @Samples({ + @Sample(method = "part1", input = TEST, expected = "514579"), + @Sample(method = "part2", input = TEST, expected = "241861950"), + }) + public static void main(final String[] args) throws Exception { + AoC2020_01.create().run(); } - private static final List TEST = splitLines( - "1721\r\n" + - "979\r\n" + - "366\r\n" + - "299\r\n" + - "675\r\n" + - "1456" - ); + private static final String TEST = """ + 1721 + 979 + 366 + 299 + 675 + 1456 + """; } diff --git a/src/main/java/AoC2020_02.java b/src/main/java/AoC2020_02.java index 59926e20..f4fa6ae6 100644 --- a/src/main/java/AoC2020_02.java +++ b/src/main/java/AoC2020_02.java @@ -1,96 +1,86 @@ import static com.github.pareronia.aoc.Utils.asCharacterStream; -import static java.util.Objects.requireNonNull; import java.util.List; import java.util.function.Predicate; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -public class AoC2020_02 extends AoCBase { +public class AoC2020_02 + extends SolutionBase, Long, Long> { - private final List inputs; - - private AoC2020_02(List input, boolean debug) { + private AoC2020_02(final boolean debug) { super(debug); - this.inputs = input; } - public static AoC2020_02 create(List input) { - return new AoC2020_02(input, false); + public static AoC2020_02 create() { + return new AoC2020_02(false); } - public static AoC2020_02 createDebug(List input) { - return new AoC2020_02(input, true); + public static AoC2020_02 createDebug() { + return new AoC2020_02(true); } @Override - public Long solvePart1() { - return countValid(PasswordAndPolicy::isValid1); + protected List parseInput(final List inputs) { + return inputs.stream().map(PasswordAndPolicy::create).toList(); + } + + @Override + public Long solvePart1(final List inputs) { + return countValid(inputs, PasswordAndPolicy::isValid1); } @Override - public Long solvePart2() { - return countValid(PasswordAndPolicy::isValid2); + public Long solvePart2(final List inputs) { + return countValid(inputs, PasswordAndPolicy::isValid2); } - private long countValid(final Predicate predicate) { - return this.inputs.stream() - .map(PasswordAndPolicy::create) - .filter(predicate) - .count(); + private long countValid( + final List inputs, + final Predicate predicate + ) { + return inputs.stream().filter(predicate).count(); } - public static void main(String[] args) throws Exception { - assert AoC2020_02.createDebug(TEST).solvePart1() == 2; - assert AoC2020_02.createDebug(TEST).solvePart2() == 1; - - final List input = Aocd.getData(2020, 2); - lap("Part 1", () -> AoC2020_02.create(input).solvePart1()); - lap("Part 2", () -> AoC2020_02.create(input).solvePart2()); + @Samples({ + @Sample(method = "part1", input = TEST, expected = "2"), + @Sample(method = "part2", input = TEST, expected = "1"), + }) + public static void main(final String[] args) throws Exception { + AoC2020_02.create().run(); } - private static final List TEST = splitLines( - "1-3 a: abcde\r\n" + - "1-3 b: cdefg\r\n" + - "2-9 c: ccccccccc" - ); + private static final String TEST = """ + 1-3 a: abcde + 1-3 b: cdefg + 2-9 c: ccccccccc + """; - @RequiredArgsConstructor - @ToString - private static final class PasswordAndPolicy { - private final Integer first; - private final Integer second; - private final String wanted; - private final String password; + record PasswordAndPolicy(int first, int second, char wanted, String password) { - public static PasswordAndPolicy create(String input) { - final String[] splits = requireNonNull(input).split(": "); + public static PasswordAndPolicy create(final String input) { + final String[] splits = input.split(": "); final String[] leftAndRight = splits[0].split(" "); final String[] firstAndSecond = leftAndRight[0].split("-"); - final Integer first = Integer.valueOf(firstAndSecond[0]); - final Integer second = Integer.valueOf(firstAndSecond[1]); - final String wanted = leftAndRight[1]; + final int first = Integer.parseInt(firstAndSecond[0]); + final int second = Integer.parseInt(firstAndSecond[1]); + final char wanted = leftAndRight[1].charAt(0); final String password = splits[1]; return new PasswordAndPolicy(first, second, wanted, password); } - private boolean equal(char c, String string) { - return String.valueOf(c).equals(string); - } - public boolean isValid1() { final long count = asCharacterStream(password) - .filter(c -> equal(c, wanted)) + .filter(c -> c == wanted) .count(); return first <= count && count <= second; } public boolean isValid2() { - return equal(password.charAt(first - 1), wanted) - ^ equal(password.charAt(second - 1), wanted); + return password.charAt(first - 1) == wanted + ^ password.charAt(second - 1) == wanted; } } } diff --git a/src/main/java/AoC2020_03.java b/src/main/java/AoC2020_03.java index 7becc397..00bd6dcc 100644 --- a/src/main/java/AoC2020_03.java +++ b/src/main/java/AoC2020_03.java @@ -10,9 +10,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - public class AoC2020_03 extends AoCBase { private final Slope slope; @@ -40,8 +37,8 @@ private Slope parse(final List inputs) { private List path(final Steps steps) { final List path = new ArrayList<>(); for (int row = 0, col = 0; - row < slope.getHeight(); - row += steps.getDown(), col = (col + steps.getRight()) % slope.getWidth()) { + row < slope.height(); + row += steps.down(), col = (col + steps.right()) % slope.width()) { path.add(Cell.at(row, col)); } return path; @@ -98,19 +95,9 @@ public static void main(final String[] args) throws Exception { ".#..#...#.#" ); - @RequiredArgsConstructor - @Getter - private static final class Slope { - private final Set trees; - private final Integer width; - private final Integer height; - } + record Slope(Set trees, int width, int height) { } - @RequiredArgsConstructor - @Getter - private static final class Steps { - private final Integer down; - private final Integer right; + record Steps(int down, int right) { public static Steps of(final Integer down, final Integer right) { return new Steps(down, right); diff --git a/src/main/java/AoC2020_04.java b/src/main/java/AoC2020_04.java index f169c991..b07f5aa5 100644 --- a/src/main/java/AoC2020_04.java +++ b/src/main/java/AoC2020_04.java @@ -6,139 +6,135 @@ import java.util.List; import java.util.Set; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Predicate; import com.github.pareronia.aoc.RangeInclusive; +import com.github.pareronia.aoc.StringOps; import com.github.pareronia.aoc.StringUtils; import com.github.pareronia.aoc.Utils; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.Builder; -import lombok.ToString; - -public class AoC2020_04 extends AoCBase { - - private final Set passports; +public class AoC2020_04 + extends SolutionBase, Integer, Integer> { - private AoC2020_04(final List input, final boolean debug) { + private AoC2020_04(final boolean debug) { super(debug); - this.passports = parse(input); } - public static AoC2020_04 create(final List input) { - return new AoC2020_04(input, false); + public static AoC2020_04 create() { + return new AoC2020_04(false); } - public static AoC2020_04 createDebug(final List input) { - return new AoC2020_04(input, true); + public static AoC2020_04 createDebug() { + return new AoC2020_04(true); } - private Set parse(final List inputs) { - final Function, Passport> buildPassport = block -> { - final Passport.PassportBuilder passportBuilder = Passport.builder(); - final Consumer applyField = field -> { - final String[] fieldSplits = field.split(":"); - try { - Passport.PassportBuilder.class - .getMethod(fieldSplits[0], String.class) - .invoke(passportBuilder, fieldSplits[1]); - } catch (IllegalAccessException | IllegalArgumentException - | InvocationTargetException - | NoSuchMethodException | SecurityException e) { - throw new RuntimeException(e); - } - }; - block.stream().flatMap(line -> Arrays.stream(line.split(" "))) - .forEach(applyField); - return passportBuilder.build(); - }; - return toBlocks(inputs).stream() - .map(buildPassport) + @Override + protected Set parseInput(final List inputs) { + return StringOps.toBlocks(inputs).stream() + .map(Passport::fromInput) .collect(toSet()); } @Override - public Long solvePart1() { - return countValid(Passport::isValid1); + public Integer solvePart1(final Set passports) { + return countValid(passports, Passport::isValid1); } @Override - public Long solvePart2() { - return countValid(Passport::isValid2); + public Integer solvePart2(final Set passports) { + return countValid(passports, Passport::isValid2); } - private long countValid(final Predicate predicate) { - return this.passports.stream().filter(predicate).count(); + private int countValid( + final Set passports, + final Predicate predicate + ) { + return (int) passports.stream().filter(predicate).count(); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "10"), + @Sample(method = "part2", input = TEST, expected = "6"), + }) public static void main(final String[] args) throws Exception { - assert AoC2020_04.createDebug(TEST).solvePart1() == 10; - assert AoC2020_04.createDebug(TEST).solvePart2() == 6; - - final Puzzle puzzle = Puzzle.create(2020, 4); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2020_04.create(input)::solvePart1), - () -> lap("Part 2", AoC2020_04.create(input)::solvePart2) - ); + AoC2020_04.create().run(); } - private static final List TEST = splitLines( - "ecl:gry pid:860033327 eyr:2020 hcl:#fffffd\r\n" + - "byr:1937 iyr:2017 cid:147 hgt:183cm\r\n" + - "\r\n" + - "iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884\r\n" + - "hcl:#cfa07d byr:1929\r\n" + - "\r\n" + - "hcl:#ae17e1 iyr:2013\r\n" + - "eyr:2024\r\n" + - "ecl:brn pid:760753108 byr:1931\r\n" + - "hgt:179cm\r\n" + - "\r\n" + - "hcl:#cfa07d eyr:2025 pid:166559648\r\n" + - "iyr:2011 ecl:brn hgt:59in\r\n" + - "\r\n" + - "eyr:1972 cid:100\r\n" + - "hcl:#18171d ecl:amb hgt:170 pid:186cm iyr:2018 byr:1926\r\n" + - "\r\n" + - "iyr:2019\r\n" + - "hcl:#602927 eyr:1967 hgt:170cm\r\n" + - "ecl:grn pid:012533040 byr:1946\r\n" + - "\r\n" + - "hcl:dab227 iyr:2012\r\n" + - "ecl:brn hgt:182cm pid:021572410 eyr:2020 byr:1992 cid:277\r\n" + - "\r\n" + - "hgt:59cm ecl:zzz\r\n" + - "eyr:2038 hcl:74454a iyr:2023\r\n" + - "pid:3556412378 byr:2007\r\n" + - "\r\n" + - "pid:087499704 hgt:74in ecl:grn iyr:2012 eyr:2030 byr:1980\r\n" + - "hcl:#623a2f\r\n" + - "\r\n" + - "eyr:2029 ecl:blu cid:129 byr:1989\r\n" + - "iyr:2014 pid:896056539 hcl:#a97842 hgt:165cm\r\n" + - "\r\n" + - "hcl:#888785\r\n" + - "hgt:164cm byr:2001 iyr:2015 cid:88\r\n" + - "pid:545766238 ecl:hzl\r\n" + - "eyr:2022\r\n" + - "\r\n" + - "iyr:2010 hgt:158cm hcl:#b6652a ecl:blu byr:1944 eyr:2021 pid:093154719" - ); + private static final String TEST = """ + ecl:gry pid:860033327 eyr:2020 hcl:#fffffd + byr:1937 iyr:2017 cid:147 hgt:183cm + + iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884 + hcl:#cfa07d byr:1929 + + hcl:#ae17e1 iyr:2013 + eyr:2024 + ecl:brn pid:760753108 byr:1931 + hgt:179cm + + hcl:#cfa07d eyr:2025 pid:166559648 + iyr:2011 ecl:brn hgt:59in + + eyr:1972 cid:100 + hcl:#18171d ecl:amb hgt:170 pid:186cm iyr:2018 byr:1926 + + iyr:2019 + hcl:#602927 eyr:1967 hgt:170cm + ecl:grn pid:012533040 byr:1946 + + hcl:dab227 iyr:2012 + ecl:brn hgt:182cm pid:021572410 eyr:2020 byr:1992 cid:277 + + hgt:59cm ecl:zzz + eyr:2038 hcl:74454a iyr:2023 + pid:3556412378 byr:2007 + + pid:087499704 hgt:74in ecl:grn iyr:2012 eyr:2030 byr:1980 + hcl:#623a2f + + eyr:2029 ecl:blu cid:129 byr:1989 + iyr:2014 pid:896056539 hcl:#a97842 hgt:165cm + + hcl:#888785 + hgt:164cm byr:2001 iyr:2015 cid:88 + pid:545766238 ecl:hzl + eyr:2022 + + iyr:2010 hgt:158cm hcl:#b6652a ecl:blu byr:1944 eyr:2021 pid:093154719 + """; - @Builder - @ToString - private static final class Passport { - private final String byr; // (Birth Year) - private final String iyr; // (Issue Year) - private final String eyr; // (Expiration Year) - private final String hgt; // (Height) - private final String hcl; // (Hair Color) - private final String ecl; // (Eye Color) - private final String pid; // (Passport ID) - private final String cid; // (Country ID) - + record Passport( + String byr, // (Birth Year) + String iyr, // (Issue Year) + String eyr, // (Expiration Year) + String hgt, // (Height) + String hcl, // (Hair Color) + String ecl, // (Eye Color) + String pid // (Passport ID) + ) { + + public static Passport fromInput(final List block) { + final Passport.PassportBuilder passportBuilder = Passport.builder(); + final Consumer applyField = field -> { + final String[] fieldSplits = field.split(":"); + try { + Passport.PassportBuilder.class + .getMethod(fieldSplits[0], String.class) + .invoke(passportBuilder, fieldSplits[1]); + } catch (IllegalAccessException | IllegalArgumentException + | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + throw new RuntimeException(e); + } + }; + block.stream().flatMap(line -> Arrays.stream(line.split(" "))) + .forEach(applyField); + return passportBuilder.build(); + } + public boolean isValid1() { return this.byr != null && this.iyr != null @@ -162,10 +158,12 @@ private boolean eyrValid() { } private boolean hgtValid() { - final Integer hgt = Integer.valueOf(this.hgt.substring(0, this.hgt.length() - 2)); - if (this.hgt.endsWith("in")) { + final int len = this.hgt.length(); + if (this.hgt.endsWith("in")) { + final Integer hgt = Integer.valueOf(this.hgt.substring(0, len - 2)); return RangeInclusive.between(59, 76).contains(hgt); } else if (this.hgt.endsWith("cm")) { + final Integer hgt = Integer.valueOf(this.hgt.substring(0, len - 2)); return RangeInclusive.between(150, 193).contains(hgt); } else { return false; @@ -195,5 +193,62 @@ public boolean isValid2() { && byrValid() && iyrValid() && eyrValid() && hgtValid() && hclValid() && eclValid() && pidValid(); } + + public static PassportBuilder builder() { + return new PassportBuilder(); + } + + public static final class PassportBuilder { + private String byr; + private String iyr; + private String eyr; + private String hgt; + private String hcl; + private String ecl; + private String pid; + + public PassportBuilder byr(final String byr) { + this.byr = byr; + return this; + } + + public PassportBuilder iyr(final String iyr) { + this.iyr = iyr; + return this; + } + + public PassportBuilder eyr(final String eyr) { + this.eyr = eyr; + return this; + } + + public PassportBuilder hgt(final String hgt) { + this.hgt = hgt; + return this; + } + + public PassportBuilder hcl(final String hcl) { + this.hcl = hcl; + return this; + } + + public PassportBuilder ecl(final String ecl) { + this.ecl = ecl; + return this; + } + + public PassportBuilder pid(final String pid) { + this.pid = pid; + return this; + } + + public PassportBuilder cid(final String cid) { + return this; + } + + public Passport build() { + return new Passport(byr, iyr, eyr, hgt, hcl, ecl, pid); + } + } } } \ No newline at end of file diff --git a/src/main/java/AoC2020_05.java b/src/main/java/AoC2020_05.java index 291543d4..bbd0a0fc 100644 --- a/src/main/java/AoC2020_05.java +++ b/src/main/java/AoC2020_05.java @@ -1,30 +1,30 @@ +import static com.github.pareronia.aoc.AssertUtils.unreachable; import static java.util.stream.Collectors.toList; import java.util.List; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2020_05 extends AoCBase { +public class AoC2020_05 extends SolutionBase, Integer, Integer> { - private final List inputs; - - private AoC2020_05(final List input, final boolean debug) { + private AoC2020_05(final boolean debug) { super(debug); - this.inputs = input; } - public static AoC2020_05 create(final List input) { - return new AoC2020_05(input, false); + public static AoC2020_05 create() { + return new AoC2020_05(false); } - public static AoC2020_05 createDebug(final List input) { - return new AoC2020_05(input, true); + public static AoC2020_05 createDebug() { + return new AoC2020_05(true); } private List translated(final List inputs) { return inputs.stream() - .map(s -> s.replaceAll("F", "0").replaceAll("B", "1") - .replaceAll("L", "0").replaceAll("R", "1")) + .map(s -> s.replace('F', '0').replace('B', '1') + .replace('L', '0').replace('R', '1')) .sorted() .collect(toList()); } @@ -34,46 +34,47 @@ private int asInt(final String s) { } @Override - public Integer solvePart1() { - final List list = translated(this.inputs); + protected List parseInput(final List inputs) { + return inputs; + } + + @Override + public Integer solvePart1(final List inputs) { + final List list = translated(inputs); return asInt(list.get(list.size() - 1)); } @Override - public Integer solvePart2() { - final int last = this.inputs.get(0).length() - 1; - final List list = translated(this.inputs); + public Integer solvePart2(final List inputs) { + final int last = inputs.get(0).length() - 1; + final List list = translated(inputs); for (int i = 1; i < list.size() - 1; i++) { if (list.get(i).charAt(last) == list.get(i - 1).charAt(last)) { return asInt(list.get(i)) - 1; } } - throw new RuntimeException("Unsolvable"); + throw unreachable(); } + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "820"), + @Sample(method = "part2", input = TEST2, expected = "3"), + }) public static void main(final String[] args) throws Exception { - assert createDebug(TEST1).solvePart1() == 820; - assert createDebug(TEST2).solvePart2() == 3; - - final Puzzle puzzle = puzzle(AoC2020_05.class); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", create(input)::solvePart1), - () -> lap("Part 2", create(input)::solvePart2) - ); + AoC2020_05.create().run(); } - private static final List TEST1 = splitLines( - "FBFBBFFRLR\r\n" + - "BFFFBBFRRR\r\n" + - "FFFBBBFRRR\r\n" + - "BBFFBBFRLL" - ); - private static final List TEST2 = splitLines( - "FFFFFFFLLL\r\n" + - "FFFFFFFLLR\r\n" + - "FFFFFFFLRL\r\n" + - "FFFFFFFRLL\r\n" + - "FFFFFFFRLR" - ); + private static final String TEST1 = """ + FBFBBFFRLR + BFFFBBFRRR + FFFBBBFRRR + BBFFBBFRLL + """; + private static final String TEST2 = """ + FFFFFFFLLL + FFFFFFFLLR + FFFFFFFLRL + FFFFFFFRLL + FFFFFFFRLR + """; } \ No newline at end of file diff --git a/src/main/java/AoC2020_07.java b/src/main/java/AoC2020_07.java index d7e18ada..1844a6c4 100644 --- a/src/main/java/AoC2020_07.java +++ b/src/main/java/AoC2020_07.java @@ -1,138 +1,127 @@ -import static java.lang.Boolean.FALSE; -import static java.util.Arrays.asList; import static java.util.stream.Collectors.toSet; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Value; - -public class AoC2020_07 extends AoCBase { +public class AoC2020_07 extends SolutionBase { private static final String SHINY_GOLD = "shiny gold"; - private final Graph graph; - - private AoC2020_07(List input, boolean debug) { + private AoC2020_07(final boolean debug) { super(debug); - this.graph = parse(input); } - public static AoC2020_07 create(List input) { - return new AoC2020_07(input, false); + public static AoC2020_07 create() { + return new AoC2020_07(false); } - public static AoC2020_07 createDebug(List input) { - return new AoC2020_07(input, true); + public static AoC2020_07 createDebug() { + return new AoC2020_07(true); } - private Graph parse(List inputs) { - return new Graph( - inputs.stream() - .flatMap(line -> { - final String[] sp = line.split(" contain "); - final String source = sp[0].split(" bags")[0]; - return asList(sp[1].split(", ")).stream() - .filter(r -> !r.equals("no other bags.")) - .map(r -> { - final String[] contained = r.split(" "); - return new Edge(source, - contained[1] + " " + contained[2], - Integer.valueOf(contained[0])); - }); - }) - .collect(toSet()) - ); + @Override + protected Graph parseInput(final List inputs) { + return Graph.fromInput(inputs); } @Override - public Long solvePart1() { - return this.graph.getEdges().stream() - .map(Edge::getSrc) - .filter(src -> !src.equals(SHINY_GOLD)) + public Long solvePart1(final Graph graph) { + return graph.edges().stream() + .map(Edge::src) .distinct() - .filter(src -> this.graph.containsPath(src, SHINY_GOLD)) + .filter(src -> !src.equals(SHINY_GOLD)) + .filter(src -> graph.containsPath(src, SHINY_GOLD)) .count(); } @Override - public Integer solvePart2() { - return this.graph.countWeights(SHINY_GOLD); + public Integer solvePart2(final Graph graph) { + return graph.countWeights(SHINY_GOLD); } - public static void main(String[] args) throws Exception { - assert AoC2020_07.createDebug(TEST1).solvePart1() == 4; - assert AoC2020_07.createDebug(TEST1).solvePart2() == 32; - assert AoC2020_07.createDebug(TEST2).solvePart2() == 126; - - final List input = Aocd.getData(2020, 7); - lap("Part 1", () -> AoC2020_07.create(input).solvePart1()); - lap("Part 2", () -> AoC2020_07.create(input).solvePart2()); + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "4"), + @Sample(method = "part2", input = TEST1, expected = "32"), + @Sample(method = "part2", input = TEST2, expected = "126"), + }) + public static void main(final String[] args) throws Exception { + AoC2020_07.create().run(); } - private static final List TEST1 = splitLines( - "light red bags contain 1 bright white bag, 2 muted yellow bags.\r\n" + - "dark orange bags contain 3 bright white bags, 4 muted yellow bags.\r\n" + - "bright white bags contain 1 shiny gold bag.\r\n" + - "muted yellow bags contain 2 shiny gold bags, 9 faded blue bags.\r\n" + - "shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags.\r\n" + - "dark olive bags contain 3 faded blue bags, 4 dotted black bags.\r\n" + - "vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.\r\n" + - "faded blue bags contain no other bags.\r\n" + - "dotted black bags contain no other bags." - ); - private static final List TEST2 = splitLines( - "shiny gold bags contain 2 dark red bags.\r\n" + - "dark red bags contain 2 dark orange bags.\r\n" + - "dark orange bags contain 2 dark yellow bags.\r\n" + - "dark yellow bags contain 2 dark green bags.\r\n" + - "dark green bags contain 2 dark blue bags.\r\n" + - "dark blue bags contain 2 dark violet bags.\r\n" + - "dark violet bags contain no other bags." - ); + private static final String TEST1 = """ + light red bags contain 1 bright white bag, 2 muted yellow bags. + dark orange bags contain 3 bright white bags, 4 muted yellow bags. + bright white bags contain 1 shiny gold bag. + muted yellow bags contain 2 shiny gold bags, 9 faded blue bags. + shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags. + dark olive bags contain 3 faded blue bags, 4 dotted black bags. + vibrant plum bags contain 5 faded blue bags, 6 dotted black bags. + faded blue bags contain no other bags. + dotted black bags contain no other bags. + """; + private static final String TEST2 = """ + shiny gold bags contain 2 dark red bags. + dark red bags contain 2 dark orange bags. + dark orange bags contain 2 dark yellow bags. + dark yellow bags contain 2 dark green bags. + dark green bags contain 2 dark blue bags. + dark blue bags contain 2 dark violet bags. + dark violet bags contain no other bags. + """; - @Value - private static final class Edge { - private final String src; - private final String dst; - private final Integer weight; - } + record Edge(String src, String dst, int weight) { } - @RequiredArgsConstructor - @Getter - private static final class Graph { - private final Set edges; - private final Map paths = new HashMap<>(); - private final Map weights = new HashMap<>(); + record Graph(Set edges, Map paths, Map weights) { + + private Graph(final Set edges) { + this(edges, new HashMap<>(), new HashMap<>()); + } + + public static Graph fromInput(final List inputs) { + final Set edges = inputs.stream() + .flatMap(line -> { + final String[] sp = line.split(" contain "); + final String source = sp[0].split(" bags")[0]; + return Arrays.stream(sp[1].split(", ")) + .filter(r -> !r.equals("no other bags.")) + .map(r -> { + final String[] contained = r.split(" "); + return new Edge( + source, + contained[1] + " " + contained[2], + Integer.parseInt(contained[0])); + }); + }) + .collect(toSet()); + return new Graph(edges); + } - public boolean containsPath(String src, String dst) { + public boolean containsPath(final String src, final String dst) { if (!paths.containsKey(src)) { paths.put( src, this.edges.stream() - .filter(e -> e.getSrc().equals(src)) - .map(e -> e.getDst().equals(dst) || containsPath(e.getDst(), dst)) - .reduce(FALSE, (a, b) -> a || b) - ); + .filter(e -> e.src().equals(src)) + .anyMatch(e -> e.dst().equals(dst) || containsPath(e.dst(), dst))); } return paths.get(src); } - public Integer countWeights(String src) { + public int countWeights(final String src) { if (!weights.containsKey(src)) { weights.put( src, this.edges.stream() - .filter(e -> e.getSrc().equals(src)) - .map(e -> e.getWeight() * (1 + countWeights(e.getDst()))) - .reduce(0, (a, b) -> a + b) - ); + .filter(e -> e.src().equals(src)) + .mapToInt(e -> e.weight() * (1 + countWeights(e.dst()))) + .sum()); } return weights.get(src); } diff --git a/src/main/java/AoC2020_08.java b/src/main/java/AoC2020_08.java index 7af6266e..3a85558a 100644 --- a/src/main/java/AoC2020_08.java +++ b/src/main/java/AoC2020_08.java @@ -11,8 +11,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Value; - public class AoC2020_08 extends AoCBase { private final List instructions; @@ -129,9 +127,5 @@ public static void main(final String[] args) throws Exception { "acc +6" ); - @Value - private static final class Instruction_ { - private final String operator; - private final Integer operand; - } + record Instruction_(String operator, Integer operand) {} } \ No newline at end of file diff --git a/src/main/java/AoC2020_09.java b/src/main/java/AoC2020_09.java index a1ae9a97..2e901d63 100644 --- a/src/main/java/AoC2020_09.java +++ b/src/main/java/AoC2020_09.java @@ -1,128 +1,96 @@ -import static com.github.pareronia.aoc.IntegerSequence.Range.between; -import static java.util.stream.Collectors.summingLong; -import static java.util.stream.Collectors.toList; +import static com.github.pareronia.aoc.IntegerSequence.Range.range; +import static com.github.pareronia.aoc.StringOps.splitLines; -import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.LongSummaryStatistics; -import java.util.Set; +import java.util.stream.IntStream; +import java.util.stream.Stream; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2020_09 extends AoCBase { +public class AoC2020_09 extends SolutionBase, Long, Long> { - private final List numbers; - - private AoC2020_09(final List input, final boolean debug) { + private AoC2020_09(final boolean debug) { super(debug); - this.numbers = input.stream().map(Long::valueOf).collect(toList()); } - public static AoC2020_09 create(final List input) { - return new AoC2020_09(input, false); + public static AoC2020_09 create() { + return new AoC2020_09(false); } - public static AoC2020_09 createDebug(final List input) { - return new AoC2020_09(input, true); - } - - private boolean containsTwoSummands(final List numbers, final Long sum) { - final Set seen = new HashSet<>(); - for (final Long n1 : numbers) { - seen.add(n1); - final Long n2 = sum - n1; - if (seen.contains(n2)) { - return true; - } - } - return false; - } - - private Long findInvalidNumber(final Integer windowSize) { - for (final int i : between(windowSize, this.numbers.size())) { - final Long number = this.numbers.get(i); - final List searchWindow = this.numbers.subList(i - windowSize, i); - if (!containsTwoSummands(searchWindow, number)) { - return number; - } - } - throw new RuntimeException("Unsolvable"); + public static AoC2020_09 createDebug() { + return new AoC2020_09(true); } @Override - public Long solvePart1() { - return findInvalidNumber(25); + protected List parseInput(final List inputs) { + return inputs.stream().map(Long::parseLong).toList(); + } + + private Long findInvalidNumber(final List numbers, final int windowSize) { + return IntStream.range(windowSize, numbers.size()) + .filter(i -> numbers.subList(i - windowSize, i).stream() + .noneMatch(n -> numbers.subList(i - windowSize, i) + .contains(numbers.get(i) - n))) + .mapToObj(numbers::get) + .findFirst().orElseThrow(); } - private List> collectAllSublistsBeforeTargetWithMinimumSize2(final Integer targetPos) { - final List window = this.numbers.subList(0, targetPos); - final ArrayList> sublists = new ArrayList<>(); - for (int i = 0; i <= window.size(); i++) { - for (int j = i + 1; j <= window.size(); j++) { - if (j - i < 2) { - continue; - } - sublists.add(window.subList(i, j)); - } - } - return sublists; + @Override + public Long solvePart1(final List numbers) { + return findInvalidNumber(numbers, 25); } - private Long findWeakness(final Integer windowSize) { - final long target = findInvalidNumber(windowSize); - final List> sublists - = collectAllSublistsBeforeTargetWithMinimumSize2(this.numbers.indexOf(target)); - final List> sublistsWithSumEqualToTarget = sublists.stream() - .filter(sl -> sl.stream().collect(summingLong(Long::longValue)) == target) - .collect(toList()); - if (sublistsWithSumEqualToTarget.size() != 1) { - throw new RuntimeException("Unsolvable"); - } - final List subListWithSumEqualToTarget = sublistsWithSumEqualToTarget.get(0); - final LongSummaryStatistics stats = subListWithSumEqualToTarget.stream() - .mapToLong(Long::valueOf) - .summaryStatistics(); - return stats.getMin() + stats.getMax(); + private long findWeakness(final List numbers, final int windowSize) { + final long target = findInvalidNumber(numbers, windowSize); + final int targetPos = numbers.indexOf(target); + final Stream> sublists = range(targetPos + 1).intStream().boxed() + .flatMap(i -> IntStream.range(i + 1, targetPos + 1) + .filter(j -> j - i >= 2) + .mapToObj(j -> numbers.subList(i, j))); + return sublists + .filter(s -> s.stream().mapToLong(Long::longValue).sum() == target) + .mapToLong(s -> + s.stream().mapToLong(Long::longValue).min().getAsLong() + + s.stream().mapToLong(Long::longValue).max().getAsLong()) + .findFirst().getAsLong(); } @Override - public Long solvePart2() { - return findWeakness(25); + public Long solvePart2(final List numbers) { + return findWeakness(numbers, 25); } - public static void main(final String[] args) throws Exception { - assert AoC2020_09.createDebug(TEST).findInvalidNumber(5) == 127; - assert AoC2020_09.createDebug(TEST).findWeakness(5) == 62; + @Override + public void samples() { + final AoC2020_09 test = AoC2020_09.createDebug(); + assert test.findInvalidNumber(test.parseInput(splitLines(TEST)), 5) == 127; + assert test.findWeakness(test.parseInput(splitLines(TEST)), 5) == 62; + } - final Puzzle puzzle = Puzzle.create(2020, 9); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2020_09.create(input)::solvePart1), - () -> lap("Part 2", AoC2020_09.create(input)::solvePart2) - ); + public static void main(final String[] args) throws Exception { + AoC2020_09.create().run(); } - private static final List TEST = splitLines( - "35\r\n" + - "20\r\n" + - "15\r\n" + - "25\r\n" + - "47\r\n" + - "40\r\n" + - "62\r\n" + - "55\r\n" + - "65\r\n" + - "95\r\n" + - "102\r\n" + - "117\r\n" + - "150\r\n" + - "182\r\n" + - "127\r\n" + - "219\r\n" + - "299\r\n" + - "277\r\n" + - "309\r\n" + - "576" - ); + private static final String TEST = """ + 35 + 20 + 15 + 25 + 47 + 40 + 62 + 55 + 65 + 95 + 102 + 117 + 150 + 182 + 127 + 219 + 299 + 277 + 309 + 576 + """; } \ No newline at end of file diff --git a/src/main/java/AoC2020_10.java b/src/main/java/AoC2020_10.java index 9046832d..e15f389a 100644 --- a/src/main/java/AoC2020_10.java +++ b/src/main/java/AoC2020_10.java @@ -1,117 +1,120 @@ -import static java.util.Comparator.comparingInt; -import static java.util.stream.Collectors.counting; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; +import static com.github.pareronia.aoc.IntegerSequence.Range.range; +import static com.github.pareronia.aoc.IterTools.enumerate; +import static com.github.pareronia.aoc.Utils.last; +import static java.util.stream.Collectors.toCollection; -import java.util.Collections; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Stream; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.Counter; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2020_10 extends AoCBase { +public class AoC2020_10 extends SolutionBase, Long, Long> { - private final List numbers; - - private AoC2020_10(List input, boolean debug) { + private AoC2020_10(final boolean debug) { super(debug); - this.numbers = input.stream().map(Integer::valueOf).collect(toList()); - this.numbers.add(0); - this.numbers.add(this.numbers.stream().max(comparingInt(n -> n)).orElseThrow() + 3); - Collections.sort(this.numbers); } - public static AoC2020_10 create(List input) { - return new AoC2020_10(input, false); + public static AoC2020_10 create() { + return new AoC2020_10(false); } - public static AoC2020_10 createDebug(List input) { - return new AoC2020_10(input, true); + public static AoC2020_10 createDebug() { + return new AoC2020_10(true); } @Override - public Long solvePart1() { - final HashMap jumps = Stream.iterate(1, i -> i + 1) - .takeWhile(i -> i < this.numbers.size()) - .map(i -> this.numbers.get(i) - this.numbers.get(i - 1)) - .collect(groupingBy(jump -> jump, HashMap::new, counting())); + protected List parseInput(final List inputs) { + final List numbers = new ArrayList<>(List.of(0)); + inputs.stream() + .map(Integer::valueOf) + .sorted() + .collect(toCollection(() -> numbers)); + numbers.add(last(numbers) + 3); + return numbers; + } + + @Override + public Long solvePart1(final List numbers) { + final Counter jumps = new Counter<>( + range(numbers.size()).intStream().skip(1) + .mapToObj(i -> numbers.get(i) - numbers.get(i - 1))); return jumps.get(1) * jumps.get(3); } // 0: 1, 1: 1, 4: 1, 5: 1, 6: 2, 7: 4, 10: 4, 11: 4, 12: 8, 15: 8, 16: 8, 19: 8 @Override - public Long solvePart2() { - log(this.numbers); + public Long solvePart2(final List numbers) { final Map map = new HashMap<>(); map.put(0, 1L); - this.numbers.stream().skip(1).forEach(i -> { - Stream.iterate(this.numbers.indexOf(i) - 1, j -> j - 1) - .takeWhile(j -> j >= 0) - .map(this.numbers::get) - .filter(j -> i - j <= 3) - .forEach(j -> map.merge(i, map.get(j), Long::sum)); + enumerate(numbers.stream().skip(1)).forEach(e -> { + range(e.index(), -1, -1).intStream() + .map(numbers::get) + .filter(j -> e.value() - j <= 3) + .forEach(j -> map.merge(e.value(), map.get(j), Long::sum)); log(map); }); - return map.get(this.numbers.get(this.numbers.size() - 1)); + return map.get(last(numbers)); } - public static void main(String[] args) throws Exception { - assert AoC2020_10.createDebug(TEST1).solvePart1() == 35; - assert AoC2020_10.createDebug(TEST2).solvePart1() == 220; - assert AoC2020_10.createDebug(TEST1).solvePart2() == 8; - assert AoC2020_10.createDebug(TEST2).solvePart2() == 19208; - - final List input = Aocd.getData(2020, 10); - lap("Part 1", () -> AoC2020_10.create(input).solvePart1()); - lap("Part 2", () -> AoC2020_10.create(input).solvePart2()); + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "35"), + @Sample(method = "part1", input = TEST2, expected = "220"), + @Sample(method = "part2", input = TEST1, expected = "8"), + @Sample(method = "part2", input = TEST2, expected = "19208"), + }) + public static void main(final String[] args) throws Exception { + AoC2020_10.create().run(); } - private static final List TEST1 = splitLines( - "16\r\n" + - "10\r\n" + - "15\r\n" + - "5\r\n" + - "1\r\n" + - "11\r\n" + - "7\r\n" + - "19\r\n" + - "6\r\n" + - "12\r\n" + - "4" - ); - private static final List TEST2 = splitLines( - "28\r\n" + - "33\r\n" + - "18\r\n" + - "42\r\n" + - "31\r\n" + - "14\r\n" + - "46\r\n" + - "20\r\n" + - "48\r\n" + - "47\r\n" + - "24\r\n" + - "23\r\n" + - "49\r\n" + - "45\r\n" + - "19\r\n" + - "38\r\n" + - "39\r\n" + - "11\r\n" + - "1\r\n" + - "32\r\n" + - "25\r\n" + - "35\r\n" + - "8\r\n" + - "17\r\n" + - "7\r\n" + - "9\r\n" + - "4\r\n" + - "2\r\n" + - "34\r\n" + - "10\r\n" + - "3" - ); + private static final String TEST1 = """ + 16 + 10 + 15 + 5 + 1 + 11 + 7 + 19 + 6 + 12 + 4 + """; + private static final String TEST2 = """ + 28 + 33 + 18 + 42 + 31 + 14 + 46 + 20 + 48 + 47 + 24 + 23 + 49 + 45 + 19 + 38 + 39 + 11 + 1 + 32 + 25 + 35 + 8 + 17 + 7 + 9 + 4 + 2 + 34 + 10 + 3 + """; } \ No newline at end of file diff --git a/src/main/java/AoC2020_11.java b/src/main/java/AoC2020_11.java index a04329a0..0b8b1e98 100644 --- a/src/main/java/AoC2020_11.java +++ b/src/main/java/AoC2020_11.java @@ -13,8 +13,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public class AoC2020_11 extends AoCBase { private static final char FLOOR = '.'; @@ -141,9 +139,5 @@ public static void main(final String[] args) throws Exception { "L.LLLLL.LL" ); - @RequiredArgsConstructor - private static final class CycleResult { - private final CharGrid grid; - private final boolean changed; - } + record CycleResult(CharGrid grid, boolean changed) {} } \ No newline at end of file diff --git a/src/main/java/AoC2020_12.java b/src/main/java/AoC2020_12.java index 13d7f447..56ae1564 100644 --- a/src/main/java/AoC2020_12.java +++ b/src/main/java/AoC2020_12.java @@ -11,8 +11,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Value; - public class AoC2020_12 extends AoCBase { private final List instructions; @@ -84,10 +82,7 @@ public static Action of(final String code) { } } - @Value - private static final class NavigationInstruction { - private final Action action; - private final int value; + record NavigationInstruction(Action action, int value) { public static NavigationInstruction of(final String action, final String value) { return new NavigationInstruction( @@ -122,8 +117,8 @@ public int getDistanceTraveled() { } private void executeInstruction(final NavigationInstruction instuction) { - final Action action = instuction.getAction(); - final int value = instuction.getValue(); + final Action action = instuction.action(); + final int value = instuction.value(); if (action == Action.RIGHT) { this.nav.turn(Turn.fromDegrees(value)); } else if (action == Action.LEFT) { diff --git a/src/main/java/AoC2020_13.java b/src/main/java/AoC2020_13.java index fa1d6f13..5139a8e0 100644 --- a/src/main/java/AoC2020_13.java +++ b/src/main/java/AoC2020_13.java @@ -1,115 +1,103 @@ -import java.util.ArrayList; +import static com.github.pareronia.aoc.IterTools.enumerate; + +import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; + +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public class AoC2020_13 extends SolutionBase { + + private AoC2020_13(final boolean debug) { + super(debug); + } -import com.github.pareronia.aocd.Aocd; + public static AoC2020_13 create() { + return new AoC2020_13(false); + } -import lombok.Value; + public static AoC2020_13 createDebug() { + return new AoC2020_13(true); + } -public class AoC2020_13 extends AoCBase { - - private final Integer target; - private final List buses; - - private AoC2020_13(List input, boolean debug) { - super(debug); - assert input.size() == 2; - this.target = Integer.valueOf(input.get(0)); - this.buses = new ArrayList<>(); - final String[] splits = input.get(1).split(","); - for (int i = 0; i < splits.length; i++) { - final String s = splits[i]; - if (!s.equals("x")) { - buses.add(new Bus(Integer.valueOf(s), i)); - } - } - } - - public static AoC2020_13 create(List input) { - return new AoC2020_13(input, false); - } + @Override + protected Notes parseInput(final List inputs) { + return Notes.fromInput(inputs); + } - public static AoC2020_13 createDebug(List input) { - return new AoC2020_13(input, true); - } - - @Override - public Integer solvePart1() { - int cnt = 0; - while (true) { - final int t = this.target + cnt; - for (final Bus b : this.buses) { - if (t % b.getPeriod() == 0) { - return b.getPeriod() * cnt; - } + @Override + public Integer solvePart1(final Notes notes) { + return Stream.iterate(0, cnt -> cnt + 1) + .flatMap(cnt -> notes.buses.stream().map(b -> new int[] { cnt, b.period })) + .filter(a -> (notes.target + a[0]) % a[1] == 0) + .map(a -> a[0] * a[1]) + .findFirst().orElseThrow(); + } + + @Override + public Long solvePart2(final Notes notes) { + long r = 0; + long lcm = notes.buses.get(0).period(); + for (int i = 1; i < notes.buses.size(); i++) { + final Bus bus = notes.buses.get(i); + while ((r + bus.offset()) % bus.period() != 0) { + r += lcm; } - cnt++; - } - } - - @Override - public Long solvePart2() { - long r = 0; - long lcm = 1; - for (int i = 0; i < this.buses.size() - 1; i++) { - final Bus cur = this.buses.get(i); - final Bus nxt = this.buses.get(i + 1); - lcm = lcm * cur.getPeriod(); - while (true) { - r += lcm; - if ((r + (long) nxt.getOffset()) % nxt.getPeriod() == 0) { - break; - } - } - } - return r; - } + lcm *= bus.period(); + } + return r; + } + + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "295"), + @Sample(method = "part2", input = TEST2, expected = "3417"), + @Sample(method = "part2", input = TEST3, expected = "754018"), + @Sample(method = "part2", input = TEST4, expected = "779210"), + @Sample(method = "part2", input = TEST5, expected = "1261476"), + @Sample(method = "part2", input = TEST6, expected = "1202161486"), + }) + public static void main(final String[] args) throws Exception { + AoC2020_13.create().run(); + } + + private static final String TEST1 = """ + 939 + 7,13,x,x,59,x,31,19 + """; + private static final String TEST2 = """ + 999 + 17,x,13,19 + """; + private static final String TEST3 = """ + 999 + 67,7,59,61 + """; + private static final String TEST4 = """ + 999 + 67,x,7,59,61 + """; + private static final String TEST5 = """ + 999 + 67,7,x,59,61 + """; + private static final String TEST6 = """ + 999 + 1789,37,47,1889 + """; + + record Bus(int period, int offset) {} - public static void main(String[] args) throws Exception { - assert AoC2020_13.createDebug(TEST1).solvePart1() == 295; - assert AoC2020_13.createDebug(TEST2).solvePart2() == 108; - assert AoC2020_13.createDebug(TEST3).solvePart2() == 3417; - assert AoC2020_13.createDebug(TEST4).solvePart2() == 754018; - assert AoC2020_13.createDebug(TEST5).solvePart2() == 779210; - assert AoC2020_13.createDebug(TEST6).solvePart2() == 1261476; - assert AoC2020_13.createDebug(TEST7).solvePart2() == 1202161486; + record Notes(int target, List buses) { - final List input = Aocd.getData(2020, 13); - lap("Part 1", () -> AoC2020_13.create(input).solvePart1()); - lap("Part 2", () -> AoC2020_13.create(input).solvePart2()); - } - - private static final List TEST1 = splitLines( - "939\r\n" + - "7,13,x,x,59,x,31,19" - ); - private static final List TEST2 = splitLines( - "999\r\n" + - "3,x,5,x,7" - ); - private static final List TEST3 = splitLines( - "999\r\n" + - "17,x,13,19" - ); - private static final List TEST4 = splitLines( - "999\r\n" + - "67,7,59,61" - ); - private static final List TEST5 = splitLines( - "999\r\n" + - "67,x,7,59,61" - ); - private static final List TEST6 = splitLines( - "999\r\n" + - "67,7,x,59,61" - ); - private static final List TEST7 = splitLines( - "999\r\n" + - "1789,37,47,1889" - ); - - @Value - private static final class Bus { - private final Integer period; - private final Integer offset; - } + public static Notes fromInput(final List inputs) { + final int target = Integer.parseInt(inputs.get(0)); + final List buses = enumerate(Arrays.stream(inputs.get(1).split(","))) + .filter(e -> !"x".equals(e.value())) + .map(e -> new Bus(Integer.parseInt(e.value()), e.index())) + .toList(); + return new Notes(target, buses); + } + } } \ No newline at end of file diff --git a/src/main/java/AoC2020_16.java b/src/main/java/AoC2020_16.java index 10acc969..1c6549ce 100644 --- a/src/main/java/AoC2020_16.java +++ b/src/main/java/AoC2020_16.java @@ -13,82 +13,56 @@ import java.util.stream.Stream; import com.github.pareronia.aoc.RangeInclusive; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2020_16 extends AoCBase { +public class AoC2020_16 + extends SolutionBase { - private final Set rules; - private final Ticket myTicket; - private final List tickets; - - private AoC2020_16(final List input, final boolean debug) { + private AoC2020_16(final boolean debug) { super(debug); - final List> blocks = toBlocks(input); - this.rules = blocks.get(0).stream() - .map(this::parseRule) - .collect(toSet()); - this.myTicket = parseTicket(blocks.get(1).get(1)); - this.tickets = blocks.get(2).stream().skip(1) - .map(this::parseTicket) - .collect(toList()); } - private final Rule parseRule(final String s) { - final var splits1 = s.split(": "); - final var builder = Rule.builder(); - builder.field(splits1[0]); - for (final var split : splits1[1].split(" or ")) { - final var splits3 = split.split("-"); - final int start = Integer.parseInt(splits3[0]); - final int end = Integer.parseInt(splits3[1]); - builder.validRange(RangeInclusive.between(start, end)); - } - return builder.build(); - } - - private final Ticket parseTicket(final String s) { - return new Ticket(Arrays.stream(s.split(",")) - .map(Integer::parseInt) - .collect(toList())); - } - - public static AoC2020_16 create(final List input) { - return new AoC2020_16(input, false); + public static AoC2020_16 create() { + return new AoC2020_16(false); } - public static AoC2020_16 createDebug(final List input) { - return new AoC2020_16(input, true); + public static AoC2020_16 createDebug() { + return new AoC2020_16(true); } @Override - public Integer solvePart1() { - return this.tickets.stream() - .flatMap(t -> t.invalidValues(rules).stream()) + protected AoC2020_16.Notes parseInput(final List inputs) { + return Notes.fromInput(inputs); + } + + @Override + public Integer solvePart1(final Notes notes) { + return notes.tickets.stream() + .flatMap(t -> t.invalidValues(notes.rules).stream()) .mapToInt(Integer::valueOf) .sum(); } @Override - public Long solvePart2() { - return Matches.create(this.tickets, this.rules).stream() + public Long solvePart2(final Notes notes) { + return Matches.create(notes.tickets, notes.rules).stream() .filter(m -> m.rule.field.startsWith("departure ")) - .mapToLong(m -> this.myTicket.values.get(m.idx)) + .mapToLong(m -> notes.myTicket.values.get(m.idx)) .reduce(1, (a, b) -> a * b); } + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "71"), + @Sample(method = "part2", input = TEST2, expected = "1716"), + }) public static void main(final String[] args) throws Exception { - assert createDebug(TEST1).solvePart1() == 71; - assert createDebug(TEST2).solvePart2() == 1716; - - final Puzzle puzzle = puzzle(AoC2020_16.class); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", create(input)::solvePart1), - () -> lap("Part 2", create(input)::solvePart2) - ); + AoC2020_16.create().run(); } - private static final List TEST1 = splitLines(""" + private static final String TEST1 = """ class: 1-3 or 5-7 row: 6-11 or 33-44 seat: 13-40 or 45-50 @@ -101,8 +75,8 @@ public static void main(final String[] args) throws Exception { 40,4,50 55,2,20 38,6,12 - """); - private static final List TEST2 = splitLines(""" + """; + private static final String TEST2 = """ departure date: 0-1 or 4-19 departure time: 0-5 or 8-19 departure track: 0-13 or 16-19 @@ -114,12 +88,26 @@ public static void main(final String[] args) throws Exception { 3,9,18 15,1,5 5,14,9 - """); + """; - private static final record Rule( + private record Rule( String field, Set> validRanges ) { + + public static Rule parseRule(final String s) { + final var splits1 = s.split(": "); + final var builder = Rule.builder(); + builder.field(splits1[0]); + for (final var split : splits1[1].split(" or ")) { + final var splits3 = split.split("-"); + final int start = Integer.parseInt(splits3[0]); + final int end = Integer.parseInt(splits3[1]); + builder.validRange(RangeInclusive.between(start, end)); + } + return builder.build(); + } + public boolean validate(final int value) { return this.validRanges.stream().anyMatch(r -> r.contains(value)); } @@ -148,8 +136,14 @@ public RuleBuilder validRange(final RangeInclusive validRange) { } } - private static final record Ticket(List values) { + private record Ticket(List values) { + public static Ticket parseTicket(final String s) { + return new Ticket(Arrays.stream(s.split(",")) + .map(Integer::parseInt) + .collect(toList())); + } + public boolean invalid(final Set rules) { return this.values.stream() .anyMatch(fieldDoesNotMatchAnyRule(rules)); @@ -166,6 +160,22 @@ private Predicate fieldDoesNotMatchAnyRule(final Set rules) { } } + record Notes( + Set rules, Ticket myTicket, List tickets) { + + public static Notes fromInput(final List input) { + final List> blocks = StringOps.toBlocks(input); + final Set rules = blocks.get(0).stream() + .map(Rule::parseRule) + .collect(toSet()); + final Ticket myTicket = Ticket.parseTicket(blocks.get(1).get(1)); + final List tickets = blocks.get(2).stream().skip(1) + .map(Ticket::parseTicket) + .collect(toList()); + return new Notes(rules, myTicket, tickets); + } + } + private static final record Match(Rule rule, int idx) { } private static final record Matches(Map> matches) { diff --git a/src/main/java/AoC2020_17.java b/src/main/java/AoC2020_17.java index cc0a357d..9aa267f0 100644 --- a/src/main/java/AoC2020_17.java +++ b/src/main/java/AoC2020_17.java @@ -7,90 +7,87 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.BiFunction; +import java.util.function.Function; import com.github.pareronia.aoc.CharGrid; +import com.github.pareronia.aoc.Grid.Cell; import com.github.pareronia.aoc.game_of_life.GameOfLife; import com.github.pareronia.aoc.game_of_life.InfiniteGrid; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2020_17 extends AoCBase { +public class AoC2020_17 extends SolutionBase { private static final char ON = '#'; private static final char OFF = '.'; private static final int GENERATIONS = 6; - private final List input; - - private AoC2020_17(final List input, final boolean debug) { + private AoC2020_17(final boolean debug) { super(debug); - this.input = input; } - public static AoC2020_17 create(final List input) { - return new AoC2020_17(input, false); + public static AoC2020_17 create() { + return new AoC2020_17(false); } - public static AoC2020_17 createDebug(final List input) { - return new AoC2020_17(input, true); + public static AoC2020_17 createDebug() { + return new AoC2020_17(true); + } + + @Override + protected CharGrid parseInput(final List inputs) { + return CharGrid.from(inputs); + } + + @Override + public Integer solvePart1(final CharGrid grid) { + return solve(grid, this::create3DCell); + } + + @Override + public Integer solvePart2(final CharGrid grid) { + return solve(grid, this::create4DCell); } - private List create3DCell(final int row, final int col) { - return List.of(0, row, col); + private List create3DCell(final Cell cell) { + return List.of(0, cell.getRow(), cell.getCol()); } - private List create4DCell(final int row, final int col) { - return List.of(0, 0, row, col); - } - - @SuppressWarnings("unchecked") - private GameOfLife> parse( - final BiFunction> cellFactory - ) { - final CharGrid grid = new CharGrid(this.input); - final Set> on = grid.getAllEqualTo(ON) - .map(cell -> cellFactory.apply(cell.getRow(), cell.getCol())) - .collect(toSet()); - return new GameOfLife<>(new InfiniteGrid(), GameOfLife.classicRules, on); + private List create4DCell(final Cell cell) { + return List.of(0, 0, cell.getRow(), cell.getCol()); } + @SuppressWarnings("unchecked") private int solve( - final BiFunction> cellFactory + final CharGrid grid, + final Function> cellFactory ) { - GameOfLife> gol = parse(cellFactory); + final Set> on = grid.getAllEqualTo(ON) + .map(cell -> cellFactory.apply(cell)) + .collect(toSet()); + GameOfLife> gol = new GameOfLife<>( + new InfiniteGrid(), GameOfLife.classicRules, on); + for (int i = 0; i < GENERATIONS; i++) { gol = gol.nextGeneration(); } - return gol.getAlive().size(); - } - - @Override - public Integer solvePart1() { - return solve(this::create3DCell); - } - - @Override - public Integer solvePart2() { - return solve(this::create4DCell); + return gol.alive().size(); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "112"), + @Sample(method = "part2", input = TEST, expected = "848"), + }) public static void main(final String[] args) throws Exception { - assert createDebug(TEST).solvePart1() == 112; - assert createDebug(TEST).solvePart2() == 848; - - final Puzzle puzzle = puzzle(AoC2020_17.class); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", create(input)::solvePart1), - () -> lap("Part 2", create(input)::solvePart2) - ); + AoC2020_17.create().run(); } - private static final List TEST = splitLines( - ".#.\r\n" + - "..#\r\n" + - "###" - ); + private static final String TEST = """ + .#.\r + ..#\r + ### + """; @SuppressWarnings("unused") private static final class Printer { diff --git a/src/main/java/AoC2020_18.java b/src/main/java/AoC2020_18.java index d617b333..92ea7ff0 100644 --- a/src/main/java/AoC2020_18.java +++ b/src/main/java/AoC2020_18.java @@ -6,26 +6,22 @@ import java.util.Set; import com.github.pareronia.aoc.Utils; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -public class AoC2020_18 extends AoCBase { - - private final List input; +public class AoC2020_18 extends SolutionBase, Long, Long> { - private AoC2020_18(final List input, final boolean debug) { + private AoC2020_18(final boolean debug) { super(debug); - this.input = input; } - public static AoC2020_18 create(final List input) { - return new AoC2020_18(input, false); + public static AoC2020_18 create() { + return new AoC2020_18(false); } - public static AoC2020_18 createDebug(final List input) { - return new AoC2020_18(input, true); + public static AoC2020_18 createDebug() { + return new AoC2020_18(true); } private List tokenize(final String string) { @@ -81,49 +77,47 @@ private String fixForAdditionPreference(final String string) { } @Override - public Long solvePart1() { - return this.input.stream() + protected List parseInput(final List inputs) { + return inputs; + } + + @Override + public Long solvePart1(final List input) { + return input.stream() .map(this::tokenize) .map(this::evaluate) - .mapToLong(ResultAndPosition::getResult) + .mapToLong(ResultAndPosition::result) .sum(); } @Override - public Long solvePart2() { - return this.input.stream() + public Long solvePart2(final List input) { + return input.stream() .map(this::fixForAdditionPreference) .map(this::tokenize) .map(this::evaluate) - .mapToLong(ResultAndPosition::getResult) + .mapToLong(ResultAndPosition::result) .sum(); } + @Samples({ + // 71 + 51 + 26 + 437 + 12240 + 13632 + @Sample(method = "part1", input = TEST, expected = "26457"), + // 231 + 51 + 46 + 1445 + 669060 + 23340 + @Sample(method = "part2", input = TEST, expected = "694173"), + }) public static void main(final String[] args) throws Exception { - assert createDebug(TEST).solvePart1() == 71 + 51 + 26 + 437 + 12240 + 13632; - assert createDebug(TEST).solvePart2() == 231 + 51 + 46 + 1445 + 669060 + 23340; - - final Puzzle puzzle = puzzle(AoC2020_18.class); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", create(input)::solvePart1), - () -> lap("Part 2", create(input)::solvePart2) - ); + AoC2020_18.create().run(); } - private static final List TEST = splitLines( - "1 + 2 * 3 + 4 * 5 + 6\r\n" + - "1 + (2 * 3) + (4 * (5 + 6))\r\n" + - "2 * 3 + (4 * 5)\r\n" + - "5 + (8 * 3 + 9 + 3 * 4 * 3)\r\n" + - "5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))\r\n" + - "((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2" - ); + private static final String TEST = """ + 1 + 2 * 3 + 4 * 5 + 6 + 1 + (2 * 3) + (4 * (5 + 6)) + 2 * 3 + (4 * 5) + 5 + (8 * 3 + 9 + 3 * 4 * 3) + 5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4)) + ((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2 + """; - @RequiredArgsConstructor - private static final class ResultAndPosition { - @Getter - private final long result; - private final int position; - } + record ResultAndPosition(long result, int position) {} } \ No newline at end of file diff --git a/src/main/java/AoC2020_19.java b/src/main/java/AoC2020_19.java new file mode 100644 index 00000000..6f8ddb8f --- /dev/null +++ b/src/main/java/AoC2020_19.java @@ -0,0 +1,177 @@ +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toMap; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2020_19 + extends SolutionBase { + + private AoC2020_19(final boolean debug) { + super(debug); + } + + public static AoC2020_19 create() { + return new AoC2020_19(false); + } + + public static AoC2020_19 createDebug() { + return new AoC2020_19(true); + } + + @Override + protected RulesAndMessages parseInput(final List inputs) { + return RulesAndMessages.fromInput(inputs); + } + + @Override + public Integer solvePart1(final RulesAndMessages input) { + final Pattern pattern + = Pattern.compile(this.getRegexp(input.rules, "0")); + return (int) input.messages.stream() + .filter(m -> pattern.matcher(m).matches()) + .count(); + } + + @Override + public Integer solvePart2(final RulesAndMessages input) { + final Pattern pattern31 + = Pattern.compile(this.getRegexp(input.rules, "31")); + log("31:" + pattern31.pattern()); + final Pattern pattern42 + = Pattern.compile(this.getRegexp(input.rules, "42")); + log("42:" + pattern42.pattern()); + int ans = 0; + for (final String message : input.messages) { + log("%s (%d)".formatted(message, message.length())); + int pos = 0; + int count42 = 0; + final Matcher m42 = pattern42.matcher(message); + while (m42.find(pos) && m42.start() == pos) { + log("42: (%d,%d) : %s".formatted(m42.start(), m42.end(), m42.group())); + count42++; + pos = m42.end(); + } + int count31 = 0; + final Matcher m31 = pattern31.matcher(message); + while (m31.find(pos) && m31.start() == pos) { + log("31: (%d,%d) : %s".formatted(m31.start(), m31.end(), m31.group())); + count31++; + pos = m31.end(); + } + log(pos); + if (0 < count31 && count31 < count42 && pos == message.length()) { + log("OK"); + ans++; + } + } + return ans; + } + + private String getRegexp(final Map rules, final String key) { + if ("|".equals(key)) { + return "|"; + } + final String rule = rules.get(key); + if (rule.startsWith("\"")) { + return rule.substring(1, 2); + } + final String ans = Arrays.stream(rule.split(" ")) + .map(sp -> this.getRegexp(rules, sp)) + .collect(joining()); + return "(%s)".formatted(ans); + } + + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "2"), + @Sample(method = "part1", input = TEST2, expected = "3"), + @Sample(method = "part2", input = TEST2, expected = "12"), + }) + public static void main(final String[] args) throws Exception { + AoC2020_19.create().run(); + } + + private static final String TEST1 = """ + 0: 4 1 5 + 1: 2 3 | 3 2 + 2: 4 4 | 5 5 + 3: 4 5 | 5 4 + 4: "a" + 5: "b" + + ababbb + bababa + abbbab + aaabbb + aaaabbb + """; + private static final String TEST2 = """ + 42: 9 14 | 10 1 + 9: 14 27 | 1 26 + 10: 23 14 | 28 1 + 1: "a" + 11: 42 31 + 5: 1 14 | 15 1 + 19: 14 1 | 14 14 + 12: 24 14 | 19 1 + 16: 15 1 | 14 14 + 31: 14 17 | 1 13 + 6: 14 14 | 1 14 + 2: 1 24 | 14 4 + 0: 8 11 + 13: 14 3 | 1 12 + 15: 1 | 14 + 17: 14 2 | 1 7 + 23: 25 1 | 22 14 + 28: 16 1 + 4: 1 1 + 20: 14 14 | 1 15 + 3: 5 14 | 16 1 + 27: 1 6 | 14 18 + 14: "b" + 21: 14 1 | 1 14 + 25: 1 1 | 1 14 + 22: 14 14 + 8: 42 + 26: 14 22 | 1 20 + 18: 15 15 + 7: 14 5 | 1 21 + 24: 14 1 + + abbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa + bbabbbbaabaabba + babbbbaabbbbbabbbbbbaabaaabaaa + aaabbbbbbaaaabaababaabababbabaaabbababababaaa + bbbbbbbaaaabbbbaaabbabaaa + bbbababbbbaaaaaaaabbababaaababaabab + ababaaaaaabaaab + ababaaaaabbbaba + baabbaaaabbaaaababbaababb + abbbbabbbbaaaababbbbbbaaaababb + aaaaabbaabaaaaababaa + aaaabbaaaabbaaa + aaaabbaabbaaaaaaabbbabbbaaabbaabaaa + babaaabbbaaabaababbaabababaaab + aabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba + """; + + record RulesAndMessages(Map rules, List messages) { + + public static RulesAndMessages fromInput(final List inputs) { + final List> blocks = StringOps.toBlocks(inputs); + final Map rules = blocks.get(0).stream() + .map(line -> StringOps.splitOnce(line, ": ")) + .collect(toMap(StringSplit::left, StringSplit::right)); + return new RulesAndMessages(rules, blocks.get(1)); + } + } +} diff --git a/src/main/java/AoC2020_20.java b/src/main/java/AoC2020_20.java index e990b13e..2045d842 100644 --- a/src/main/java/AoC2020_20.java +++ b/src/main/java/AoC2020_20.java @@ -1,604 +1,593 @@ -import static com.github.pareronia.aoc.SetUtils.union; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toSet; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import com.github.pareronia.aoc.CharGrid; -import com.github.pareronia.aocd.Aocd; -import com.github.pareronia.aocd.Puzzle; - -public class AoC2020_20 extends AoCBase { - - private final TileSet tileSet; - private final Logger logger; - - private AoC2020_20(final List input, final boolean debug) { - super(debug); - this.logger = obj -> log(() -> obj); - this.tileSet = parse(input); - } - - public static final AoC2020_20 create(final List input) { - return new AoC2020_20(input, false); - } - - public static final AoC2020_20 createDebug(final List input) { - return new AoC2020_20(input, true); - } - - private TileSet parse(final List inputs) { - final Set tiles = toBlocks(inputs).stream() - .map(block -> { - Integer id = null; - final List grid = new ArrayList<>(); - for (final String line : block) { - if (line.startsWith("Tile ")) { - id = Integer.valueOf(line.substring("Tile ".length(), line.length() - 1)); - } else { - grid.add(line); - } - } - return new Tile(id, grid); - }) - .collect(toSet()); - return new TileSet(tiles, logger); - } - - @Override - public Long solvePart1() { - this.tileSet.getTiles().forEach(this::log); - return Math.prod(this.tileSet.findCorners().stream().map(Tile::getId).collect(toSet())); - } - - @Override - public Long solvePart2() { - this.tileSet.puzzle(); - this.tileSet.removeTileEdges(); - this.tileSet.printPlacedTiles(); - CharGrid image = CharGrid.from(this.tileSet.createImageGrid()); - print(image); - log("Looking for Nessies..."); - Map nessies = null; - final Iterator permutations = image.getPermutations(); - while (permutations.hasNext()) { - image = permutations.next(); - nessies = NessieFinder.findNessies(image); - if (nessies.size() > 1) { - for (final Entry nessie : nessies.entrySet()) { - log(String.format("Found 1 Nessie at (%d, %d)!", - nessie.getKey(), nessie.getValue())); - } - break; - } else if (nessies.size() == 1) { - final CharGrid grid = NessieFinder.markNessies(nessies, image); - print(grid); - log("One is not enough? Looking for more Nessies..."); - } - } - final long octothorps = image.countAllEqualTo('#'); - log("Octothorps: " + octothorps); - image = NessieFinder.markNessies(nessies, image); - print(image); - return octothorps - 15 * nessies.size(); - } - - private void print(final CharGrid image) { - image.getRowsAsStrings().forEach(this::log); - } - - public static void main(final String[] args) throws Exception { - assert AoC2020_20.createDebug(splitLines(TEST)).solvePart1() == 20899048083289L; - assert AoC2020_20.createDebug(splitLines(TEST)).solvePart2() == 273L; - - final Puzzle puzzle = Aocd.puzzle(2020, 20); - final List inputData = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2020_20.create(inputData)::solvePart1), - () -> lap("Part 2", AoC2020_20.create(inputData)::solvePart2) - ); - } - - private static final String TEST = - "Tile 2311:\r\n" + - "..##.#..#.\r\n" + - "##..#.....\r\n" + - "#...##..#.\r\n" + - "####.#...#\r\n" + - "##.##.###.\r\n" + - "##...#.###\r\n" + - ".#.#.#..##\r\n" + - "..#....#..\r\n" + - "###...#.#.\r\n" + - "..###..###\r\n" + - "\r\n" + - "Tile 1951:\r\n" + - "#.##...##.\r\n" + - "#.####...#\r\n" + - ".....#..##\r\n" + - "#...######\r\n" + - ".##.#....#\r\n" + - ".###.#####\r\n" + - "###.##.##.\r\n" + - ".###....#.\r\n" + - "..#.#..#.#\r\n" + - "#...##.#..\r\n" + - "\r\n" + - "Tile 1171:\r\n" + - "####...##.\r\n" + - "#..##.#..#\r\n" + - "##.#..#.#.\r\n" + - ".###.####.\r\n" + - "..###.####\r\n" + - ".##....##.\r\n" + - ".#...####.\r\n" + - "#.##.####.\r\n" + - "####..#...\r\n" + - ".....##...\r\n" + - "\r\n" + - "Tile 1427:\r\n" + - "###.##.#..\r\n" + - ".#..#.##..\r\n" + - ".#.##.#..#\r\n" + - "#.#.#.##.#\r\n" + - "....#...##\r\n" + - "...##..##.\r\n" + - "...#.#####\r\n" + - ".#.####.#.\r\n" + - "..#..###.#\r\n" + - "..##.#..#.\r\n" + - "\r\n" + - "Tile 1489:\r\n" + - "##.#.#....\r\n" + - "..##...#..\r\n" + - ".##..##...\r\n" + - "..#...#...\r\n" + - "#####...#.\r\n" + - "#..#.#.#.#\r\n" + - "...#.#.#..\r\n" + - "##.#...##.\r\n" + - "..##.##.##\r\n" + - "###.##.#..\r\n" + - "\r\n" + - "Tile 2473:\r\n" + - "#....####.\r\n" + - "#..#.##...\r\n" + - "#.##..#...\r\n" + - "######.#.#\r\n" + - ".#...#.#.#\r\n" + - ".#########\r\n" + - ".###.#..#.\r\n" + - "########.#\r\n" + - "##...##.#.\r\n" + - "..###.#.#.\r\n" + - "\r\n" + - "Tile 2971:\r\n" + - "..#.#....#\r\n" + - "#...###...\r\n" + - "#.#.###...\r\n" + - "##.##..#..\r\n" + - ".#####..##\r\n" + - ".#..####.#\r\n" + - "#..#.#..#.\r\n" + - "..####.###\r\n" + - "..#.#.###.\r\n" + - "...#.#.#.#\r\n" + - "\r\n" + - "Tile 2729:\r\n" + - "...#.#.#.#\r\n" + - "####.#....\r\n" + - "..#.#.....\r\n" + - "....#..#.#\r\n" + - ".##..##.#.\r\n" + - ".#.####...\r\n" + - "####.#.#..\r\n" + - "##.####...\r\n" + - "##..#.##..\r\n" + - "#.##...##.\r\n" + - "\r\n" + - "Tile 3079:\r\n" + - "#.#.#####.\r\n" + - ".#..######\r\n" + - "..#.......\r\n" + - "######....\r\n" + - "####.#..#.\r\n" + - ".#...#.##.\r\n" + - "#.#####.##\r\n" + - "..#.###...\r\n" + - "..#.......\r\n" + - "..#.###..."; - - static final class Tile { - private final Integer id; - private final CharGrid grid; - - public Tile(final Integer id, final List grid) { - this.id = id; - this.grid = CharGrid.from(grid); - } - - public Tile(final Integer id, final CharGrid grid) { - this.id = id; - this.grid = grid; - } - - public Integer getId() { - return id; - } - - public CharGrid getGrid() { - return grid; - } - - private char[] getTopEdge() { - return this.grid.getTopEdge(); - } - - private char[] getBottomEdge() { - return this.grid.getBottomEdge(); - } - - private char[] getLeftEdge() { - return this.grid.getLeftEdge(); - } - - private char[] getRightEdge() { - return this.grid.getRightEdge(); - } - - private char[] getRow(final int row) { - return this.grid.getRow(row); - } - - private Integer getHeight() { - return this.grid.getHeight(); - } - - private Set getAllEdges() { - return this.grid.getAllEdges(); - } - - private Set getAllEdgesReversed() { - return this.grid.getAllEdgesReversed(); - } - - public Tile getWithEdgesRemoved() { - return new Tile(id, grid.getWithEdgesRemoved()); - } - - public Iterator getAllPermutations() { - return new Iterator<>() { - final Iterator inner = Tile.this.getGrid().getPermutations(); - - @Override - public boolean hasNext() { - return inner.hasNext(); - } - - @Override - public Tile next() { - return new Tile(Tile.this.getId(), inner.next()); - } - }; - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - sb.append("Tile ").append(this.id).append(":").append(System.lineSeparator()); -// for (char[] cs : grid) { -// sb.append(new String(cs)).append(System.lineSeparator()); -// } - return sb.toString(); - } - - @Override - public int hashCode() { - return Objects.hash(id); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Tile)) { - return false; - } - final Tile other = (Tile) obj; - return Objects.equals(id, other.id); - } - } - - private static final class TileSet { - private final Set tiles; - private final Tile[][] placedTiles; - private final Logger logger; - - public TileSet(final Set tiles, final Logger logger) { - this.tiles = tiles; - this.logger = logger; - this.placedTiles = new Tile[Math.sqrt(tiles.size())][Math.sqrt(tiles.size())]; - } - - public Set getTiles() { - return this.tiles; - } - - private void log(final Object object) { - this.logger.log(object); - } - - private void placeTile(final Tile tile, final int row, final int col) { - placedTiles[row][col] = tile; - tiles.remove(tile); - } - - public void printPlacedTiles() { - if (placedTiles.length > 3) { - return; - } - for (final Tile[] tiles : placedTiles) { - final Tile tile = Arrays.stream(tiles) - .filter(Objects::nonNull) - .findFirst().orElse(null); - if (tile == null) { - continue; - } - for (int i = 0; i < tile.getHeight(); i++ ) { - final int row = i; - log(Arrays.stream(tiles).map(t -> { - if (t == null) { - return " "; - } - return new String(t.getRow(row)); - }).collect(joining(" "))); - } - log(""); - } - for (final Tile[] tiles : placedTiles) { - final Tile tile = Arrays.stream(tiles) - .filter(Objects::nonNull) - .findFirst().orElse(null); - if (tile == null) { - continue; - } - log(Arrays.stream(tiles).map(t -> { - if (t == null) { - return " "; - } - return String.valueOf(t.getId()); - }).collect(joining(" "))); - log(""); - } - } - - private Set getEdgesForMatching(final Tile tile) { - if (Arrays.stream(placedTiles) - .flatMap(a -> Arrays.stream(a)) - .anyMatch(tile::equals)) { - return tile.getAllEdges(); - } else { - return new HashSet<>(union(tile.getAllEdges(), tile.getAllEdgesReversed())); - } - } - - private boolean haveCommonEdge(final Tile tile1, final Tile tile2) { - return tile1.getAllEdges().stream() - .flatMap(edge1 -> getEdgesForMatching(tile2).stream() - .map(edge2 -> new char[][] { edge1, edge2 })) - .filter(p -> Arrays.equals(p[0], p[1])) - .count() == 1; - } - - public Set findCorners() { - return getTiles().stream() - .filter(tile1 -> getTiles().stream() - .filter(tile2 -> !tile2.getId().equals(tile1.getId())) - .filter(tile2 -> haveCommonEdge(tile1, tile2)) - .count() == 2) - .collect(toSet()); - } - - public void puzzle() { - final Set corners = findCorners(); - // Pick a corner, any corner... - final ArrayList cornersList = new ArrayList<>(corners); - Collections.shuffle(cornersList); - final Tile corner = cornersList.get(0); - log("Unplaced tiles: " + getTiles().size()); - log("Starting with " + corner.getId()); - final Iterator allPermutations = corner.getAllPermutations(); - while (allPermutations.hasNext()) { - final Tile next = allPermutations.next(); - placeTile(next, 0, 0); - final Optional right = TileMatcher.findRightSideMatch(next, getTiles()); - final Optional bottom = TileMatcher.findBottomSideMatch(next, getTiles()); - if (right.isPresent() && bottom.isPresent()) { - log("Found corner orientation with right and bottom side match"); - placeTile(right.get(), 0, 1); - break; - } - } - assert placedTiles[0][0] != null && placedTiles[0][1] != null; - log("Unplaced tiles: " + getTiles().size()); - printPlacedTiles(); - Tile current = placedTiles[0][1]; - // first row - log("Continue first row"); - for (int i = 2; i < placedTiles[0].length; i++) { - current = placedTiles[0][i-1]; - final Optional right = TileMatcher.findRightSideMatch(current, getTiles()); - assert right.isPresent(); - placeTile(right.get(), 0, i); - printPlacedTiles(); - log("Unplaced tiles: " + getTiles().size()); - } - - // next rows - for (int j = 1; j < placedTiles.length; j++) { - log("Row " + (j + 1)); - for (int i = 0; i < placedTiles[j].length; i++) { - current = placedTiles[j-1][i]; - final Optional bottom = TileMatcher.findBottomSideMatch(current, getTiles()); - assert bottom.isPresent(); - placeTile(bottom.get(), j, i); - printPlacedTiles(); - log("Unplaced tiles: " + getTiles().size()); - } - } - } - - public void removeTileEdges() { - for (int i = 0; i < placedTiles.length; i++) { - final Tile[] tiles = placedTiles[i]; - for (int j = 0; j < tiles.length; j++) { - placedTiles[i][j] = tiles[j].getWithEdgesRemoved(); - } - } - } - - public List createImageGrid() { - final List grid = new ArrayList<>(); - for (int i = 0; i < placedTiles.length; i++) { - final Tile[] tiles = placedTiles[i]; - for (int j = 0; j < tiles[0].grid.getHeight(); j++) { - final StringBuilder row = new StringBuilder(); - for (final Tile tile : tiles) { - row.append(tile.getRow(j)); - } - grid.add(row.toString()); - } - } - return grid; - } - } - - private static final class Math { - - public static long prod(final Collection numbers) { - return numbers.stream().map(Integer::longValue).reduce(1L, (a, b) -> a * b); - } - - public static int sqrt(final int number) { - return Double.valueOf(java.lang.Math.sqrt(number)).intValue(); - } - } - - static final class NessieFinder { - - private static final Pattern PATTERN2 = Pattern.compile(".\\#..\\#..\\#..\\#..\\#..\\#"); - private static final Pattern PATTERN1 = Pattern.compile("\\#....\\#\\#....\\#\\#....\\#\\#\\#"); - private static final char NESSIE_CHAR = '\u2592'; - - public static Map findNessies(final CharGrid grid) { - final Map nessies = new HashMap<>(); - for (int i = 1; i < grid.getHeight(); i++) { - final Matcher m1 = PATTERN1.matcher(grid.getRowAsString(i)); - while (m1.find()) { - final int tail = m1.start(0); - if ("#".equals(grid.getRowAsString(i - 1).substring(tail + 18, tail + 19))) { - final Matcher m2 = PATTERN2 - .matcher(grid.getRowAsString(i + 1).substring(tail)); - if (m2.find()) { - nessies.put(i, tail); - } - } - } - } - return nessies; - } - - public static CharGrid markNessies(final Map nessies, final CharGrid gridIn) { - final List grid = gridIn.getRowsAsStringList(); - for (final Entry nessie : nessies.entrySet()) { - final int idx = nessie.getValue(); - final char[] chars0 = grid.get(nessie.getKey() - 1).toCharArray(); - chars0[idx+18] = NESSIE_CHAR; - grid.set(nessie.getKey() - 1, new String(chars0)); - final char[] chars1 = grid.get(nessie.getKey()).toCharArray(); - chars1[idx] = NESSIE_CHAR; - chars1[idx+5] = NESSIE_CHAR; - chars1[idx+6] = NESSIE_CHAR; - chars1[idx+11] = NESSIE_CHAR; - chars1[idx+12] = NESSIE_CHAR; - chars1[idx+17] = NESSIE_CHAR; - chars1[idx+18] = NESSIE_CHAR; - chars1[idx+19] = NESSIE_CHAR; - grid.set(nessie.getKey(), new String(chars1)); - final char[] chars2 = grid.get(nessie.getKey() + 1).toCharArray(); - chars2[idx+1] = NESSIE_CHAR; - chars2[idx+4] = NESSIE_CHAR; - chars2[idx+7] = NESSIE_CHAR; - chars2[idx+10] = NESSIE_CHAR; - chars2[idx+13] = NESSIE_CHAR; - chars2[idx+16] = NESSIE_CHAR; - grid.set(nessie.getKey() + 1, new String(chars2)); - } - for (int j = 0; j < grid.size(); j++) { - final char[] chars = grid.get(j).toCharArray(); - for (int i = 0; i < chars.length; i++) { - if (chars[i] == '#') { - chars[i] = '~'; - } else if (chars[i] == '.') { - chars[i] = '_'; - } - } - grid.set(j, new String(chars)); - } - return CharGrid.from(grid); - } - } - - static final class TileMatcher { - - private static Predicate rightSide(final Tile tile) { - return t -> Arrays.equals(tile.getRightEdge(), t.getLeftEdge()); - } - - private static Predicate bottomSide(final Tile tile) { - return t -> Arrays.equals(tile.getBottomEdge(), t.getTopEdge()); - } - - public static Optional findRightSideMatch(final Tile tile, final Set tiles) { - return findMatch(tile, tiles, rightSide(tile)); - } - - public static Optional findBottomSideMatch(final Tile tile, final Set tiles) { - return findMatch(tile, tiles, bottomSide(tile)); - } - - private static Optional findMatch(final Tile tile, final Set tiles, final Predicate matcher) { - return tiles.stream() - .flatMap(t -> asStream(t.getAllPermutations())) - .filter(matcher) - .findAny(); - } - - private static Stream asStream(final Iterator sourceIterator) { - final Iterable iterable = () -> sourceIterator; - return StreamSupport.stream(iterable.spliterator(), false); - } - } - - private interface Logger { - void log(Object object); - } -} +import static com.github.pareronia.aoc.SetUtils.union; +import static com.github.pareronia.aoc.StringOps.toBlocks; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toSet; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.github.pareronia.aoc.CharGrid; +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.Utils; +import com.github.pareronia.aoc.solution.Logger; +import com.github.pareronia.aoc.solution.LoggerEnabled; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public class AoC2020_20 extends SolutionBase, Long, Long> { + + private AoC2020_20(final boolean debug) { + super(debug); + } + + public static final AoC2020_20 create() { + return new AoC2020_20(false); + } + + public static final AoC2020_20 createDebug() { + return new AoC2020_20(true); + } + + @Override + protected Set parseInput(final List inputs) { + return toBlocks(inputs).stream() + .map(Tile::fromInput) + .collect(toSet()); + } + + @Override + public Long solvePart1(final Set tiles) { + final TileSet tileSet = new TileSet(tiles, this.logger); + tileSet.getTiles().forEach(this::log); + return Math.prod(tileSet.findCorners().stream().map(Tile::id).collect(toSet())); + } + + @Override + public Long solvePart2(final Set tiles) { + final TileSet ts = new TileSet(new HashSet<>(tiles), this.logger); + return ts.findCorners().stream() + .map(corner -> { + final TileSet tileSet = new TileSet(new HashSet<>(tiles), this.logger); + return buildImage(tileSet, corner); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .peek(this::print) + .mapToLong(image -> { + final long octothorps = image.countAllEqualTo('#'); + log("Octothorps: " + octothorps); + final List nessies = findNessies(image); + return octothorps - 15 * nessies.size(); + }) + .min().getAsLong(); + } + + private Optional buildImage(final TileSet tileSet, final Tile corner) { + tileSet.puzzle(corner); + tileSet.removeTileEdges(); + tileSet.printPlacedTiles(); + if (Arrays.stream(tileSet.placedTiles).anyMatch(t -> t == null)) { + return Optional.empty(); + } + return Optional.of(CharGrid.from(tileSet.createImageGrid())); + } + + private List findNessies(CharGrid image) { + log("Looking for Nessies..."); + List nessies = null; + final Iterator permutations = image.getPermutations(); + while (permutations.hasNext()) { + image = permutations.next(); + nessies = NessieFinder.findNessies(image); + if (nessies.size() > 1) { + for (final Cell nessie : nessies) { + log(String.format("Found 1 Nessie at (%d, %d)!", + nessie.getRow(), nessie.getCol())); + } + log("Total Nessies found: " + nessies.size()); + break; + } else if (nessies.size() == 1) { + final CharGrid grid = NessieFinder.markNessies(nessies, image); + print(grid); + log("One is not enough? Looking for more Nessies..."); + } + } + image = NessieFinder.markNessies(nessies, image); + print(image); + return nessies; + } + + private void print(final CharGrid image) { + image.getRowsAsStrings().forEach(this::log); + } + + @Samples({ + @Sample(method = "part1", input = TEST, expected = "20899048083289"), + @Sample(method = "part2", input = TEST, expected = "273"), + }) + public static void main(final String[] args) throws Exception { + AoC2020_20.createDebug().run(); + } + + private static final String TEST = + """ + Tile 2311: + ..##.#..#. + ##..#..... + #...##..#. + ####.#...# + ##.##.###. + ##...#.### + .#.#.#..## + ..#....#.. + ###...#.#. + ..###..### + + Tile 1951: + #.##...##. + #.####...# + .....#..## + #...###### + .##.#....# + .###.##### + ###.##.##. + .###....#. + ..#.#..#.# + #...##.#.. + + Tile 1171: + ####...##. + #..##.#..# + ##.#..#.#. + .###.####. + ..###.#### + .##....##. + .#...####. + #.##.####. + ####..#... + .....##... + + Tile 1427: + ###.##.#.. + .#..#.##.. + .#.##.#..# + #.#.#.##.# + ....#...## + ...##..##. + ...#.##### + .#.####.#. + ..#..###.# + ..##.#..#. + + Tile 1489: + ##.#.#.... + ..##...#.. + .##..##... + ..#...#... + #####...#. + #..#.#.#.# + ...#.#.#.. + ##.#...##. + ..##.##.## + ###.##.#.. + + Tile 2473: + #....####. + #..#.##... + #.##..#... + ######.#.# + .#...#.#.# + .######### + .###.#..#. + ########.# + ##...##.#. + ..###.#.#. + + Tile 2971: + ..#.#....# + #...###... + #.#.###... + ##.##..#.. + .#####..## + .#..####.# + #..#.#..#. + ..####.### + ..#.#.###. + ...#.#.#.# + + Tile 2729: + ...#.#.#.# + ####.#.... + ..#.#..... + ....#..#.# + .##..##.#. + .#.####... + ####.#.#.. + ##.####... + ##..#.##.. + #.##...##. + + Tile 3079: + #.#.#####. + .#..###### + ..#....... + ######.... + ####.#..#. + .#...#.##. + #.#####.## + ..#.###... + ..#....... + ..#.###... + """; + + record Tile(int id, CharGrid grid) { + + public static Tile fromInput(final List block) { + Integer id = null; + final List grid = new ArrayList<>(); + for (final String line : block) { + if (line.startsWith("Tile ")) { + id = Integer.valueOf(line.substring("Tile ".length(), line.length() - 1)); + } else { + grid.add(line); + } + } + return new Tile(id, CharGrid.from(grid)); + } + + private char[] getTopEdge() { + return this.grid.getTopEdge(); + } + + private char[] getBottomEdge() { + return this.grid.getBottomEdge(); + } + + private char[] getLeftEdge() { + return this.grid.getLeftEdge(); + } + + private char[] getRightEdge() { + return this.grid.getRightEdge(); + } + + private char[] getRow(final int row) { + return this.grid.getRow(row); + } + + private Integer getHeight() { + return this.grid.getHeight(); + } + + private Set getAllEdges() { + return this.grid.getAllEdges(); + } + + private Set getAllEdgesReversed() { + return this.grid.getAllEdgesReversed(); + } + + public Tile getWithEdgesRemoved() { + return new Tile(id, grid.getWithEdgesRemoved()); + } + + public Iterator getAllPermutations() { + return new Iterator<>() { + final Iterator inner = Tile.this.grid().getPermutations(); + + @Override + public boolean hasNext() { + return inner.hasNext(); + } + + @Override + public Tile next() { + return new Tile(Tile.this.id(), inner.next()); + } + }; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("Tile ").append(this.id).append(":").append(System.lineSeparator()); +// for (char[] cs : grid) { +// sb.append(new String(cs)).append(System.lineSeparator()); +// } + return sb.toString(); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof final Tile other)) { + return false; + } + return Objects.equals(id, other.id); + } + } + + private static final class TileSet implements LoggerEnabled { + private final Set tiles; + private final Tile[][] placedTiles; + private final Logger logger; + + public TileSet(final Set tiles, final Logger logger) { + this.tiles = tiles; + this.logger = logger; + this.placedTiles = new Tile[Math.sqrt(tiles.size())][Math.sqrt(tiles.size())]; + } + + public Set getTiles() { + return this.tiles; + } + + @Override + public Logger getLogger() { + return logger; + } + + private void placeTile(final Tile tile, final int row, final int col) { + placedTiles[row][col] = tile; + tiles.remove(tile); + } + + public void printPlacedTiles() { + if (placedTiles.length > 3) { + return; + } + for (final Tile[] tiles : placedTiles) { + final Tile tile = Arrays.stream(tiles) + .filter(Objects::nonNull) + .findFirst().orElse(null); + if (tile == null) { + continue; + } + for (int i = 0; i < tile.getHeight(); i++ ) { + final int row = i; + log(Arrays.stream(tiles).map(t -> { + if (t == null) { + return " "; + } + return new String(t.getRow(row)); + }).collect(joining(" "))); + } + log(""); + } + for (final Tile[] tiles : placedTiles) { + final Tile tile = Arrays.stream(tiles) + .filter(Objects::nonNull) + .findFirst().orElse(null); + if (tile == null) { + continue; + } + log(Arrays.stream(tiles).map(t -> { + if (t == null) { + return " "; + } + return String.valueOf(t.id()); + }).collect(joining(" "))); + log(""); + } + } + + private Set getEdgesForMatching(final Tile tile) { + if (Arrays.stream(placedTiles) + .flatMap(Arrays::stream) + .anyMatch(tile::equals)) { + return tile.getAllEdges(); + } else { + return new HashSet<>(union(tile.getAllEdges(), tile.getAllEdgesReversed())); + } + } + + private boolean haveCommonEdge(final Tile tile1, final Tile tile2) { + return tile1.getAllEdges().stream() + .flatMap(edge1 -> getEdgesForMatching(tile2).stream() + .map(edge2 -> new char[][] { edge1, edge2 })) + .filter(p -> Arrays.equals(p[0], p[1])) + .count() == 1; + } + + public Set findCorners() { + return getTiles().stream() + .filter(tile1 -> getTiles().stream() + .filter(tile2 -> tile2.id() != tile1.id()) + .filter(tile2 -> haveCommonEdge(tile1, tile2)) + .count() == 2) + .collect(toSet()); + } + + public void puzzle(final Tile corner) { + log("Unplaced tiles: " + getTiles().size()); + log("Starting with " + corner.id()); + final Iterator allPermutations = corner.getAllPermutations(); + while (allPermutations.hasNext()) { + final Tile next = allPermutations.next(); + placeTile(next, 0, 0); + final Optional right = TileMatcher.findRightSideMatch(next, getTiles()); + final Optional bottom = TileMatcher.findBottomSideMatch(next, getTiles()); + if (right.isPresent() && bottom.isPresent()) { + log("Found corner orientation with right and bottom side match"); + placeTile(right.get(), 0, 1); + break; + } + } + assert placedTiles[0][0] != null && placedTiles[0][1] != null; + log("Unplaced tiles: " + getTiles().size()); + printPlacedTiles(); + Tile current = placedTiles[0][1]; + // first row + log("Continue first row"); + for (int i = 2; i < placedTiles[0].length; i++) { + current = placedTiles[0][i-1]; + final Optional right = TileMatcher.findRightSideMatch(current, getTiles()); + assert right.isPresent(); + placeTile(right.get(), 0, i); + printPlacedTiles(); + log("Unplaced tiles: " + getTiles().size()); + } + + // next rows + for (int j = 1; j < placedTiles.length; j++) { + log("Row " + (j + 1)); + for (int i = 0; i < placedTiles[j].length; i++) { + current = placedTiles[j-1][i]; + final Optional bottom = TileMatcher.findBottomSideMatch(current, getTiles()); + assert bottom.isPresent(); + placeTile(bottom.get(), j, i); + printPlacedTiles(); + log("Unplaced tiles: " + getTiles().size()); + } + } + } + + public void removeTileEdges() { + for (int i = 0; i < placedTiles.length; i++) { + final Tile[] tiles = placedTiles[i]; + for (int j = 0; j < tiles.length; j++) { + placedTiles[i][j] = tiles[j].getWithEdgesRemoved(); + } + } + } + + public List createImageGrid() { + final List grid = new ArrayList<>(); + for (final AoC2020_20.Tile[] tiles : placedTiles) { + for (int j = 0; j < tiles[0].grid.getHeight(); j++) { + final StringBuilder row = new StringBuilder(); + for (final Tile tile : tiles) { + row.append(tile.getRow(j)); + } + grid.add(row.toString()); + } + } + return grid; + } + } + + private static final class Math { + + public static long prod(final Collection numbers) { + return numbers.stream().map(Integer::longValue).reduce(1L, (a, b) -> a * b); + } + + public static int sqrt(final int number) { + return Double.valueOf(java.lang.Math.sqrt(number)).intValue(); + } + } + + static final class NessieFinder { + + private static final Pattern PATTERN2 = Pattern.compile(".\\#..\\#..\\#..\\#..\\#..\\#"); + private static final Pattern PATTERN1 = Pattern.compile("(?=\\#....\\#\\#....\\#\\#....\\#\\#\\#)"); + private static final char NESSIE_CHAR = '\u2592'; + + public static List findNessies(final CharGrid grid) { + final List nessies = new ArrayList<>(); + for (int i = 1; i < grid.getHeight(); i++) { + final Matcher m1 = PATTERN1.matcher(grid.getRowAsString(i)); + while (m1.find()) { + final int tail = m1.start(0); + final int row = i; + if (nessies.stream().filter(c -> c.getRow() == row) + .anyMatch(c -> c.getCol() > tail - 20)) { + continue; + } + if ("#".equals(grid.getRowAsString(i - 1).substring(tail + 18, tail + 19))) { + final Matcher m2 = PATTERN2 + .matcher(grid.getRowAsString(i + 1).substring(tail)); + if (m2.find()) { + nessies.add(Cell.at(i, tail)); + } + } + } + } + return nessies; + } + + public static CharGrid markNessies(final List nessies, final CharGrid gridIn) { + final List grid = gridIn.getRowsAsStringList(); + for (final Cell nessie : nessies) { + final int idx = nessie.getCol(); + final char[] chars0 = grid.get(nessie.getRow() - 1).toCharArray(); + chars0[idx+18] = NESSIE_CHAR; + grid.set(nessie.getRow() - 1, new String(chars0)); + final char[] chars1 = grid.get(nessie.getRow()).toCharArray(); + chars1[idx] = NESSIE_CHAR; + chars1[idx+5] = NESSIE_CHAR; + chars1[idx+6] = NESSIE_CHAR; + chars1[idx+11] = NESSIE_CHAR; + chars1[idx+12] = NESSIE_CHAR; + chars1[idx+17] = NESSIE_CHAR; + chars1[idx+18] = NESSIE_CHAR; + chars1[idx+19] = NESSIE_CHAR; + grid.set(nessie.getRow(), new String(chars1)); + final char[] chars2 = grid.get(nessie.getRow() + 1).toCharArray(); + chars2[idx+1] = NESSIE_CHAR; + chars2[idx+4] = NESSIE_CHAR; + chars2[idx+7] = NESSIE_CHAR; + chars2[idx+10] = NESSIE_CHAR; + chars2[idx+13] = NESSIE_CHAR; + chars2[idx+16] = NESSIE_CHAR; + grid.set(nessie.getRow() + 1, new String(chars2)); + } + for (int j = 0; j < grid.size(); j++) { + final char[] chars = grid.get(j).toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '#') { + chars[i] = '~'; + } else if (chars[i] == '.') { + chars[i] = '_'; + } + } + grid.set(j, new String(chars)); + } + return CharGrid.from(grid); + } + } + + static final class TileMatcher { + + private static Predicate rightSide(final Tile tile) { + return t -> Arrays.equals(tile.getRightEdge(), t.getLeftEdge()); + } + + private static Predicate bottomSide(final Tile tile) { + return t -> Arrays.equals(tile.getBottomEdge(), t.getTopEdge()); + } + + public static Optional findRightSideMatch(final Tile tile, final Set tiles) { + return findMatch(tile, tiles, rightSide(tile)); + } + + public static Optional findBottomSideMatch(final Tile tile, final Set tiles) { + return findMatch(tile, tiles, bottomSide(tile)); + } + + private static Optional findMatch(final Tile tile, final Set tiles, final Predicate matcher) { + return tiles.stream() + .flatMap(t -> Utils.stream(t.getAllPermutations())) + .filter(matcher) + .findAny(); + } + } +} diff --git a/src/main/java/AoC2020_22.java b/src/main/java/AoC2020_22.java index 0e4fb847..0aa5021f 100644 --- a/src/main/java/AoC2020_22.java +++ b/src/main/java/AoC2020_22.java @@ -1,223 +1,192 @@ import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toCollection; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Set; import com.github.pareronia.aoc.MutableInt; +import com.github.pareronia.aoc.StringOps; import com.github.pareronia.aoc.StringUtils; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Logger; +import com.github.pareronia.aoc.solution.LoggerEnabled; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2020_22 extends AoCBase { - - private final List inputs; - - private AoC2020_22(final List input, final boolean debug) { - super(debug); - this.inputs = input; - } - - public static final AoC2020_22 create(final List input) { - return new AoC2020_22(input, false); - } +public class AoC2020_22 + extends SolutionBase, Integer, Integer> { + + private AoC2020_22(final boolean debug) { + super(debug); + } + + public static final AoC2020_22 create() { + return new AoC2020_22(false); + } - public static final AoC2020_22 createDebug(final List input) { - return new AoC2020_22(input, true); - } - - private Players parse() { - final List> blocks = new ArrayList<>(); - int i = 0; - blocks.add(new ArrayList()); - for (final String input: inputs) { - if (input.isEmpty()) { - blocks.add(new ArrayList()); - i++; - } else { - blocks.get(i).add(input); - } - } - assert blocks.size() == 2; - - final List pl1 = new ArrayList<>(); - final List pl2 = new ArrayList<>(); - - for (final String string : blocks.get(0)) { - if (string.startsWith("Player")) { - continue; - } - pl1.add(Integer.valueOf(string)); - } - for (final String string : blocks.get(1)) { - if (string.startsWith("Player")) { - continue; - } - pl2.add(Integer.valueOf(string)); - } - - return new Players(pl1, pl2); - } + public static final AoC2020_22 createDebug() { + return new AoC2020_22(true); + } + + @Override + protected List parseInput(final List inputs) { + return inputs; + } + + @Override + public Integer solvePart1(final List inputs) { + final CrabCombat combat = CrabCombat.fromInput(inputs, this.logger); + combat.playRegular(); + return combat.getScore(); + } + + @Override + public Integer solvePart2(final List inputs) { + final CrabCombat combat = CrabCombat.fromInput(inputs, this.logger); + combat.playRecursive(); + return combat.getScore(); + } + + @Samples({ + @Sample(method = "part1", input = TEST, expected = "306"), + @Sample(method = "part2", input = TEST, expected = "291"), + @Sample(method = "part2", input = LOOP, expected = "105"), + }) + public static void main(final String[] args) throws Exception { + AoC2020_22.create().run(); + } + + private static final String TEST = """ + Player 1: + 9 + 2 + 6 + 3 + 1 + + Player 2: + 5 + 8 + 4 + 7 + 10 + """; + + private static final String LOOP = """ + Player 1: + 43 + 19 + + Player 2r + 2 + 29 + 14 + """; + + record CrabCombat( + List player1, + List player2, + Logger logger + ) implements LoggerEnabled { + + public static CrabCombat fromInput( + final List inputs, final Logger logger + ) { + final List> blocks = StringOps.toBlocks(inputs); + final List player1 = new ArrayList<>(); + final List player2 = new ArrayList<>(); + blocks.get(0).stream().skip(1) + .map(Integer::valueOf) + .collect(toCollection(() -> player1)); + blocks.get(1).stream().skip(1) + .map(Integer::valueOf) + .collect(toCollection(() -> player2)); + return new CrabCombat(player1, player2, logger); + } - private void playRegularCombat(final Players players) { - playCombat(players, new MutableInt(), false); - } - - private void playRecursiveCombat(final Players players) { - playCombat(players, new MutableInt(), true); - } - - private void playCombat(final Players players, final MutableInt _game, final boolean recursive) { - _game.increment(); - final int game = _game.intValue(); - log(() ->String.format("=== Game %d ===", game)); - final Set seen = new HashSet<>(); - final MutableInt rnd = new MutableInt(1); - final List pl1 = players.player1; - final List pl2 = players.player2; - while (!pl1.isEmpty() && !pl2.isEmpty()) { - log(() ->""); - log(() ->String.format("-- Round %d (Game %d) --", rnd.intValue(), game)); - log(() ->String.format("Player 1's deck: %s", StringUtils.join(pl1, ", "))); - log(() ->String.format("Player 2's deck: %s", StringUtils.join(pl2, ", "))); - final Round round = new Round(pl1, pl2); - if (recursive && seen.contains(round)) { - pl2.clear(); - break; - } - seen.add(round); - final Integer n1 = pl1.remove(0); - log(() ->String.format("Player 1 plays: %d", n1)); - final Integer n2 = pl2.remove(0); - log(() ->String.format("Player 2 plays: %d", n2)); - final Integer winner; - if (recursive && pl1.size() >= n1 && pl2.size() >= n2) { - log(() ->"Playing a sub-game to determine the winner..."); - log(() ->""); - final List pl1_sub = new ArrayList<>(pl1.subList(0, n1)); - final List pl2_sub = new ArrayList<>(pl2.subList(0, n2)); - playCombat(new Players(pl1_sub, pl2_sub), _game, true); - log(() ->""); - log(() ->String.format("...anyway, back to game %d.", game)); - winner = pl2_sub.isEmpty() ? 1 : 2; - } else { - winner = n1 > n2 ? 1 : 2; - } - if (winner == 1) { - pl1.addAll(asList(n1, n2)); - } else { - pl2.addAll(asList(n2, n1)); - } - log(() ->String.format("Player %d wins round %d of game %d!", winner, rnd.intValue(), game)); - rnd.increment(); - } - final Integer winner = pl2.isEmpty() ? 1 : 2; - log(() ->String.format("The winner of game %d is player %d!", game, winner)); - } - - private int getScore(final Players players) { - log(""); - log(""); - log("== Post-game results =="); - log(String.format("Player 1's deck: %s", StringUtils.join(players.player1, ", "))); - log(String.format("Player 2's deck: %s", StringUtils.join(players.player2, ", "))); - log(""); - log(""); - final List winner = players.player2.isEmpty() ? players.player1 : players.player2; - int total = 0; - for (int i = 0; i < winner.size(); i++) { - total += (winner.size() - i) * winner.get(i); - } - return total; - } - - @Override - public Integer solvePart1() { - final Players players = parse(); - playRegularCombat(players); - return getScore(players); - } - - @Override - public Integer solvePart2() { - final Players players = parse(); - playRecursiveCombat(players); - return getScore(players); - } - - public static void main(final String[] args) throws Exception { - assert AoC2020_22.createDebug(splitLines(TEST)).solvePart1() == 306; - assert AoC2020_22.createDebug(splitLines(TEST)).solvePart2() == 291; - assert AoC2020_22.createDebug(splitLines(LOOP)).solvePart2() == 105; - - final Puzzle puzzle = Puzzle.create(2022, 22); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2022_22.create(input)::solvePart1), - () -> lap("Part 2", AoC2022_22.create(input)::solvePart2) - ); - } - - private static final String TEST = - "Player 1:\r\n" + - "9\r\n" + - "2\r\n" + - "6\r\n" + - "3\r\n" + - "1\r\n" + - "\r\n" + - "Player 2:\r\n" + - "5\r\n" + - "8\r\n" + - "4\r\n" + - "7\r\n" + - "10"; - - private static final String LOOP = - "Player 1:\r\n" + - "43\r\n" + - "19\r\n" + - "\r\n" + - "Player 2:\r\n" + - "2\r\n" + - "29\r\n" + - "14"; - - private static final class Players { - private final List player1; - private final List player2; - - public Players(final List player1, final List player2) { - this.player1 = player1; - this.player2 = player2; - } - } - - private static final class Round { - private final List pl1; - private final List pl2; - - public Round(final List pl1, final List pl2) { - this.pl1 = new ArrayList<>(pl1); - this.pl2 = new ArrayList<>(pl2); - } + @Override + public Logger getLogger() { + return logger; + } - @Override - public int hashCode() { - return Objects.hash(pl1, pl2); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Round)) { - return false; - } - final Round other = (Round) obj; - return Objects.equals(pl1, other.pl1) && Objects.equals(pl2, other.pl2); - } - } + public void playRegular() { + play(this, new MutableInt(), false); + } + + public void playRecursive() { + play(this, new MutableInt(), true); + } + + private void play( + final CrabCombat combat, + final MutableInt _game, + final boolean recursive + ) { + _game.increment(); + final int game = _game.intValue(); + log(() -> String.format("=== Game %d ===", game)); + final Set seen = new HashSet<>(); + final MutableInt rnd = new MutableInt(1); + final List pl1 = combat.player1; + final List pl2 = combat.player2; + while (!pl1.isEmpty() && !pl2.isEmpty()) { + log(() -> ""); + log(() -> String.format("-- Round %d (Game %d) --", rnd.intValue(), game)); + log(() -> String.format("Player 1's deck: %s", StringUtils.join(pl1, ", "))); + log(() -> String.format("Player 2's deck: %s", StringUtils.join(pl2, ", "))); + final Round round = new Round(new ArrayList<>(pl1), new ArrayList<>(pl2)); + if (recursive && seen.contains(round)) { + pl2.clear(); + break; + } + seen.add(round); + final Integer n1 = pl1.remove(0); + log(() -> String.format("Player 1 plays: %d", n1)); + final Integer n2 = pl2.remove(0); + log(() -> String.format("Player 2 plays: %d", n2)); + final Integer winner; + if (recursive && pl1.size() >= n1 && pl2.size() >= n2) { + log(() -> "Playing a sub-game to determine the winner..."); + log(() -> ""); + final List pl1_sub = new ArrayList<>(pl1.subList(0, n1)); + final List pl2_sub = new ArrayList<>(pl2.subList(0, n2)); + play(new CrabCombat(pl1_sub, pl2_sub, logger), _game, true); + log(() -> ""); + log(() -> String.format("...anyway, back to game %d.", game)); + winner = pl2_sub.isEmpty() ? 1 : 2; + } else { + winner = n1 > n2 ? 1 : 2; + } + if (winner == 1) { + pl1.addAll(asList(n1, n2)); + } else { + pl2.addAll(asList(n2, n1)); + } + log(() -> String.format("Player %d wins round %d of game %d!", winner, rnd.intValue(), game)); + rnd.increment(); + } + final Integer winner = pl2.isEmpty() ? 1 : 2; + log(() -> String.format("The winner of game %d is player %d!", game, winner)); + } + + public int getScore() { + log(""); + log(""); + log("== Post-game results =="); + log(String.format("Player 1's deck: %s", StringUtils.join(player1, ", "))); + log(String.format("Player 2's deck: %s", StringUtils.join(player2, ", "))); + log(""); + log(""); + final List winner = player2.isEmpty() ? player1 : player2; + int total = 0; + for (int i = 0; i < winner.size(); i++) { + total += (winner.size() - i) * winner.get(i); + } + return total; + } + + record Round(List pl1, List pl2) {} + } } diff --git a/src/main/java/AoC2020_23.java b/src/main/java/AoC2020_23.java index 40adf572..c420950b 100644 --- a/src/main/java/AoC2020_23.java +++ b/src/main/java/AoC2020_23.java @@ -1,146 +1,111 @@ -import static java.util.Arrays.asList; +import static com.github.pareronia.aoc.IntegerSequence.Range.range; +import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toList; -import java.util.HashMap; -import java.util.IntSummaryStatistics; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.Map; +import java.util.Set; +import java.util.stream.IntStream; import java.util.stream.Stream; -import com.github.pareronia.aoc.Utils; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -public class AoC2020_23 extends AoCBase { - - private final List labels; +public class AoC2020_23 extends SolutionBase, String, Long> { - private AoC2020_23(final List input, final boolean debug) { + private AoC2020_23(final boolean debug) { super(debug); - assert input.size() == 1; - this.labels = Utils.asCharacterStream(input.get(0)) - .map(c -> new String(new char[] { c.charValue() })) - .map(Integer::valueOf) - .collect(toList()); } - public static final AoC2020_23 create(final List input) { - return new AoC2020_23(input, false); + public static final AoC2020_23 create() { + return new AoC2020_23(false); } - public static final AoC2020_23 createDebug(final List input) { - return new AoC2020_23(input, true); - } - - private Map prepareCups() { - final Map cups = new HashMap<>(); - final Integer first = this.labels.get(0); - final Integer last = this.labels.get(this.labels.size() - 1); - final Cup tail = new Cup(last, (Cup) null); - Cup prev = tail; - Cup cup; - for (int i = this.labels.size() - 2; i > 0; i--) { - final Integer label = this.labels.get(i); - cup = new Cup(label, prev); - cups.put(label, cup); - prev = cup; - } - final Cup head = new Cup(first, prev); - cups.put(first, head); - tail.setNext(head); - cups.put(last, tail); - return cups; + public static final AoC2020_23 createDebug() { + return new AoC2020_23(true); } - private Cup doMove(final Map cups, final Cup current, final Integer size, final Integer min, final Integer max) { - final Cup p1 = current.getNext(); - final Cup p2 = p1.getNext(); - final Cup p3 = p2.getNext(); - current.setNext(p3.getNext()); - final List pickup = asList(p1.getLabel(), p2.getLabel(), p3.getLabel()); - Integer d = current.getLabel() - 1; - if (d < min) { - d = max; - } + @Override + protected List parseInput(final List inputs) { + final Integer[] digits + = StringOps.getDigits(inputs.get(0), inputs.get(0).length()); + return Arrays.stream(digits).toList(); + } + + private int[] prepareCups(final List labels) { + final int[] cups = new int[labels.size() + 1]; + range(labels.size()).forEach(i -> { + cups[labels.get(i)] = labels.get((i + 1) % labels.size()); + }); + return cups; + } + + private int doMove( + final int[] cups, + final int current, + final int min, + final int max + ) { + final int c = current; + final int p1 = cups[c]; + final int p2 = cups[p1]; + final int p3 = cups[p2]; + cups[c] = cups[p3]; + final Set pickup = Set.of(p1, p2, p3); + int d = c - 1; + if (d < min) { + d = max; + } while (pickup.contains(d)) { d--; if (d < min) { d = max; } } - final Cup destination = cups.get(d); - p3.setNext(destination.getNext()); - destination.setNext(p1); - return current.getNext(); - } - + cups[p3] = cups[d]; + cups[d] = p1; + return cups[c]; + } + @Override - public Long solvePart1() { - final Map cups = prepareCups(); - final int size = this.labels.size(); - final IntSummaryStatistics stats - = this.labels.stream().mapToInt(Integer::valueOf).summaryStatistics(); - final Integer min = stats.getMin(); - final Integer max = stats.getMax(); - Cup current = cups.get(this.labels.get(0)); + public String solvePart1(final List labels) { + final int[] cups = prepareCups(labels); + int current = labels.get(0); for (int i = 0; i < 100; i++) { - current = doMove(cups, current, size, min, max); + current = doMove(cups, current, 1, 9); } - Cup cup = cups.get(1); - final StringBuilder result = new StringBuilder(); - while (cup.getNext().getLabel() != 1) { - result.append(cup.getNext().getLabel()); - cup = cup.getNext(); - } - return Long.valueOf(result.toString()); + return IntStream.iterate(cups[1], cup -> cups[cup]) + .takeWhile(cup -> cup != 1) + .mapToObj(String::valueOf) + .collect(joining()); } @Override - public Long solvePart2() { - final IntSummaryStatistics stats - = this.labels.stream().mapToInt(Integer::valueOf).summaryStatistics(); - final Integer min = stats.getMin(); - final Integer max = stats.getMax(); - Stream.iterate(max + 1, i -> i + 1).limit(1_000_000 - this.labels.size()) - .collect(toCollection(() -> this.labels)); - final Map cups = prepareCups(); - Cup current = cups.get(this.labels.get(0)); + public Long solvePart2(final List labels) { + final List newLabels = new ArrayList<>(1_000_000); + newLabels.addAll(labels); + Stream.iterate(10, i -> i + 1).limit(1_000_000 - labels.size()) + .collect(toCollection(() -> newLabels)); + final int[] cups = prepareCups(newLabels); + int current = labels.get(0); for (int i = 0; i < 10_000_000; i++) { - current = doMove(cups, current, 1_000_000, min, 1_000_000); + current = doMove(cups, current, 1, 1_000_000); } - final Cup one = cups.get(1); - final long star1 = one.getNext().getLabel().longValue(); - final long star2 = one.getNext().getNext().getLabel().longValue(); + final long star1 = cups[1]; + final long star2 = cups[cups[1]]; return star1 * star2; } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "67384529"), + @Sample(method = "part2", input = TEST, expected = "149245887792"), + }) public static void main(final String[] args) throws Exception { - assert AoC2020_23.createDebug(TEST).solvePart1() == 67384529; - assert AoC2020_23.createDebug(TEST).solvePart2() == 149245887792L; - - final List input = Aocd.getData(2020, 23); - lap("Part 1", () -> AoC2020_23.create(input).solvePart1()); - lap("Part 2", () -> AoC2020_23.create(input).solvePart2()); + AoC2020_23.create().run(); } - private static final List TEST = splitLines( - "389125467" - ); - - - @AllArgsConstructor - @Getter - @Setter - @EqualsAndHashCode - @ToString - private static final class Cup { - private final Integer label; - @ToString.Exclude private Cup next; - } + private static final String TEST = "389125467"; } diff --git a/src/main/java/AoC2020_24.java b/src/main/java/AoC2020_24.java index 45b5672e..19b06d80 100644 --- a/src/main/java/AoC2020_24.java +++ b/src/main/java/AoC2020_24.java @@ -1,6 +1,6 @@ -import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.counting; +import static java.util.stream.Collectors.groupingBy; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -9,13 +9,12 @@ import java.util.regex.Pattern; import com.github.pareronia.aoc.game_of_life.GameOfLife; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.EqualsAndHashCode; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -public class AoC2020_24 extends AoCBase { +public class AoC2020_24 + extends SolutionBase { private static final Map DIRS = Map.of( "ne", new Direction(1, -1), @@ -26,118 +25,109 @@ public class AoC2020_24 extends AoCBase { "w", new Direction(-1, 0) ); - private final List input; - - private AoC2020_24(final List input, final boolean debug) { + private AoC2020_24(final boolean debug) { super(debug); - this.input = input; } - public static final AoC2020_24 create(final List input) { - return new AoC2020_24(input, false); + public static final AoC2020_24 create() { + return new AoC2020_24(false); } - public static final AoC2020_24 createDebug(final List input) { - return new AoC2020_24(input, true); - } - - private Set buildFloor() { - final Pattern pattern = Pattern.compile("(n?(e|w)|s?(e|w))"); - final Set tiles = new HashSet<>(); - for (final String line : this.input) { - final List directions = pattern.matcher(line).results() - .map(MatchResult::group) - .map(DIRS::get) - .collect(toList()); - Tile tile = Tile.at(0, 0); - for (final Direction direction : directions) { - tile = Tile.at(tile.q + direction.q, tile.r + direction.r); - } - if (tiles.contains(tile)) { - tiles.remove(tile); - } else { - tiles.add(tile); - } - } - return tiles; + public static final AoC2020_24 createDebug() { + return new AoC2020_24(true); } @Override - public Integer solvePart1() { - return buildFloor().size(); + protected AoC2020_24.Floor parseInput(final List inputs) { + return Floor.fromInput(inputs); + } + + @Override + public Integer solvePart1(final Floor floor) { + return floor.tiles.size(); } @Override - public Integer solvePart2() { - GameOfLife gol = new GameOfLife<>(new HexGrid(), new Rules(), buildFloor()); + public Integer solvePart2(final Floor floor) { + GameOfLife gol + = new GameOfLife<>(new HexGrid(), new Rules(), floor.tiles); for (int i = 0; i < 100; i++) { gol = gol.nextGeneration(); } - return gol.getAlive().size(); + return gol.alive().size(); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "10"), + @Sample(method = "part2", input = TEST, expected = "2208"), + }) public static void main(final String[] args) throws Exception { - assert AoC2020_24.createDebug(TEST).solvePart1() == 10; - assert AoC2020_24.createDebug(TEST).solvePart2() == 2208; - - final Puzzle puzzle = Puzzle.create(2020, 24); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2020_24.create(input)::solvePart1), - () -> lap("Part 2", AoC2020_24.create(input)::solvePart2) - ); + AoC2020_24.create().run(); } - private static final List TEST = splitLines( - "sesenwnenenewseeswwswswwnenewsewsw\r\n" + - "neeenesenwnwwswnenewnwwsewnenwseswesw\r\n" + - "seswneswswsenwwnwse\r\n" + - "nwnwneseeswswnenewneswwnewseswneseene\r\n" + - "swweswneswnenwsewnwneneseenw\r\n" + - "eesenwseswswnenwswnwnwsewwnwsene\r\n" + - "sewnenenenesenwsewnenwwwse\r\n" + - "wenwwweseeeweswwwnwwe\r\n" + - "wsweesenenewnwwnwsenewsenwwsesesenwne\r\n" + - "neeswseenwwswnwswswnw\r\n" + - "nenwswwsewswnenenewsenwsenwnesesenew\r\n" + - "enewnwewneswsewnwswenweswnenwsenwsw\r\n" + - "sweneswneswneneenwnewenewwneswswnese\r\n" + - "swwesenesewenwneswnwwneseswwne\r\n" + - "enesenwswwswneneswsenwnewswseenwsese\r\n" + - "wnwnesenesenenwwnenwsewesewsesesew\r\n" + - "nenewswnwewswnenesenwnesewesw\r\n" + - "eneswnwswnwsenenwnwnwwseeswneewsenese\r\n" + - "neswnwewnwnwseenwseesewsenwsweewe\r\n" + - "wseweeenwnesenwwwswnew" - ); + private static final String TEST = """ + sesenwnenenewseeswwswswwnenewsewsw + neeenesenwnwwswnenewnwwsewnenwseswesw + seswneswswsenwwnwse + nwnwneseeswswnenewneswwnewseswneseene + swweswneswnenwsewnwneneseenw + eesenwseswswnenwswnwnwsewwnwsene + sewnenenenesenwsewnenwwwse + wenwwweseeeweswwwnwwe + wsweesenenewnwwnwsenewsenwwsesesenwne + neeswseenwwswnwswswnw + nenwswwsewswnenenewsenwsenwnesesenew + enewnwewneswsewnwswenweswnenwsenwsw + sweneswneswneneenwnewenewwneswswnese + swwesenesewenwneswnwwneseswwne + enesenwswwswneneswsenwnewswseenwsese + wnwnesenesenenwwnenwsewesewsesesew + nenewswnwewswnenesenwnesewesw + eneswnwswnwsenenwnwnwwseeswneewsenese + neswnwewnwnwseenwseesewsenwsweewe + wseweeenwnesenwwwswn + """; - @RequiredArgsConstructor(staticName = "at") - @EqualsAndHashCode - @ToString - private static final class Tile { - private final int q; - private final int r; + record Tile(int q, int r) { + + public static Tile at(final int q, final int r) { + return new Tile(q, r); + } + + public Tile at(final Direction direction) { + return Tile.at(this.q + direction.q, this.r + direction.r); + } } - @RequiredArgsConstructor - @ToString - private static final class Direction { - private final int q; - private final int r; + record Direction(int q, int r) {} + + record Floor(Set tiles) { + + public static Floor fromInput(final List input) { + final Pattern pattern = Pattern.compile("(n?(e|w)|s?(e|w))"); + final Set tiles = new HashSet<>(); + for (final String line : input) { + final Tile tile = pattern.matcher(line).results() + .map(MatchResult::group) + .map(DIRS::get) + .reduce(Tile.at(0, 0), Tile::at, (t1, t2) -> t2); + if (tiles.contains(tile)) { + tiles.remove(tile); + } else { + tiles.add(tile); + } + } + return new Floor(tiles); + } } private static final class HexGrid implements GameOfLife.Type { @Override public Map getNeighbourCounts(final Set alive) { - final Map neighbourCounts = new HashMap<>(); - for (final Tile tile : alive) { - for (final Direction d : DIRS.values()) { - final Tile n = Tile.at(tile.q + d.q, tile.r + d.r); - neighbourCounts.merge(n, 1L, Long::sum); - } - } - return neighbourCounts; + return alive.stream() + .flatMap(tile -> DIRS.values().stream().map(tile::at)) + .collect(groupingBy(tile -> tile, counting())); } } diff --git a/src/main/java/AoC2020_25.java b/src/main/java/AoC2020_25.java index f7863f51..23ee88de 100644 --- a/src/main/java/AoC2020_25.java +++ b/src/main/java/AoC2020_25.java @@ -1,24 +1,24 @@ import java.util.List; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2020_25 extends AoCBase { +public class AoC2020_25 + extends SolutionBase { private static final int MOD = 20201227; - private final List input; - - private AoC2020_25(final List input, final boolean debug) { + private AoC2020_25(final boolean debug) { super(debug); - this.input = input; } - public static AoC2020_25 create(final List input) { - return new AoC2020_25(input, false); + public static AoC2020_25 create() { + return new AoC2020_25(false); } - public static AoC2020_25 createDebug(final List input) { - return new AoC2020_25(input, true); + public static AoC2020_25 createDebug() { + return new AoC2020_25(true); } private long findLoopSize(final long key) { @@ -40,32 +40,34 @@ private long findEncryptionKey(final long pubKey, final long loopSize) { } @Override - public Long solvePart1() { - assert this.input.size() == 2; - final long pubKey1 = Integer.parseInt(this.input.get(0)); - final long pubKey2 = Integer.parseInt(this.input.get(1)); - final long loopSize2 = findLoopSize(pubKey2); - return findEncryptionKey(pubKey1, loopSize2); + protected PublicKeys parseInput(final List inputs) { + return new PublicKeys( + Long.parseLong(inputs.get(0)), + Long.parseLong(inputs.get(1))); + } + + @Override + public Long solvePart1(final PublicKeys keys) { + final long loopSize2 = findLoopSize(keys.key2); + return findEncryptionKey(keys.key1, loopSize2); } @Override - public Integer solvePart2() { - return 0; + public String solvePart2(final PublicKeys keys) { + return "🎄"; } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "14897079") + }) public static void main(final String[] args) throws Exception { - assert createDebug(TEST).solvePart1() == 14897079; - - final Puzzle puzzle = puzzle(AoC2020_25.class); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", create(input)::solvePart1), - () -> lap("Part 2", create(input)::solvePart2) - ); + AoC2020_25.create().run(); } - private static final List TEST = splitLines( - "5764801\r\n" + - "17807724" - ); + private static final String TEST = """ + 5764801 + 17807724 + """; + + record PublicKeys(long key1, long key2) {} } \ No newline at end of file diff --git a/src/main/java/AoC2021_01.java b/src/main/java/AoC2021_01.java index 9a02b565..495a6599 100644 --- a/src/main/java/AoC2021_01.java +++ b/src/main/java/AoC2021_01.java @@ -1,61 +1,63 @@ -import static java.util.stream.Collectors.toList; - import java.util.List; import java.util.stream.IntStream; -import com.github.pareronia.aocd.Aocd; - -public class AoC2021_01 extends AoCBase { - - private final List depths; - - private AoC2021_01(final List input, final boolean debug) { - super(debug); - this.depths = input.stream().map(Integer::valueOf).collect(toList()); - } - - public static final AoC2021_01 create(final List input) { - return new AoC2021_01(input, false); - } - - public static final AoC2021_01 createDebug(final List input) { - return new AoC2021_01(input, true); - } - - private long countIncreases(final int window) { - return IntStream.range(window, this.depths.size()) - .filter(i -> this.depths.get(i) > this.depths.get(i - window)) - .count(); - } - - @Override - public Long solvePart1() { - return countIncreases(1); - } - - @Override - public Long solvePart2() { - return countIncreases(3); - } - - public static void main(final String[] args) throws Exception { - assert AoC2021_01.createDebug(TEST).solvePart1() == 7; - assert AoC2021_01.createDebug(TEST).solvePart2() == 5; - - final List input = Aocd.getData(2021, 1); - lap("Part 1", () -> AoC2021_01.create(input).solvePart1()); - lap("Part 2", () -> AoC2021_01.create(input).solvePart2()); - } - - private static final List TEST = splitLines( - "199\r\n" + - "200\r\n" + - "208\r\n" + - "210\r\n" + - "200\r\n" + - "207\r\n" + - "240\r\n" + - "269\r\n" + - "260\r\n" + - "263" ); +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public class AoC2021_01 extends SolutionBase, Long, Long> { + + private AoC2021_01(final boolean debug) { + super(debug); + } + + public static final AoC2021_01 create() { + return new AoC2021_01(false); + } + + public static final AoC2021_01 createDebug() { + return new AoC2021_01(true); + } + + @Override + protected List parseInput(final List inputs) { + return inputs.stream().map(Integer::valueOf).toList(); + } + + private long countIncreases(final List depths, final int window) { + return IntStream.range(window, depths.size()) + .filter(i -> depths.get(i) > depths.get(i - window)) + .count(); + } + + @Override + public Long solvePart1(final List depths) { + return countIncreases(depths, 1); + } + + @Override + public Long solvePart2(final List depths) { + return countIncreases(depths, 3); + } + + @Samples({ + @Sample(method = "part1", input = TEST, expected = "7"), + @Sample(method = "part2", input = TEST, expected = "5"), + }) + public static void main(final String[] args) throws Exception { + AoC2021_01.create().run(); + } + + private static final String TEST = """ + 199 + 200 + 208 + 210 + 200 + 207 + 240 + 269 + 260 + 263 + """; } diff --git a/src/main/java/AoC2021_02.java b/src/main/java/AoC2021_02.java index 564d6076..8b2ef002 100644 --- a/src/main/java/AoC2021_02.java +++ b/src/main/java/AoC2021_02.java @@ -6,10 +6,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - public class AoC2021_02 extends AoCBase { // private static final String FORWARD = "forward"; @@ -40,12 +36,12 @@ public static final AoC2021_02 createDebug(final List input) { public Long solvePart1() { long hor = 0, ver = 0; for (final Command command : this.commands) { - if (UP.equals(command.getDirection())) { - ver -= command.getAmount(); - } else if (DOWN.equals(command.getDirection())) { - ver += command.getAmount(); + if (UP.equals(command.direction())) { + ver -= command.amount(); + } else if (DOWN.equals(command.direction())) { + ver += command.amount(); } else { - hor += command.getAmount(); + hor += command.amount(); } } return hor * ver; @@ -55,13 +51,13 @@ public Long solvePart1() { public Long solvePart2() { long hor = 0, ver = 0, aim = 0; for (final Command command : this.commands) { - if (UP.equals(command.getDirection())) { - aim -= command.getAmount(); - } else if (DOWN.equals(command.getDirection())) { - aim += command.getAmount(); + if (UP.equals(command.direction())) { + aim -= command.amount(); + } else if (DOWN.equals(command.direction())) { + aim += command.amount(); } else { - hor += command.getAmount(); - ver += (aim * command.getAmount()); + hor += command.amount(); + ver += (aim * command.amount()); } } return hor * ver; @@ -88,14 +84,10 @@ public static void main(final String[] args) throws Exception { "forward 2" ); - @RequiredArgsConstructor(access = AccessLevel.PRIVATE) - @Getter - private static final class Command { - private final String direction; - private final int amount; + record Command(String direction, int amount) { public static Command create(final String direction, final String amount) { - return new Command(direction, Integer.valueOf(amount)); + return new Command(direction, Integer.parseInt(amount)); } } } diff --git a/src/main/java/AoC2021_03.java b/src/main/java/AoC2021_03.java index 52dd3d27..1e541ea2 100644 --- a/src/main/java/AoC2021_03.java +++ b/src/main/java/AoC2021_03.java @@ -1,107 +1,106 @@ -import static java.util.stream.Collectors.toList; - import java.util.List; import java.util.function.Function; -import com.github.pareronia.aocd.Aocd; - -import lombok.RequiredArgsConstructor; - -public class AoC2021_03 extends AoCBase { - - private final List lines; - - private AoC2021_03(final List input, final boolean debug) { - super(debug); - this.lines = input; - } - - public static final AoC2021_03 create(final List input) { - return new AoC2021_03(input, false); - } - - public static final AoC2021_03 createDebug(final List input) { - return new AoC2021_03(input, true); - } - - private BitCount bitCounts(final List inputs, final int pos) { - final int zeroes = (int) inputs.stream() - .filter(s -> s.charAt(pos) == '0') - .count(); - return new BitCount(inputs.size() - zeroes, zeroes); - } - - private int ans(final String value1, final String value2) { - return Integer.parseInt(value1, 2) * Integer.parseInt(value2, 2); - } - - @Override - public Integer solvePart1() { - final StringBuilder gamma = new StringBuilder(); - final StringBuilder epsilon = new StringBuilder(); - for (int i = 0; i < this.lines.get(0).length(); i++) { - final BitCount bitCounts = bitCounts(this.lines, i); - gamma.append(bitCounts.mostCommon()); - epsilon.append(bitCounts.leastCommon()); - } - return ans(gamma.toString(), epsilon.toString()); - } - - private String reduce(List strings, final Function keep) { - int pos = 0; - while (strings.size() > 1) { - final BitCount bitCounts = bitCounts(strings, pos); - final int fpos = pos++; - final char toKeep = keep.apply(bitCounts); - strings = strings.stream() - .filter(s -> s.charAt(fpos) == toKeep) - .collect(toList()); - } - return strings.get(0); - } - - @Override - public Integer solvePart2() { - final String o2 = reduce(List.copyOf(this.lines), BitCount::mostCommon); - final String co2 = reduce(List.copyOf(this.lines), BitCount::leastCommon); - return ans(o2, co2); - } - - public static void main(final String[] args) throws Exception { - assert AoC2021_03.createDebug(TEST).solvePart1() == 198; - assert AoC2021_03.createDebug(TEST).solvePart2() == 230; - - final List input = Aocd.getData(2021, 3); - lap("Part 1", () -> AoC2021_03.create(input).solvePart1()); - lap("Part 2", () -> AoC2021_03.create(input).solvePart2()); - } - - private static final List TEST = splitLines( - "00100\r\n" + - "11110\r\n" + - "10110\r\n" + - "10111\r\n" + - "10101\r\n" + - "01111\r\n" + - "00111\r\n" + - "11100\r\n" + - "10000\r\n" + - "11001\r\n" + - "00010\r\n" + - "01010" - ); - - @RequiredArgsConstructor - private static final class BitCount { - private final int ones; - private final int zeroes; - - public char mostCommon() { - return ones >= zeroes ? '1' : '0'; - } - - public char leastCommon() { - return ones < zeroes ? '1' : '0'; - } - } +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public class AoC2021_03 extends SolutionBase, Integer, Integer> { + + private AoC2021_03(final boolean debug) { + super(debug); + } + + public static final AoC2021_03 create() { + return new AoC2021_03(false); + } + + public static final AoC2021_03 createDebug() { + return new AoC2021_03(true); + } + + @Override + protected List parseInput(final List inputs) { + return inputs; + } + + private int ans(final String value1, final String value2) { + return Integer.parseInt(value1, 2) * Integer.parseInt(value2, 2); + } + + @Override + public Integer solvePart1(final List lines) { + final StringBuilder gamma = new StringBuilder(); + final StringBuilder epsilon = new StringBuilder(); + for (int i = 0; i < lines.get(0).length(); i++) { + final BitCount bitCounts = BitCount.atPos(lines, i); + gamma.append(bitCounts.mostCommon()); + epsilon.append(bitCounts.leastCommon()); + } + return ans(gamma.toString(), epsilon.toString()); + } + + private String reduce( + List strings, + final Function keep + ) { + int pos = 0; + while (strings.size() > 1) { + final BitCount bitCounts = BitCount.atPos(strings, pos); + final int fpos = pos++; + final char toKeep = keep.apply(bitCounts); + strings = strings.stream() + .filter(s -> s.charAt(fpos) == toKeep) + .toList(); + } + return strings.get(0); + } + + @Override + public Integer solvePart2(final List lines) { + final String o2 = reduce(lines, BitCount::mostCommon); + final String co2 = reduce(lines, BitCount::leastCommon); + return ans(o2, co2); + } + + @Samples({ + @Sample(method = "part1", input = TEST, expected = "198"), + @Sample(method = "part2", input = TEST, expected = "230"), + }) + public static void main(final String[] args) throws Exception { + AoC2021_03.create().run(); + } + + private static final String TEST = """ + 00100 + 11110 + 10110 + 10111 + 10101 + 01111 + 00111 + 11100 + 10000 + 11001 + 00010 + 01010 + """; + + record BitCount(int ones, int zeroes) { + + public static BitCount atPos(final List strings, final int pos) { + final int zeroes = (int) strings.stream() + .filter(s -> s.charAt(pos) == '0') + .count(); + return new BitCount(strings.size() - zeroes, zeroes); + } + + public char mostCommon() { + return ones >= zeroes ? '1' : '0'; + } + + public char leastCommon() { + return ones < zeroes ? '1' : '0'; + } + } } diff --git a/src/main/java/AoC2021_04.java b/src/main/java/AoC2021_04.java index cb6633c2..b96ec15a 100644 --- a/src/main/java/AoC2021_04.java +++ b/src/main/java/AoC2021_04.java @@ -1,170 +1,116 @@ -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; +import static com.github.pareronia.aoc.AssertUtils.unreachable; +import static com.github.pareronia.aoc.IntegerSequence.Range.range; +import static com.github.pareronia.aoc.StringOps.toBlocks; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.function.Function; +import java.util.stream.Stream; import com.github.pareronia.aoc.StringUtils; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.Utils; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -public class AoC2021_04 extends AoCBase { - - private final List draws; - private final List boards; +public class AoC2021_04 extends SolutionBase, Integer, Integer> { - private AoC2021_04(final List input, final boolean debug) { + private AoC2021_04(final boolean debug) { super(debug); - final List> blocks = toBlocks(input); - this.draws = Arrays.stream(blocks.get(0).get(0).split(",")) - .map(Integer::valueOf) - .collect(toList()); - log(this.draws); - this.boards = blocks.subList(1, blocks.size()).stream() - .map(Board::new) - .collect(toList()); } - public static final AoC2021_04 create(final List input) { - return new AoC2021_04(input, false); + public static final AoC2021_04 create() { + return new AoC2021_04(false); } - public static final AoC2021_04 createDebug(final List input) { - return new AoC2021_04(input, true); - } - - private void play(final Function stop) { - for (final Integer draw : this.draws) { - this.boards.forEach(b -> b.mark(draw)); - final List winners = this.boards.stream() - .filter(b -> !b.isComplete()) - .filter(Board::win) - .collect(toList()); - for (final Board winner : winners) { - printGrid(winner.getNumbers()); - log(""); - winner.complete(); - if (stop.apply(new Bingo(draw, winner))) { - return; - } - } - } + public static final AoC2021_04 createDebug() { + return new AoC2021_04(true); } - private int solve(final int stopCount) { - final List bingoes = new ArrayList<>(); - play(bingo -> { - bingoes.add(bingo); - return bingoes.size() == stopCount; - }); - final Bingo lastBingo = bingoes.get(bingoes.size() - 1); - return lastBingo.getDraw() * lastBingo.getBoard().value(); + @Override + protected List parseInput(final List inputs) { + return inputs; + } + + private int solve(final BingoGame game, final int stopCount) { + final List bingoes = game.play(stopCount); + final BingoGame.Bingo lastBingo = Utils.last(bingoes); + return lastBingo.draw() * lastBingo.board().value(); } @Override - public Integer solvePart1() { - return solve(1); + public Integer solvePart1(final List input) { + final BingoGame game = BingoGame.fromInput(input); + return solve(game, 1); } @Override - public Integer solvePart2() { - return solve(this.boards.size()); + public Integer solvePart2(final List input) { + final BingoGame game = BingoGame.fromInput(input); + return solve(game, game.boards.size()); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "4512"), + @Sample(method = "part2", input = TEST, expected = "1924"), + }) public static void main(final String[] args) throws Exception { - assert AoC2021_04.create(TEST).solvePart1() == 4512; - assert AoC2021_04.create(TEST).solvePart2() == 1924; - - final Puzzle puzzle = Puzzle.create(2021, 4); - final List input = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2021_04.create(input)::solvePart1), - () -> lap("Part 2", AoC2021_04.create(input)::solvePart2) - ); + AoC2021_04.create().run(); } - private static final List TEST = splitLines( - "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\r\n" + - "\r\n" + - "22 13 17 11 0\r\n" + - " 8 2 23 4 24\r\n" + - "21 9 14 16 7\r\n" + - " 6 10 3 18 5\r\n" + - " 1 12 20 15 19\r\n" + - "\r\n" + - " 3 15 0 2 22\r\n" + - " 9 18 13 17 5\r\n" + - "19 8 7 25 23\r\n" + - "20 11 10 24 4\r\n" + - "14 21 16 12 6\r\n" + - "\r\n" + - "14 21 17 24 4\r\n" + - "10 16 15 9 19\r\n" + - "18 8 23 26 20\r\n" + - "22 11 13 6 5\r\n" + - " 2 0 12 3 7" - ); + private static final String TEST = """ + 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 + """; - private void printGrid(final int[][] grid) { - Arrays.stream(grid).forEach(r -> - log(Arrays.stream(r) - .mapToObj(Integer::valueOf) - .map(String::valueOf) - .collect(joining(" ")))); - } - - @RequiredArgsConstructor - @Getter - private static final class Bingo { - private final int draw; - private final Board board; - } - - @Getter private static class Board { private static final int MARKED = -1; private final int[][] numbers; private boolean complete = false; - public Board(final List numbers) { - final int[][] cells = new int[numbers.size()][numbers.get(0).length()]; - for (int i = 0; i < numbers.size(); i++) { - cells[i] = Arrays.stream(numbers.get(i).split("\\s+")) - .filter(StringUtils::isNotBlank) - .mapToInt(Integer::parseInt) - .toArray(); - } - this.numbers = cells; + public boolean isComplete() { + return complete; + } + + public Board(final List numbers) { + this.numbers = range(numbers.size()).intStream() + .mapToObj(i -> Arrays.stream(numbers.get(i).split("\\s+")) + .filter(StringUtils::isNotBlank) + .mapToInt(Integer::parseInt) + .toArray()) + .toArray(int[][]::new); } public void mark(final int number) { - for (int row = 0; row < getHeight(); row++) { - final int[] cs = numbers[row]; - for (int col = 0; col < getWidth(); col++) { - if (cs[col] == number) { - cs[col] = MARKED; - } - } - } + range(getHeight()).intStream().forEach(r -> + range(getWidth()).intStream() + .filter(c -> this.numbers[r][c] == number) + .forEach(c -> this.numbers[r][c] = MARKED)); } public boolean win() { - for (int row = 0; row < getHeight(); row++) { - if (allMarked(this.numbers[row])) { - return true; - } - } - for (int col = 0; col < getWidth(); col++) { - if (allMarked(getColumn(col))) { - return true; - } - } - return false; + return Stream.concat( + range(getHeight()).intStream().mapToObj(row -> numbers[row]), + range(getWidth()).intStream().mapToObj(this::getColumn)) + .anyMatch(rc -> Arrays.stream(rc).allMatch(n -> n == MARKED)); } public void complete() { @@ -172,28 +118,16 @@ public void complete() { } public int value() { - int value = 0; - for (int row = 0; row < getHeight(); row++) { - final int[] cs = numbers[row]; - for (int col = 0; col < getWidth(); col++) { - if (cs[col] != MARKED) { - value += cs[col]; - } - } - } - return value; - } - - private boolean allMarked(final int[] rc) { - return Arrays.stream(rc).allMatch(n -> n == MARKED); + return range(getHeight()).intStream().flatMap(r -> + range(getWidth()).intStream().map(c -> numbers[r][c])) + .filter(v -> v != MARKED) + .sum(); } private int[] getColumn(final int col) { - final int[] column = new int[getHeight()]; - for (int row = 0; row < getHeight(); row++) { - column[row] = this.numbers[row][col]; - } - return column; + return range(getHeight()).intStream() + .map(row -> this.numbers[row][col]) + .toArray(); } private int getWidth() { @@ -204,4 +138,40 @@ private int getHeight() { return this.numbers.length; } } + + record BingoGame(List draws, List boards) { + + record Bingo(int draw, Board board) {} + + public static BingoGame fromInput(final List input) { + final List> blocks = toBlocks(input); + final List draws = Arrays.stream(blocks.get(0).get(0).split(",")) + .map(Integer::valueOf) + .toList(); + final List boards = blocks.subList(1, blocks.size()).stream() + .map(Board::new) + .toList(); + return new BingoGame(draws, boards); + } + + public List play(final int stopCount) { + final List bingoes = new ArrayList<>(); + for (final Integer draw : draws) { + boards.forEach(b -> b.mark(draw)); + final List winners = boards.stream() + .filter(b -> !b.isComplete()) + .filter(Board::win) + .toList(); + for (final Board winner : winners) { + winner.complete(); + final Bingo bingo = new Bingo(draw, winner); + bingoes.add(bingo); + if (bingoes.size() == stopCount) { + return bingoes; + } + } + } + throw unreachable(); + } + } } diff --git a/src/main/java/AoC2021_05.java b/src/main/java/AoC2021_05.java index d4c99003..1eb18890 100644 --- a/src/main/java/AoC2021_05.java +++ b/src/main/java/AoC2021_05.java @@ -1,97 +1,94 @@ -import static java.util.stream.Collectors.toList; - import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.stream.Stream; -import com.github.pareronia.aoc.Grid.Cell; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.Counter; +import com.github.pareronia.aoc.IntegerSequence.Range; +import com.github.pareronia.aoc.geometry.Position; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; /** * TODO Extract some geometry lib stuff from this */ -public class AoC2021_05 extends AoCBase { +public class AoC2021_05 extends SolutionBase, Long, Long> { - private final List inputs; - - private AoC2021_05(final List input, final boolean debug) { + private AoC2021_05(final boolean debug) { super(debug); - this.inputs = input; - } - - private Map parseMap(final List input, final boolean diag) { - final Map map = new HashMap<>(); - for (final String s : input) { - final List p = Arrays.stream(s.split(" -> ")) - .flatMap(q -> Arrays.stream(q.split(","))) - .map(Integer::valueOf) - .collect(toList()); - assert p.size() == 4; - assert p.stream().allMatch(pp -> pp >= 0); - final int x1 = p.get(0); - final int y1 = p.get(1); - final int x2 = p.get(2); - final int y2 = p.get(3); - final int mx = x1 == x2 ? 0 : (x1 < x2 ? 1 : -1); - final int my = y1 == y2 ? 0 : (y1 < y2 ? 1 : -1); - if (!diag && mx != 0 && my != 0) { - continue; - } - final int len = Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2)); - for (int i = 0; i <= len; i++) { - final Cell cell = Cell.at(x1 + mx * i, y1 + my * i); - map.put(cell, map.getOrDefault(cell, 0) + 1); - } - } - return map; } - public static final AoC2021_05 create(final List input) { - return new AoC2021_05(input, false); + public static final AoC2021_05 create() { + return new AoC2021_05(false); } - public static final AoC2021_05 createDebug(final List input) { - return new AoC2021_05(input, true); + public static final AoC2021_05 createDebug() { + return new AoC2021_05(true); } - private int ans(final Map map) { - return (int) map.values().stream().filter(v -> v > 1).count(); + @Override + protected List parseInput(final List inputs) { + return inputs.stream().map(LineSegment::fromInput).toList(); } + private long countIntersections( + final List lines, + final boolean diag + ) { + final Counter counter = new Counter<>(lines.stream() + .filter(line -> diag || line.slopeX() == 0 || line.slopeY() == 0) + .flatMap(LineSegment::positions)); + return counter.values().stream().filter(v -> v > 1).count(); + } + @Override - public Integer solvePart1() { - final Map map = parseMap(this.inputs, false); - log(map); - return ans(map); + public Long solvePart1(final List lines) { + return countIntersections(lines, false); } @Override - public Integer solvePart2() { - final Map map = parseMap(this.inputs, true); - log(map); - return ans(map); + public Long solvePart2(final List lines) { + return countIntersections(lines, true); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "5"), + @Sample(method = "part2", input = TEST, expected = "12"), + }) public static void main(final String[] args) throws Exception { - assert AoC2021_05.create(TEST).solvePart1() == 5; - assert AoC2021_05.create(TEST).solvePart2() == 12; - - final List input = Aocd.getData(2021, 5); - lap("Part 1", () -> AoC2021_05.create(input).solvePart1()); - lap("Part 2", () -> AoC2021_05.create(input).solvePart2()); + AoC2021_05.create().run(); } - private static final List TEST = splitLines( - "0,9 -> 5,9\r\n" + - "8,0 -> 0,8\r\n" + - "9,4 -> 3,4\r\n" + - "2,2 -> 2,1\r\n" + - "7,0 -> 7,4\r\n" + - "6,4 -> 2,0\r\n" + - "0,9 -> 2,9\r\n" + - "3,4 -> 1,4\r\n" + - "0,0 -> 8,8\r\n" + - "5,5 -> 8,2" - ); + private static final String TEST = """ + 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 + """; + + record LineSegment(int x1, int y1, int x2, int y2, int slopeX, int slopeY) { + + public static LineSegment fromInput(final String input) { + final int[] p = Arrays.stream(input.split(" -> ")) + .flatMap(q -> Arrays.stream(q.split(","))) + .mapToInt(Integer::parseInt) + .toArray(); + return new LineSegment( + p[0], p[1], p[2], p[3], + p[0] == p[2] ? 0 : (p[0] < p[2] ? 1 : -1), + p[1] == p[3] ? 0 : (p[1] < p[3] ? 1 : -1)); + } + + public Stream positions() { + final int len = Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2)); + return Range.rangeClosed(len).intStream() + .mapToObj(i -> Position.of(x1 + slopeX * i, y1 + slopeY * i)); + } + } } diff --git a/src/main/java/AoC2021_06.java b/src/main/java/AoC2021_06.java index 0aee4218..069303af 100644 --- a/src/main/java/AoC2021_06.java +++ b/src/main/java/AoC2021_06.java @@ -1,40 +1,41 @@ import static java.util.Collections.nCopies; -import static java.util.stream.Collectors.summingLong; -import static java.util.stream.Collectors.toList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2021_06 extends AoCBase { +public class AoC2021_06 extends SolutionBase, Long, Long> { - private final List initial; - - private AoC2021_06(final List input, final boolean debug) { + private AoC2021_06(final boolean debug) { super(debug); - assert input.size() == 1; - this.initial = Arrays.stream(input.get(0).split(",")) - .map(Integer::valueOf) - .collect(toList()); } - public static final AoC2021_06 create(final List input) { - return new AoC2021_06(input, false); + public static final AoC2021_06 create() { + return new AoC2021_06(false); } - public static final AoC2021_06 createDebug(final List input) { - return new AoC2021_06(input, true); + public static final AoC2021_06 createDebug() { + return new AoC2021_06(true); } - + + @Override + protected List parseInput(final List inputs) { + return Arrays.stream(inputs.get(0).split(",")) + .map(Integer::valueOf) + .toList(); + } + private long sumValues(final List list) { - return list.stream().collect(summingLong(Long::valueOf)); + return list.stream().mapToLong(Long::longValue).sum(); } - private long solve(final int days) { + private long solve(final List initial, final int days) { final LinkedList fishies = new LinkedList<>(nCopies(9, 0L)); - for (final Integer i : this.initial) { + for (final Integer i : initial) { fishies.set(i, fishies.get(i) + 1); } log(fishies, 0); @@ -52,25 +53,22 @@ private void log(final LinkedList fishies, final int day) { } @Override - public Long solvePart1() { - return solve(80); + public Long solvePart1(final List initial) { + return solve(initial, 80); } @Override - public Long solvePart2() { - return solve(256); + public Long solvePart2(final List initial) { + return solve(initial, 256); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "5934", debug = false), + @Sample(method = "part2", input = TEST, expected = "26984457539", debug = false), + }) public static void main(final String[] args) throws Exception { - assert AoC2021_06.create(TEST).solvePart1() == 5_934; - assert AoC2021_06.create(TEST).solvePart2() == 26_984_457_539L; - - final List input = Aocd.getData(2021, 6); - lap("Part 1", () -> AoC2021_06.create(input).solvePart1()); - lap("Part 2", () -> AoC2021_06.create(input).solvePart2()); + AoC2021_06.create().run(); } - private static final List TEST = splitLines( - "3,4,3,1,2" - ); + private static final String TEST = "3,4,3,1,2"; } diff --git a/src/main/java/AoC2021_07.java b/src/main/java/AoC2021_07.java index 1396e6c5..67f613af 100644 --- a/src/main/java/AoC2021_07.java +++ b/src/main/java/AoC2021_07.java @@ -1,67 +1,68 @@ -import static java.util.stream.Collectors.summarizingInt; -import static java.util.stream.Collectors.toList; - import java.util.Arrays; import java.util.IntSummaryStatistics; import java.util.List; -import java.util.function.BiFunction; +import java.util.function.IntBinaryOperator; import java.util.stream.IntStream; -import com.github.pareronia.aocd.Aocd; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2021_07 extends AoCBase { - - private final List positions; +public class AoC2021_07 extends SolutionBase, Integer, Integer> { - private AoC2021_07(final List input, final boolean debug) { + private AoC2021_07(final boolean debug) { super(debug); - this.positions = Arrays.stream(input.get(0).split(",")) - .map(Integer::valueOf) - .collect(toList()); } - public static final AoC2021_07 create(final List input) { - return new AoC2021_07(input, false); + public static final AoC2021_07 create() { + return new AoC2021_07(false); } - public static final AoC2021_07 createDebug(final List input) { - return new AoC2021_07(input, true); + public static final AoC2021_07 createDebug() { + return new AoC2021_07(true); } - - private int solve(final BiFunction calc) { - final IntSummaryStatistics summary = this.positions.stream() - .collect(summarizingInt(Integer::intValue)); + + @Override + protected List parseInput(final List inputs) { + return Arrays.stream(inputs.get(0).split(",")) + .map(Integer::valueOf) + .toList(); + } + + private int solve( + final List positions, + final IntBinaryOperator calc + ) { + final IntSummaryStatistics summary = positions.stream() + .mapToInt(Integer::intValue).summaryStatistics(); return IntStream.rangeClosed(summary.getMin(), summary.getMax()) - .map(a -> this.positions.stream() - .mapToInt(Integer::valueOf) - .map(b -> calc.apply(a, b)) + .map(a -> positions.stream() + .mapToInt(Integer::intValue) + .map(b -> calc.applyAsInt(a, b)) .sum()) .min().orElseThrow(); } @Override - public Integer solvePart1() { - return solve((a, b) -> Math.abs(a - b)); + public Integer solvePart1(final List positions) { + return solve(positions, (a, b) -> Math.abs(a - b)); } @Override - public Integer solvePart2() { - return solve((a, b) -> { + public Integer solvePart2(final List positions) { + return solve(positions, (a, b) -> { final int diff = Math.abs(a - b); return (diff * (diff + 1)) / 2; }); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "37"), + @Sample(method = "part2", input = TEST, expected = "168"), + }) public static void main(final String[] args) throws Exception { - assert AoC2021_07.create(TEST).solvePart1() == 37; - assert AoC2021_07.create(TEST).solvePart2() == 168; - - final List input = Aocd.getData(2021, 7); - lap("Part 1", () -> AoC2021_07.create(input).solvePart1()); - lap("Part 2", () -> AoC2021_07.create(input).solvePart2()); + AoC2021_07.create().run(); } - private static final List TEST = splitLines( - "16,1,2,0,4,2,7,1,2,14" - ); + private static final String TEST = "16,1,2,0,4,2,7,1,2,14"; } diff --git a/src/main/java/AoC2021_10.java b/src/main/java/AoC2021_10.java index 3cd80454..a29ecb4c 100644 --- a/src/main/java/AoC2021_10.java +++ b/src/main/java/AoC2021_10.java @@ -9,10 +9,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - public class AoC2021_10 extends AoCBase { private static final char PAREN_OPEN = '('; @@ -80,7 +76,7 @@ private Result check(final String line) { public Long solvePart1() { return this.lines.stream() .map(this::check) - .map(Result::getCorrupt) + .map(Result::corrupt) .filter(Objects::nonNull) .map(CORRUPTION_SCORES::get) .mapToLong(Long::longValue) @@ -91,7 +87,7 @@ public Long solvePart1() { public Long solvePart2() { final long[] scores = this.lines.stream() .map(this::check) - .map(Result::getIncomplete) + .map(Result::incomplete) .filter(Objects::nonNull) .map(Arrays::asList) .map(x -> x.stream() @@ -128,11 +124,7 @@ public static void main(final String[] args) throws Exception { "<{([{{}}[<[[[<>{}]]]>[]]" ); - @RequiredArgsConstructor(access = AccessLevel.PRIVATE) - @Getter - private static final class Result { - private final Character corrupt; - private final Character[] incomplete; + record Result(Character corrupt, Character[] incomplete) { public static Result corrupt(final Character c) { return new Result(c, null); diff --git a/src/main/java/AoC2021_12.java b/src/main/java/AoC2021_12.java index 9e802bfc..535400a3 100644 --- a/src/main/java/AoC2021_12.java +++ b/src/main/java/AoC2021_12.java @@ -8,60 +8,39 @@ import com.github.pareronia.aoc.MutableInt; import com.github.pareronia.aoc.StringUtils; -import com.github.pareronia.aocd.Aocd; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -public class AoC2021_12 extends AoCBase { +public class AoC2021_12 + extends SolutionBase { - private final System system; - private AoC2021_12(final List input, final boolean debug) { + private AoC2021_12(final boolean debug) { super(debug); - final Map> tunnels = new HashMap<>(); - Cave start = null, end = null; - for (final String string : input) { - final String[] split = string.split("-"); - final Cave from = new Cave(split[0]); - final Cave to = new Cave(split[1]); - tunnels.merge(from, new HashSet<>(Set.of(to)), (s1, s2) -> { - s1.addAll(s2); - return s1; - }); - if (from.isStart()) { - start = from; - } else if (to.isEnd()) { - end = to; - } - tunnels.merge(to, new HashSet<>(Set.of(from)), (s1, s2) -> { - s1.addAll(s2); - return s1; - }); - } - system = new System(Collections.unmodifiableMap(tunnels), start, end); - log(system.tunnels.size()); - log(system); } - public static final AoC2021_12 create(final List input) { - return new AoC2021_12(input, false); + public static final AoC2021_12 create() { + return new AoC2021_12(false); } - public static final AoC2021_12 createDebug(final List input) { - return new AoC2021_12(input, true); + public static final AoC2021_12 createDebug() { + return new AoC2021_12(true); } - private void dfs(final Cave start, final Cave end, final State state, + @Override + protected System parseInput(final List inputs) { + return System.fromInput(inputs); + } + + private void dfs(final System system, final Cave start, final Cave end, + final State state, final BiFunction proceed, final Runnable onPath) { if (start.equals(end)) { onPath.run(); return; } - for (final Cave to : this.system.getTunnels().get(start)) { + for (final Cave to : system.tunnels().get(start)) { if (proceed.apply(to, state)) { final State newState = State.copyOf(state); if (to.isSmall()) { @@ -70,31 +49,31 @@ private void dfs(final Cave start, final Cave end, final State state, } newState.smallCavesSeen.add(to); } - dfs(to, end, newState, proceed, onPath); + dfs(system, to, end, newState, proceed, onPath); } } } - private int solve(final BiFunction proceed) { - final Cave start = this.system.getStart(); + private int solve(final System system, final BiFunction proceed) { + final Cave start = system.start(); final State state = new State(new HashSet<>(Set.of(start)), new HashSet<>()); final MutableInt count = new MutableInt(); - dfs(start, this.system.getEnd(), state, proceed, () -> count.increment()); + dfs(system, start, system.end(), state, proceed, () -> count.increment()); return count.intValue(); } @Override - public Integer solvePart1() { - return solve((to, state) + public Integer solvePart1(final System system) { + return solve(system, (to, state) -> !to.isSmall() || !state.smallCavesSeen.contains(to)); } @Override - public Integer solvePart2() { - return solve((to, state) + public Integer solvePart2(final System system) { + return solve(system, (to, state) -> !to.isSmall() || !state.smallCavesSeen.contains(to) || (!to.isStart() @@ -102,68 +81,61 @@ public Integer solvePart2() { && state.smallCavesSeenTwice.isEmpty())); } + @Samples({ + @Sample(method = "part1", input = TEST1, debug = false, expected = "10"), + @Sample(method = "part1", input = TEST2, debug = false, expected = "19"), + @Sample(method = "part1", input = TEST3, debug = false, expected = "226"), + @Sample(method = "part2", input = TEST1, debug = false, expected = "36"), + @Sample(method = "part2", input = TEST2, debug = false, expected = "103"), + @Sample(method = "part2", input = TEST3, debug = false, expected = "3509"), + }) public static void main(final String[] args) throws Exception { - assert AoC2021_12.create(TEST1).solvePart1() == 10; - assert AoC2021_12.create(TEST2).solvePart1() == 19; - assert AoC2021_12.create(TEST3).solvePart1() == 226; - assert AoC2021_12.create(TEST1).solvePart2() == 36; - assert AoC2021_12.create(TEST2).solvePart2() == 103; - assert AoC2021_12.create(TEST3).solvePart2() == 3509; - - final Puzzle puzzle = Aocd.puzzle(2021, 12); - final List inputData = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2021_12.create(inputData)::solvePart1), - () -> lap("Part 2", AoC2021_12.create(inputData)::solvePart2) - ); + AoC2021_12.create().run(); } - private static final List TEST1 = splitLines( - "start-A\r\n" + - "start-b\r\n" + - "A-c\r\n" + - "A-b\r\n" + - "b-d\r\n" + - "A-end\r\n" + - "b-end" - ); - private static final List TEST2 = splitLines( - "dc-end\r\n" + - "HN-start\r\n" + - "start-kj\r\n" + - "dc-start\r\n" + - "dc-HN\r\n" + - "LN-dc\r\n" + - "HN-end\r\n" + - "kj-sa\r\n" + - "kj-HN\r\n" + - "kj-dc" - ); - private static final List TEST3 = splitLines( - "fs-end\r\n" + - "he-DX\r\n" + - "fs-he\r\n" + - "start-DX\r\n" + - "pj-DX\r\n" + - "end-zg\r\n" + - "zg-sl\r\n" + - "zg-pj\r\n" + - "pj-he\r\n" + - "RW-he\r\n" + - "fs-DX\r\n" + - "pj-RW\r\n" + - "zg-RW\r\n" + - "start-pj\r\n" + - "he-WI\r\n" + - "zg-he\r\n" + - "pj-fs\r\n" + - "start-RW" - ); + private static final String TEST1 = """ + start-A\r + start-b\r + A-c\r + A-b\r + b-d\r + A-end\r + b-end + """; + private static final String TEST2 = """ + dc-end\r + HN-start\r + start-kj\r + dc-start\r + dc-HN\r + LN-dc\r + HN-end\r + kj-sa\r + kj-HN\r + kj-dc + """; + private static final String TEST3 = """ + fs-end\r + he-DX\r + fs-he\r + start-DX\r + pj-DX\r + end-zg\r + zg-sl\r + zg-pj\r + pj-he\r + RW-he\r + fs-DX\r + pj-RW\r + zg-RW\r + start-pj\r + he-WI\r + zg-he\r + pj-fs\r + start-RW + """; - @RequiredArgsConstructor - private static final class State { - private final Set smallCavesSeen; - private final Set smallCavesSeenTwice; + record State(Set smallCavesSeen, Set smallCavesSeenTwice) { public static State copyOf(final State state) { return new State(new HashSet<>(Set.copyOf(state.smallCavesSeen)), @@ -171,12 +143,7 @@ public static State copyOf(final State state) { } } - @RequiredArgsConstructor - @Getter - @EqualsAndHashCode - @ToString - private static final class Cave { - private final String name; + record Cave(String name) { public boolean isSmall() { return StringUtils.isAllLowerCase(name); @@ -191,21 +158,28 @@ public boolean isEnd() { } } - @RequiredArgsConstructor - @Getter - @EqualsAndHashCode - @ToString - private static final class Tunnel { - private final Cave from; - private final Cave to; - } + record Tunnel(Cave from, Cave to) {} - @RequiredArgsConstructor - @Getter - @ToString - private static final class System { - private final Map> tunnels; - private final Cave start; - private final Cave end; - } + record System(Map> tunnels, Cave start, Cave end) { + + public static System fromInput(final List inputs) { + final Map> tunnels = new HashMap<>(); + Cave start = null, end = null; + for (final String string : inputs) { + final String[] split = string.split("-"); + final Cave from = new Cave(split[0]); + final Cave to = new Cave(split[1]); + tunnels.computeIfAbsent(from, c -> new HashSet<>()).add(to); + tunnels.computeIfAbsent(to, c -> new HashSet<>()).add(from); + for (final Cave cave : Set.of(from, to)) { + if (cave.isStart()) { + start = cave; + } + if (cave.isEnd()) { + end = cave; + } + } + } + return new System(Collections.unmodifiableMap(tunnels), start, end); + }} } diff --git a/src/main/java/AoC2021_13.java b/src/main/java/AoC2021_13.java index e3f28d41..aad8a7db 100644 --- a/src/main/java/AoC2021_13.java +++ b/src/main/java/AoC2021_13.java @@ -15,10 +15,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public class AoC2021_13 extends AoCBase { private static final char FILL = '\u2592'; @@ -38,7 +34,7 @@ private AoC2021_13(final List input, final boolean debug) { this._folds = new ArrayList<>(); for (final String line : blocks.get(1)) { final String[] split = line.substring("fold along ".length()).split("="); - _folds.add(new Fold("x".equals(split[0]) ? true : false, Integer.valueOf(split[1]))); + _folds.add(new Fold("x".equals(split[0]) ? true : false, Integer.parseInt(split[1]))); } } @@ -117,12 +113,7 @@ public static void main(final String[] args) throws Exception { "▒▒▒▒▒ " ); - @RequiredArgsConstructor - @Getter - @ToString - private static final class Fold { - private final boolean xAxis; - private final int value; + record Fold(boolean xAxis, int value) { public Set applyTo(final Set positions) { if (this.xAxis) { diff --git a/src/main/java/AoC2021_14.java b/src/main/java/AoC2021_14.java index 1833e415..067926d5 100644 --- a/src/main/java/AoC2021_14.java +++ b/src/main/java/AoC2021_14.java @@ -10,11 +10,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public class AoC2021_14 extends AoCBase { private final char[] template; @@ -56,8 +51,8 @@ private Long solve(final int cycles) { final Character elem = rules.get(pair); final Long count = e.getValue(); elemCounters.merge(elem, count, Long::sum); - pairCounters2.merge(new CharacterPair(pair.getLeft(), elem), count, Long::sum); - pairCounters2.merge(new CharacterPair(elem, pair.getRight()), count, Long::sum); + pairCounters2.merge(new CharacterPair(pair.left(), elem), count, Long::sum); + pairCounters2.merge(new CharacterPair(elem, pair.right()), count, Long::sum); } pairCounters = pairCounters2; } @@ -109,13 +104,7 @@ public static void main(final String[] args) throws Exception { "CN -> C" ); - @RequiredArgsConstructor - @Getter - @EqualsAndHashCode - @ToString - private static final class CharacterPair { - private final Character left; - private final Character right; + record CharacterPair(Character left, Character right) { public static CharacterPair from(final String string) { return new CharacterPair(string.charAt(0), string.charAt(1)); diff --git a/src/main/java/AoC2021_15.java b/src/main/java/AoC2021_15.java index 19ef3479..184d47bd 100644 --- a/src/main/java/AoC2021_15.java +++ b/src/main/java/AoC2021_15.java @@ -6,9 +6,9 @@ import java.util.stream.IntStream; import java.util.stream.Stream; -import com.github.pareronia.aoc.IntGrid; import com.github.pareronia.aoc.Grid.Cell; -import com.github.pareronia.aoc.graph.AStar; +import com.github.pareronia.aoc.IntGrid; +import com.github.pareronia.aoc.graph.Dijkstra; import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; @@ -51,15 +51,15 @@ private Stream findNeighbours(final Cell c, final int tiles) { .filter(n -> n.getCol() < tiles * this.grid.getWidth()); } - private AStar.Result runAStar(final int tiles) { + private Dijkstra.Result runAStar(final int tiles) { final Cell end = Cell.at( tiles * this.grid.getHeight() - 1, tiles * this.grid.getWidth() - 1); - return AStar.execute( + return Dijkstra.execute( START, end::equals, cell -> findNeighbours(cell, tiles), - this::getRisk); + (curr, next) -> this.getRisk(next)); } private int solve(final int tiles) { @@ -128,15 +128,16 @@ public static void main(final String[] args) throws Exception { } private static final List TEST = splitLines( - "1163751742\r\n" + - "1381373672\r\n" + - "2136511328\r\n" + - "3694931569\r\n" + - "7463417111\r\n" + - "1319128137\r\n" + - "1359912421\r\n" + - "3125421639\r\n" + - "1293138521\r\n" + - "2311944581" + """ + 1163751742\r + 1381373672\r + 2136511328\r + 3694931569\r + 7463417111\r + 1319128137\r + 1359912421\r + 3125421639\r + 1293138521\r + 2311944581""" ); } \ No newline at end of file diff --git a/src/main/java/AoC2021_16.java b/src/main/java/AoC2021_16.java index a25e53a6..68d6f005 100644 --- a/src/main/java/AoC2021_16.java +++ b/src/main/java/AoC2021_16.java @@ -8,27 +8,50 @@ import java.util.stream.LongStream; import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.solution.Logger; +import com.github.pareronia.aoc.solution.LoggerEnabled; import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Builder; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public class AoC2021_16 extends AoCBase { private static final class BITS { - @RequiredArgsConstructor - @Getter - @ToString - @Builder - public static final class Packet { - private final Integer version; - private final Integer typeId; - private final Long value; - private final List subPackets; + record Packet( + Integer version, + Integer typeId, + Long value, + List subPackets + ) { + public static PacketBuilder builder() { + return new PacketBuilder(); + } + + public static final class PacketBuilder { + private Integer version; + private Integer typeId; + private Long value; + private List subPackets; + + public PacketBuilder version(final Integer version) { + this.version = version; + return this; + } + + public PacketBuilder typeId(final Integer typeId) { + this.typeId = typeId; + return this; + } + + public PacketBuilder subPackets(final List subPackets) { + this.subPackets = subPackets; + return this; + } + + public Packet build() { + return new Packet(version, typeId, value, subPackets); + } + } } public interface BITSEventHandler { @@ -53,15 +76,16 @@ default void endOperatorPacket() { } } - @RequiredArgsConstructor - public static class LoggingBITSHandler implements BITSEventHandler { - private final boolean debug; + public static class LoggingBITSHandler implements BITSEventHandler, LoggerEnabled { + private final Logger logger; - protected void log(final Object obj) { - if (!debug) { - return; - } - System.out.println(obj); + public LoggingBITSHandler(final Logger logger) { + this.logger = logger; + } + + @Override + public Logger getLogger() { + return this.logger; } @Override @@ -94,8 +118,8 @@ public static class DOMBITSHandler extends LoggingBITSHandler { private final Deque builderStack = new ArrayDeque<>(); private final Deque packetStack = new ArrayDeque<>(); - public DOMBITSHandler(final boolean debug) { - super(debug); + public DOMBITSHandler(final Logger logger) { + super(logger); } @Override @@ -153,9 +177,16 @@ public Packet getPacket() { } } - @RequiredArgsConstructor(staticName = "createParser") public static final class Parser { private final BITSEventHandler handler; + + private Parser(final BITSEventHandler handler) { + this.handler = handler; + } + + public static Parser createParser(final BITSEventHandler handler) { + return new Parser(handler); + } public void parseHex(final String hexString) { parseBin(StringOps.hexToBin(hexString)); @@ -259,8 +290,8 @@ public static final AoC2021_16 createDebug(final List input) { private static final class VersionSumBITSHandler extends AoC2021_16.BITS.LoggingBITSHandler { private int versionSum = 0; - public VersionSumBITSHandler(final boolean debug) { - super(debug); + public VersionSumBITSHandler(final Logger logger) { + super(logger); } public int getVersionSum() { @@ -276,32 +307,32 @@ public void version(final int version) { private static final class ValueBITSCalcHandler extends AoC2021_16.BITS.DOMBITSHandler { - public ValueBITSCalcHandler(final boolean debug) { - super(debug); + public ValueBITSCalcHandler(final Logger logger) { + super(logger); } private long calcValue(final AoC2021_16.BITS.Packet packet) { - final List values = packet.getSubPackets().stream() + final List values = packet.subPackets().stream() .map(p -> { - if (p.getValue() != null) { - return p.getValue(); + if (p.value() != null) { + return p.value(); } return calcValue(p); }) .collect(toList()); final LongStream longs = values.stream().mapToLong(Long::longValue); - if (packet.getTypeId() == 0) { + if (packet.typeId() == 0) { return longs.sum(); - } else if (packet.getTypeId() == 1) { + } else if (packet.typeId() == 1) { return longs.reduce(1L, (a, b) -> a * b); - } else if (packet.getTypeId() == 2) { + } else if (packet.typeId() == 2) { return longs.min().orElseThrow(); - } else if (packet.getTypeId() == 3) { + } else if (packet.typeId() == 3) { return longs.max().orElseThrow(); - } else if (packet.getTypeId() == 5) { + } else if (packet.typeId() == 5) { assert values.size() == 2; return values.get(0) > values.get(1) ? 1L : 0L; - } else if (packet.getTypeId() == 6) { + } else if (packet.typeId() == 6) { assert values.size() == 2; return values.get(0) < values.get(1) ? 1L : 0L; } else { @@ -317,14 +348,14 @@ public long getValue() { @Override public Integer solvePart1() { - final VersionSumBITSHandler handler = new VersionSumBITSHandler(this.debug); + final VersionSumBITSHandler handler = new VersionSumBITSHandler(this.logger); BITS.Parser.createParser(handler).parseHex(this.hexData); return handler.getVersionSum(); } @Override public Long solvePart2() { - final ValueBITSCalcHandler handler = new ValueBITSCalcHandler(this.debug); + final ValueBITSCalcHandler handler = new ValueBITSCalcHandler(this.logger); BITS.Parser.createParser(handler).parseHex(this.hexData); log(handler.getPacket()); return handler.getValue(); diff --git a/src/main/java/AoC2021_18.java b/src/main/java/AoC2021_18.java index 3549ba5e..4a7be917 100644 --- a/src/main/java/AoC2021_18.java +++ b/src/main/java/AoC2021_18.java @@ -10,9 +10,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.AllArgsConstructor; -import lombok.Getter; - public class AoC2021_18 extends AoCBase { private final List inputs; @@ -94,8 +91,7 @@ public static boolean split(final Number number) { } private static boolean doSplit(final Number number) { - if (number instanceof Regular) { - final Regular regular = (Regular) number; + if (number instanceof final Regular regular) { final int value = regular.value; if (value >= 10) { final Pair pair = Pair.create(new Regular(value / 2), new Regular(value - value / 2)); @@ -242,30 +238,47 @@ static abstract class Number { protected Number parent = null; } - @AllArgsConstructor - @Getter static final class Regular extends Number { private int value = -1; + public Regular(final int value) { + this.value = value; + } + + public int getValue() { + return value; + } + @Override public String toString() { return String.valueOf(this.value); } } - @AllArgsConstructor - @Getter static final class Pair extends Number { private Number left = null; private Number right = null; + public Pair(final Number left, final Number right) { + this.left = left; + this.right = right; + } + public static Pair create(final Number left, final Number right) { final Pair pair = new Pair(left, right); left.parent = pair; right.parent = pair; return pair; } - + + public Number getLeft() { + return left; + } + + public Number getRight() { + return right; + } + public Regular leftAdjacent() { final Pair parent = getParent(); if (parent == null) { diff --git a/src/main/java/AoC2021_19.java b/src/main/java/AoC2021_19.java index ed1c6bf2..265cb734 100644 --- a/src/main/java/AoC2021_19.java +++ b/src/main/java/AoC2021_19.java @@ -20,10 +20,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public class AoC2021_19 extends AoCBase { private final List scannerData; @@ -43,7 +39,7 @@ private AoC2021_19(final List input, final boolean debug) { points.add(new Position3D( coordinates.get(0), coordinates.get(1), coordinates.get(2))); } - scannerData.add(new ScannerData(Integer.valueOf(id), points)); + scannerData.add(new ScannerData(Integer.parseInt(id), points)); } this.scannerData = Collections.unmodifiableList(scannerData); } @@ -61,20 +57,20 @@ public Map solve() { final Deque q = new ArrayDeque<>(List.copyOf(this.scannerData)); final ScannerData sc0 = q.pop(); lockedIn.put(sc0, Vector3D.of(0, 0, 0)); - assert sc0.getId() == 0; + assert sc0.id() == 0; while (!q.isEmpty()) { final ScannerData sc = q.pop(); final Optional overlapping = locateOverlappingScanner(sc, lockedIn); if (overlapping.isPresent()) { lockedIn.put( - overlapping.get().getScannerData(), - overlapping.get().getVector()); + overlapping.get().scannerData(), + overlapping.get().vector()); } else { q.add(sc); } } - log(lockedIn.keySet().stream().map(ScannerData::getId).collect(toList())); + log(lockedIn.keySet().stream().map(ScannerData::id).collect(toList())); return lockedIn; } @@ -91,7 +87,7 @@ public Map solve() { final Vector3D vector = overlap.get(); log(vector); final ScannerData overlapping = new ScannerData( - sc.getId(), + sc.id(), Transformations3D.translate(positions, vector)); return Optional.of(new OverlappingScanner(overlapping, vector)); } @@ -109,7 +105,7 @@ public Map solve() { final List overlaptx = other.beacons.stream() .flatMap(b -> positions.stream().map(p -> new Position3DPair(b, p))) - .collect(groupingBy(t -> Vector3D.from(t.getTwo(), t.getOne()), + .collect(groupingBy(t -> Vector3D.from(t.two(), t.one()), counting())) .entrySet().stream() .filter(e -> e.getValue() >= 12L) @@ -117,7 +113,7 @@ public Map solve() { .collect(toList()); if (overlaptx.size() > 0) { log(String.format( - "overlaptx between sc%d and sc%d", sc.getId(), other.getId())); + "overlaptx between sc%d and sc%d", sc.id(), other.id())); assert overlaptx.size() == 1; return Optional.of(overlaptx.get(0)); } else { @@ -140,10 +136,10 @@ public Integer solvePart2() { return scanners.values().stream() .flatMap(v1 -> scanners.values().stream() .map(v2 -> new Vector3DPair(v1, v2))) - .filter(t -> !t.getOne().equals(t.getTwo())) - .map(t -> Position3D.of(0, 0, 0).translate(t.getOne()) + .filter(t -> !t.one().equals(t.two())) + .map(t -> Position3D.of(0, 0, 0).translate(t.one()) .manhattanDistance( - Position3D.of(0, 0, 0).translate(t.getTwo()))) + Position3D.of(0, 0, 0).translate(t.two()))) .mapToInt(Integer::intValue) .max().orElseThrow(); } @@ -330,32 +326,11 @@ public List next() { } } - @RequiredArgsConstructor - @Getter - @ToString - static final class ScannerData { - private final int id; - private final List beacons; - } + record ScannerData(int id, List beacons) {} - @RequiredArgsConstructor - @Getter - private static final class OverlappingScanner { - private final ScannerData scannerData; - private final Vector3D vector; - } + record OverlappingScanner(ScannerData scannerData, Vector3D vector) {} - @RequiredArgsConstructor - @Getter - private static final class Position3DPair { - private final Position3D one; - private final Position3D two; - } + record Position3DPair(Position3D one, Position3D two) {} - @RequiredArgsConstructor - @Getter - private static final class Vector3DPair { - private final Vector3D one; - private final Vector3D two; - } + record Vector3DPair(Vector3D one, Vector3D two) {} } \ No newline at end of file diff --git a/src/main/java/AoC2021_21.java b/src/main/java/AoC2021_21.java index 3eb78038..52c3cb6f 100644 --- a/src/main/java/AoC2021_21.java +++ b/src/main/java/AoC2021_21.java @@ -2,16 +2,11 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - // TODO: add iterative verson public class AoC2021_21 extends AoCBase { @@ -33,14 +28,18 @@ public static final AoC2021_21 createDebug(final List input) { return new AoC2021_21(input, true); } - @AllArgsConstructor - @EqualsAndHashCode - @ToString private static final class Game { private int pos1; private int pos2; private int score1; private int score2; + + public Game(final int pos1, final int pos2, final int score1, final int score2) { + this.pos1 = pos1; + this.pos2 = pos2; + this.score1 = score1; + this.score2 = score2; + } public void turn1(final int[] rolls) { for (final int roll : rolls) { @@ -55,6 +54,35 @@ public void turn2(final int[] rolls) { } score2 += pos2 + 1; } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Game [pos1=").append(pos1).append(", pos2=") + .append(pos2).append(", score1=").append(score1) + .append(", score2=").append(score2).append("]"); + return builder.toString(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Game other = (Game) obj; + return pos1 == other.pos1 && pos2 == other.pos2 && score1 == other.score1 && score2 == other.score2; + } + + @Override + public int hashCode() { + return Objects.hash(pos1, pos2, score1, score2); + } } @Override @@ -98,13 +126,13 @@ private LongPair solve2(final Game game) { final int nPos = (game.pos1 + roll.getKey()) % 10; final int nScore = game.score1 + nPos + 1; if (nScore >= 21 ) { - wins = new LongPair(wins.getOne() + roll.getValue(), wins.getTwo()); + wins = new LongPair(wins.one() + roll.getValue(), wins.two()); } else { final Game newGame = new Game(game.pos2, nPos, game.score2, nScore); final LongPair nwins = solve2(newGame); wins = new LongPair( - wins.getOne() + roll.getValue() * nwins.getTwo(), - wins.getTwo() + roll.getValue() * nwins.getOne()); + wins.one() + roll.getValue() * nwins.two(), + wins.two() + roll.getValue() * nwins.one()); } } winsCache.put(game, wins); @@ -116,7 +144,7 @@ public Long solvePart2() { final LongPair ans = solve2(new Game(this.p1 - 1, this.p2 - 1, 0, 0)); log(ans); - return Math.max(ans.getOne(), ans.getTwo()); + return Math.max(ans.one(), ans.two()); } public static void main(final String[] args) throws Exception { @@ -136,10 +164,5 @@ public static void main(final String[] args) throws Exception { "Player 2 starting position: 8" ); - @RequiredArgsConstructor - @Getter - private static final class LongPair { - private final Long one; - private final Long two; - } + record LongPair(Long one, Long two) {} } \ No newline at end of file diff --git a/src/main/java/AoC2021_22.java b/src/main/java/AoC2021_22.java index 0852eb56..29b4ca1e 100644 --- a/src/main/java/AoC2021_22.java +++ b/src/main/java/AoC2021_22.java @@ -12,9 +12,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public class AoC2021_22 extends AoCBase { private static final Pattern PATTERN = Pattern.compile( @@ -201,12 +198,10 @@ public static void main(final String[] args) throws Exception { + "off x=-93533..-4276,y=-16170..68771,z=-104985..-24507" ); - @RequiredArgsConstructor - @ToString - private static final class RebootStep { - private final RangeInclusive x; - private final RangeInclusive y; - private final RangeInclusive z; - private final boolean on; - } + record RebootStep( + RangeInclusive x, + RangeInclusive y, + RangeInclusive z, + boolean on + ) {} } \ No newline at end of file diff --git a/src/main/java/AoC2021_23.java b/src/main/java/AoC2021_23.java index 1352f675..646350fe 100644 --- a/src/main/java/AoC2021_23.java +++ b/src/main/java/AoC2021_23.java @@ -7,6 +7,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; import java.util.stream.IntStream; @@ -16,12 +17,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public class AoC2021_23 extends AoCBase { private static final char WALL = '#'; @@ -57,7 +52,7 @@ private final Diagram parse(final List input) { roomC.add(0, Enum.valueOf(Amphipod.class, chars.get(2).toString())); roomD.add(0, Enum.valueOf(Amphipod.class, chars.get(3).toString())); } - return new Diagram( + return Diagram.create( hallway, roomA.toArray(new Amphipod[roomA.size()]), roomB.toArray(new Amphipod[roomB.size()]), @@ -170,10 +165,7 @@ public static void main(final String[] args) throws Exception { enum Amphipod { EMPTY, A, B, C, D } - @RequiredArgsConstructor - private static final class State implements Comparable { - private final Diagram diagram; - private final int cost; + record State(Diagram diagram, int cost) implements Comparable { @Override public int compareTo(final State other) { @@ -181,16 +173,8 @@ public int compareTo(final State other) { } } - @RequiredArgsConstructor - @Getter - @Builder - @EqualsAndHashCode - @ToString - static final class Room { - private final Amphipod destinationFor; - @ToString.Exclude private final int capacity; - private final Amphipod[] amphipods; - + record Room(Amphipod destinationFor, int capacity, Amphipod[] amphipods) { + public int vacancyFor(final Amphipod amphipod) { assert amphipod != Amphipod.EMPTY; // System.out.println("Room " + destinationFor + ": " + amphipods[0] + ","+ amphipods[1]); @@ -244,49 +228,62 @@ private int countEmpty() { .filter(a -> a == Amphipod.EMPTY) .count(); } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(amphipods); + return prime * result + Objects.hash(capacity, destinationFor); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Room other = (Room) obj; + return Arrays.equals(amphipods, other.amphipods) + && capacity == other.capacity + && destinationFor == other.destinationFor; + } } - @EqualsAndHashCode - @Getter - @ToString - static final class Diagram { - private final Room hallway; - private final Room roomA; - private final Room roomB; - private final Room roomC; - private final Room roomD; + record Diagram(Room hallway, Room roomA, Room roomB, Room roomC, Room roomD) { - public Diagram( - final Amphipod[] hallway, + public static Diagram create( + final Amphipod[] amphipodsHallway, final Amphipod[] amphipodsA, final Amphipod[] amphipodsB, final Amphipod[] amphipodsC, final Amphipod[] amphipodsD) { - this.hallway = Room.builder() - .destinationFor(Amphipod.EMPTY) - .capacity(hallway.length) - .amphipods(hallway) - .build(); - this.roomA = Room.builder() - .destinationFor(Amphipod.A) - .capacity(amphipodsA.length) - .amphipods(amphipodsA) - .build(); - this.roomB = Room.builder() - .destinationFor(Amphipod.B) - .capacity(amphipodsB.length) - .amphipods(amphipodsB) - .build(); - this.roomC = Room.builder() - .destinationFor(Amphipod.C) - .capacity(amphipodsC.length) - .amphipods(amphipodsC) - .build(); - this.roomD = Room.builder() - .destinationFor(Amphipod.D) - .capacity(amphipodsD.length) - .amphipods(amphipodsD) - .build(); + final Room hallway = new Room( + Amphipod.EMPTY, + amphipodsHallway.length, + amphipodsHallway); + final Room roomA = new Room( + Amphipod.A, + amphipodsA.length, + amphipodsA); + final Room roomB = new Room( + Amphipod.B, + amphipodsB.length, + amphipodsB); + final Room roomC = new Room( + Amphipod.C, + amphipodsC.length, + amphipodsC); + final Room roomD = new Room( + Amphipod.D, + amphipodsD.length, + amphipodsD); + return new Diagram(hallway, roomA, roomB, roomC, roomD); } public boolean complete() { @@ -311,7 +308,7 @@ public int countNonEmpty() { } public Diagram copy() { - return new Diagram( + return Diagram.create( Arrays.copyOf(this.hallway.amphipods, this.hallway.amphipods.length), Arrays.copyOf(this.roomA.amphipods, this.roomA.amphipods.length), Arrays.copyOf(this.roomB.amphipods, this.roomB.amphipods.length), @@ -567,13 +564,27 @@ List freeRightFromD() { } } - @RequiredArgsConstructor - @ToString static abstract class Move { private final Amphipod room; private final int posFrom; private final int posTo; private final int energy; + + protected Move(final Amphipod room, final int posFrom, final int posTo, final int energy) { + this.room = room; + this.posFrom = posFrom; + this.posTo = posTo; + this.energy = energy; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Move [room=").append(room).append(", posFrom=") + .append(posFrom).append(", posTo=") + .append(posTo).append(", energy=").append(energy).append("]"); + return builder.toString(); + } } static final class MoveFromHallway extends Move { diff --git a/src/main/java/AoC2021_24.java b/src/main/java/AoC2021_24.java index 8e22e095..e4d7b82a 100644 --- a/src/main/java/AoC2021_24.java +++ b/src/main/java/AoC2021_24.java @@ -19,10 +19,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.EqualsAndHashCode; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public class AoC2021_24 extends AoCBase { private static final int DIGITS = 14; @@ -171,13 +167,7 @@ private Program createProgram(final int digit) { private final Map programCache = new HashMap<>(); - @RequiredArgsConstructor - @EqualsAndHashCode - private static final class ProgramParams { - private final long w; - private final long z; - private final int digit; - } + record ProgramParams(long w, long z, int digit) {} private long execProgram(final ProgramParams params) { final Program program = createProgram(params.digit); @@ -327,10 +317,5 @@ public static void main(final String[] args) throws Exception { "add z y" ); - @RequiredArgsConstructor - @ToString - private static final class MonadInstruction { - private final String operator; - private final List operands; - } + record MonadInstruction(String operator, List operands) {} } diff --git a/src/main/java/AoC2022_10.java b/src/main/java/AoC2022_10.java index 70028228..4eb4c841 100644 --- a/src/main/java/AoC2022_10.java +++ b/src/main/java/AoC2022_10.java @@ -14,8 +14,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public class AoC2022_10 extends AoCBase { private static final int PERIOD = 40; private static final int MAX = 220; @@ -261,10 +259,7 @@ public static OpCode fromString(final String value) { } } - @RequiredArgsConstructor - private static final class Instruction { - private final OpCode operation; - private final Optional operand; + record Instruction(OpCode operation, Optional operand) { public static Instruction fromString(final String value) { final String[] splits = value.split(" "); diff --git a/src/main/java/AoC2022_13.java b/src/main/java/AoC2022_13.java index 4588f6a9..2507ace5 100644 --- a/src/main/java/AoC2022_13.java +++ b/src/main/java/AoC2022_13.java @@ -27,7 +27,7 @@ public static final AoC2022_13 createDebug(final List input) { return new AoC2022_13(input, true); } - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({ "unchecked" }) private int compare(final Object _lhs, final Object _rhs) { if (_lhs instanceof final Number lhs && _rhs instanceof final Number rhs) { return lhs.intValue() - rhs.intValue(); @@ -81,7 +81,7 @@ public Integer solvePart2() { .collect(toList()); final var dividers = Set.of(List.of(2), List.of(6)); dividers.forEach(packets::add); - Collections.sort(packets, (p1, p2) -> compare(p1, p2)); + Collections.sort(packets, this::compare); return (int) LongStream.rangeClosed(1, packets.size()) .filter(i -> dividers.contains(packets.get((int) i - 1))) .reduce(1L, (a, b) -> a * b); diff --git a/src/main/java/AoC2022_14.java b/src/main/java/AoC2022_14.java index 6bf7dc70..4047a403 100644 --- a/src/main/java/AoC2022_14.java +++ b/src/main/java/AoC2022_14.java @@ -1,77 +1,53 @@ import static com.github.pareronia.aoc.Utils.toAString; import static java.util.stream.Collectors.toList; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.IntStream; import com.github.pareronia.aoc.geometry.Position; -import com.github.pareronia.aocd.Aocd; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2022_14 extends AoCBase { +public class AoC2022_14 + extends SolutionBase { - private static final Position SOURCE = Position.of(500, 0); + public static final Position SOURCE = Position.of(500, 0); private static final char START = '+'; private static final char EMPTY = ' '; private static final char ROCK = '▒'; private static final char SAND = 'o'; - - private final Set rocks; - private final int minX; - private final int maxX; - private final int maxY; - private final boolean[][] occupied; - private AoC2022_14(final List input, final boolean debug) { + private final List listeners = new ArrayList<>(); + + private AoC2022_14(final boolean debug) { super(debug); - this.rocks = new HashSet<>(); - for (final String line : input) { - final var splits = line.split(" -> "); - IntStream.range(1, splits.length).forEach(i -> { - final var splits1 = splits[i - 1].split(","); - final var splits2 = splits[i].split(","); - final var xs = List.of(splits1, splits2).stream() - .map(a -> a[0]) - .map(Integer::parseInt) - .sorted() - .collect(toList()); - final var ys = List.of(splits1, splits2).stream() - .map(a -> a[1]) - .map(Integer::parseInt) - .sorted() - .collect(toList()); - IntStream.rangeClosed(xs.get(0), xs.get(1)).forEach(x -> - IntStream.rangeClosed(ys.get(0), ys.get(1)).forEach(y -> - this.rocks.add(Position.of(x, y)))); - }); - } - final var stats = this.rocks.stream().mapToInt(Position::getX) - .summaryStatistics(); - this.minX = stats.getMin(); - this.maxX = stats.getMax(); - this.maxY = this.rocks.stream().mapToInt(Position::getY).max().getAsInt(); - this.occupied = new boolean[maxY + 2][maxX + 150]; - rocks.stream().forEach(p -> occupied[p.getY()][p.getX()] = true); } - public static final AoC2022_14 create(final List input) { - return new AoC2022_14(input, false); + public static final AoC2022_14 create() { + return new AoC2022_14(false); } - public static final AoC2022_14 createDebug(final List input) { - return new AoC2022_14(input, true); + public static final AoC2022_14 createDebug() { + return new AoC2022_14(true); } - Position drop(final int maxY) { + public void addListener(final Listener listener) { + this.listeners.add(listener); + } + + Optional drop(final boolean[][] occupied, final int maxY) { Position curr = SOURCE; while (true) { final int y = curr.getY() + 1; final Position p = curr; for (final int x : List.of(0, -1 , 1)) { try { - if (!this.occupied[y][curr.getX() + x]) { + if (!occupied[y][curr.getX() + x]) { curr = Position.of(curr.getX() + x, y); break; } @@ -79,45 +55,49 @@ Position drop(final int maxY) { } } if (curr.equals(p)) { - return curr; + return Optional.of(curr); } if (curr.getY() > maxY) { - return null; + return Optional.empty(); } } } - private int solve(final int maxY) { + private int solve(final Cave cave, final int maxY) { + this.listeners.forEach(l -> l.start(cave)); int cnt = 0; while (true) { - final Position p = drop(maxY); - if (p == null) { - break; - } - this.occupied[p.getY()][p.getX()] = true; - cnt++; - if (p.equals(SOURCE)) { + final Optional p = drop(cave.occupied, maxY); + if (p.isPresent()) { + cave.occupied[p.get().getY()][p.get().getX()] = true; + this.listeners.forEach(l -> l.stateUpdated(cave)); + cnt++; + if (p.get().equals(SOURCE)) { + break; + } + } else { break; } } + this.listeners.forEach(Listener::close); return cnt; } - void draw() { + void draw(final Cave cave) { if (!this.debug) { return; } - final var statsX = this.rocks.stream().mapToInt(Position::getX) - .summaryStatistics(); - final var statsY = this.rocks.stream().mapToInt(Position::getY) + final var statsX = IntStream.range(0, cave.occupied.length) + .flatMap(y -> IntStream.range(0, cave.occupied[y].length) + .filter(x -> cave.occupied[y][x])) .summaryStatistics(); - IntStream.rangeClosed(SOURCE.getY(), statsY.getMax()).forEach(y -> { + IntStream.rangeClosed(SOURCE.getY(), cave.maxY).forEach(y -> { final var line = IntStream.rangeClosed(statsX.getMin(), statsX.getMax()) .mapToObj(x -> { final Position pos = Position.of(x, y); - if (this.rocks.contains(pos)) { + if (cave.rocks.contains(pos)) { return ROCK; - } else if (this.occupied[pos.getY()][pos.getX()]) { + } else if (cave.occupied[pos.getY()][pos.getX()]) { return SAND; } else if (SOURCE.equals(pos)) { return START; @@ -130,36 +110,79 @@ void draw() { } @Override - public Integer solvePart1() { - final int ans = solve(this.maxY); - draw(); + protected Cave parseInput(final List inputs) { + return Cave.fromInput(inputs); + } + + @Override + public Integer solvePart1(final Cave cave) { + final int ans = solve(cave, cave.maxY); + draw(cave); return ans; } @Override - public Integer solvePart2() { - final int max = this.maxY + 2; - IntStream.rangeClosed(minX - max, maxX + max) - .forEach(x -> this.rocks.add(Position.of(x, max))); - final int ans = solve(max); - draw(); + public Integer solvePart2(final Cave cave) { + final Cave newCave = Cave.withRocks(cave.rocks); + final int ans = solve(newCave, cave.maxY + 2); + draw(newCave); return ans; } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "24"), + @Sample(method = "part2", input = TEST, expected = "93"), + }) public static void main(final String[] args) throws Exception { - assert AoC2022_14.createDebug(TEST).solvePart1() == 24; - assert AoC2022_14.createDebug(TEST).solvePart2() == 93; - - final Puzzle puzzle = Aocd.puzzle(2022, 14); - final List inputData = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2022_14.create(inputData)::solvePart1), - () -> lap("Part 2", AoC2022_14.create(inputData)::solvePart2) - ); + AoC2022_14.create().run(); } - private static final List TEST = splitLines(""" + protected static final String TEST = """ 498,4 -> 498,6 -> 496,6 503,4 -> 502,4 -> 502,9 -> 494,9 - """); + """; + + record Cave(Set rocks, boolean[][] occupied, int maxY) { + + public static Cave withRocks(final Set rocks) { + final int maxX = rocks.stream().mapToInt(Position::getX).max().getAsInt(); + final int maxY = rocks.stream().mapToInt(Position::getY).max().getAsInt(); + final var occupied = new boolean[maxY + 2][maxX + 150]; + rocks.stream().forEach(p -> occupied[p.getY()][p.getX()] = true); + return new Cave(rocks, occupied, maxY); + } + + public static Cave fromInput(final List input) { + final Set rocks = new HashSet<>(); + for (final String line : input) { + final var splits = line.split(" -> "); + IntStream.range(1, splits.length).forEach(i -> { + final var splits1 = splits[i - 1].split(","); + final var splits2 = splits[i].split(","); + final var xs = List.of(splits1, splits2).stream() + .map(a -> a[0]) + .map(Integer::parseInt) + .sorted() + .collect(toList()); + final var ys = List.of(splits1, splits2).stream() + .map(a -> a[1]) + .map(Integer::parseInt) + .sorted() + .collect(toList()); + IntStream.rangeClosed(xs.get(0), xs.get(1)).forEach(x -> + IntStream.rangeClosed(ys.get(0), ys.get(1)).forEach(y -> + rocks.add(Position.of(x, y)))); + }); + } + return Cave.withRocks(rocks); + } + } + + public interface Listener { + void start(Cave cave); + + void stateUpdated(Cave cave); + + default void close() {} + } } diff --git a/src/main/java/AoC2022_15.java b/src/main/java/AoC2022_15.java index 6b5d270e..32cd57d4 100644 --- a/src/main/java/AoC2022_15.java +++ b/src/main/java/AoC2022_15.java @@ -1,104 +1,117 @@ -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import com.github.pareronia.aoc.IterTools; import com.github.pareronia.aoc.RangeInclusive; +import com.github.pareronia.aoc.StringOps; import com.github.pareronia.aoc.Utils; import com.github.pareronia.aoc.geometry.Position; -import com.github.pareronia.aocd.Aocd; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.SolutionBase; -public class AoC2022_15 extends AoCBase { +public class AoC2022_15 extends SolutionBase { - private final Set sensors; - private final Map> beacons; - - private AoC2022_15(final List input, final boolean debug) { + private AoC2022_15(final boolean debug) { super(debug); - this.sensors = new HashSet<>(); - this.beacons = new HashMap<>(); - for (final String line : input) { - final int[] nums = Utils.integerNumbers(line); - final var sensor = new Sensor(nums[0], nums[1], nums[2], nums[3]); - this.sensors.add(sensor); - this.beacons.computeIfAbsent(nums[3], k -> new HashSet<>()).add(nums[2]); - } - log(this.sensors); - log(this.beacons); } - public static final AoC2022_15 create(final List input) { - return new AoC2022_15(input, false); + public static final AoC2022_15 create() { + return new AoC2022_15(false); } - public static final AoC2022_15 createDebug(final List input) { - return new AoC2022_15(input, true); + public static final AoC2022_15 createDebug() { + return new AoC2022_15(true); } - private Deque> getRanges(final int y) { - final var ranges = new HashSet>(); - for (final var sensor : this.sensors) { - if (Math.abs(sensor.y - y) > sensor.distanceToBeacon) { + @Override + protected Input parseInput(final List inputs) { + final Map sensors = new HashMap<>(); + final Set beacons = new HashSet<>(); + for (final String line : inputs) { + final int[] nums = Utils.integerNumbers(line); + final Position s = Position.of(nums[0], nums[1]); + final Position b = Position.of(nums[2], nums[3]); + sensors.put(s, s.manhattanDistance(b)); + beacons.add(b); + } + return new Input(sensors, beacons); + } + + private int solve1(final Input input, final int y) { + final var ranges = new ArrayList>(); + for (final Entry entry : input.sensors.entrySet()) { + final Position s = entry.getKey(); + final int md = entry.getValue(); + final int dy = Math.abs(s.getY() - y); + if (dy > md) { continue; } - ranges.add(sensor.xRangeAt(y)); + ranges.add(RangeInclusive.between( + s.getX() - md + dy, s.getX() + md - dy)); } - return RangeMerger.mergeRanges(ranges); - } - - private int solve1(final int y) { - final Set beaconsXs = this.beacons.get(y); - return (int) getRanges(y).stream() - .mapToLong(r -> r.getMaximum() - r.getMinimum() + 1 - - beaconsXs.stream().filter(r::contains).count()) + return (int) RangeInclusive.mergeRanges(ranges).stream() + .mapToLong(r -> + r.getMaximum() - r.getMinimum() + 1 + - input.beacons.stream() + .filter(b -> b.getY() == y && r.contains(b.getX())) + .count()) .sum(); } - private long solve2(final int max) { - for (int y = max; y >= 0; y--) { - final var ranges = getRanges(y); - for (int x = 0; x <= max; x++) { - for (final var merged : ranges) { - if (merged.isAfter(x)) { - log(Position.of(x, y)); - return x * 4_000_000L + y; - } - x = merged.getMaximum() + 1; - } - } + private long solve2(final Map sensors, final int max) { + final Set acoeffs = new HashSet<>(); + final Set bcoeffs = new HashSet<>(); + for (final Entry entry : sensors.entrySet()) { + final Position s = entry.getKey(); + final int md = entry.getValue(); + acoeffs.add(s.getY() - s.getX() + md + 1); + acoeffs.add(s.getY() - s.getX() - md - 1); + bcoeffs.add(s.getX() + s.getY() + md + 1); + bcoeffs.add(s.getX() + s.getY() - md - 1); } - throw new IllegalStateException("Unsolvable"); + return IterTools.product(acoeffs, bcoeffs).stream() + .filter(ab -> ab.first() < ab.second()) + .filter(ab -> (ab.second() - ab.first()) % 2 == 0) + .map(ab -> Position.of( + (ab.second() - ab.first()) / 2, + (ab.first() + ab.second()) / 2)) + .filter(p -> 0 < p.getX() && p.getX() < max) + .filter(p -> 0 < p.getY() && p.getY() < max) + .filter(p -> sensors.keySet().stream().allMatch( + s -> p.manhattanDistance(s) > sensors.get(s))) + .mapToLong(p -> 4_000_000L * p.getX() + p.getY()) + .findFirst().orElseThrow(); } @Override - public Integer solvePart1() { - return solve1(2_000_000); + public Integer solvePart1(final Input input) { + return solve1(input, 2_000_000); } @Override - public Long solvePart2() { - return solve2(4_000_000); + public Long solvePart2(final Input input) { + return solve2(input.sensors, 4_000_000); + } + + record Input(Map sensors, Set beacons) {} + + @Override + public void samples() { + final AoC2022_15 test = AoC2022_15.createDebug(); + final Input input = test.parseInput(StringOps.splitLines(TEST)); + assert test.solve1(input, 10) == 26; + assert test.solve2(input.sensors, 20) == 56_000_011L; } public static void main(final String[] args) throws Exception { - assert AoC2022_15.createDebug(TEST).solve1(10) == 26; - assert AoC2022_15.createDebug(TEST).solve2(20) == 56_000_011L; - - final Puzzle puzzle = Aocd.puzzle(2022, 15); - final List inputData = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2022_15.create(inputData)::solvePart1), - () -> lap("Part 2", AoC2022_15.create(inputData)::solvePart2) - ); + AoC2022_15.create().run(); } - private static final List TEST = splitLines(""" + private static final String TEST = """ Sensor at x=2, y=18: closest beacon is at x=-2, y=15 Sensor at x=9, y=16: closest beacon is at x=10, y=16 Sensor at x=13, y=2: closest beacon is at x=15, y=3 @@ -113,50 +126,5 @@ public static void main(final String[] args) throws Exception { Sensor at x=16, y=7: closest beacon is at x=15, y=3 Sensor at x=14, y=3: closest beacon is at x=15, y=3 Sensor at x=20, y=1: closest beacon is at x=15, y=3 - """); - - private static final record Sensor(int x, int y, int distanceToBeacon) { - public Sensor(final int x, final int y, final int beaconX, final int beaconY) { - this(x, y, Math.abs(beaconX - x) + Math.abs(beaconY - y)); - } - - public RangeInclusive xRangeAt(final int y) { - final int dy = Math.abs(this.y - y); - assert dy <= distanceToBeacon; - return RangeInclusive.between( - x - distanceToBeacon + dy, - x + distanceToBeacon - dy); - } - } - - static final class RangeMerger { - - public static Deque> mergeRanges(final Set> ranges) { - final var m = new ArrayDeque>(); - final var sorted = new ArrayList<>(ranges); - Collections.sort(sorted, (r1, r2) -> { - final int first = Integer.compare(r1.getMinimum(), r2.getMinimum()); - if (first == 0) { - return Integer.compare(r1.getMaximum(), r2.getMaximum()); - } - return first; - }); - for (final var range : sorted) { - if (m.isEmpty()) { - m.addLast(range); - continue; - } - final var last = m.peekLast(); - if (range.isOverlappedBy(last)) { - m.removeLast(); - m.add(RangeInclusive.between( - last.getMinimum(), - Math.max(last.getMaximum(), range.getMaximum()))); - } else { - m.addLast(range); - } - } - return m; - } - } + """; } diff --git a/src/main/java/AoC2022_16.java b/src/main/java/AoC2022_16.java index 0ae23893..e6165186 100644 --- a/src/main/java/AoC2022_16.java +++ b/src/main/java/AoC2022_16.java @@ -9,12 +9,10 @@ import java.util.Set; import java.util.stream.IntStream; -import com.github.pareronia.aoc.graph.AStar; +import com.github.pareronia.aoc.graph.Dijkstra; import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; - public class AoC2022_16 extends AoCBase { private final String[] valves; @@ -68,7 +66,6 @@ public static final AoC2022_16 createDebug(final List input) { return new AoC2022_16(input, true); } - @RequiredArgsConstructor private final class DFS { private final int[][] distances; private final int maxTime; @@ -78,6 +75,12 @@ private final class DFS { private int maxFlow = 0; private int cnt = 0; + public DFS(final int[][] distances, final int maxTime, final boolean sample) { + this.distances = distances; + this.maxTime = maxTime; + this.sample = sample; + } + public void dfs(final int start, final int time) { cnt++; for (int i = 0; i < valves.length; i++) { @@ -110,11 +113,11 @@ private int[][] getDistances() { final int size = valves.length; final var distances = new int[size][size]; for (final int i : relevantValves) { - final var result = AStar.execute( + final var result = Dijkstra.execute( i, v -> false, v -> this.tunnels[v].stream(), - v -> 1); + (v, w) -> 1); for (final int j : relevantValves) { distances[i][j] = (int) result.getDistance(j); } diff --git a/src/main/java/AoC2022_17.java b/src/main/java/AoC2022_17.java index 2f779f6a..fe02d1b3 100644 --- a/src/main/java/AoC2022_17.java +++ b/src/main/java/AoC2022_17.java @@ -5,82 +5,62 @@ import static java.util.stream.Collectors.toSet; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Supplier; import java.util.stream.IntStream; import java.util.stream.Stream; +import com.github.pareronia.aoc.AssertUtils; import com.github.pareronia.aoc.Utils; import com.github.pareronia.aoc.geometry.Direction; import com.github.pareronia.aoc.geometry.Position; import com.github.pareronia.aoc.geometry.Vector; -import com.github.pareronia.aocd.Aocd; -import com.github.pareronia.aocd.Puzzle; - -import lombok.EqualsAndHashCode; -import lombok.RequiredArgsConstructor; -import lombok.ToString; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; // TODO: needs more cleanup -public class AoC2022_17 extends AoCBase { +public class AoC2022_17 extends SolutionBase, Long, Long> { private static final int OFFSET_X = 2; private static final int OFFSET_Y = 3; private static final int WIDTH = 7; - private static final int KEEP_ROWS = 40; + private static final int KEEP_ROWS = 55; private static final int LOOP_TRESHOLD = 3_000; private static final Set FLOOR = IntStream.range(0, WIDTH) .mapToObj(x -> Position.of(x, -1)).collect(toSet()); - private static final List> SHAPES = List.of( - // #### - Set.of(Position.of(0, 0), Position.of(1, 0), Position.of(2, 0), Position.of(3, 0)), - // # - // ### - // # - Set.of(Position.of(1, 2), Position.of(0, 1), Position.of(1, 1), Position.of(2, 1), Position.of(1, 0)), - // # - // # - // ### - Set.of(Position.of(2, 2), Position.of(2, 1), Position.of(0, 0), Position.of(1, 0), Position.of(2, 0)), - // # - // # - // # - // # - Set.of(Position.of(0, 0), Position.of(0, 1), Position.of(0, 2), Position.of(0, 3)), - // ## - // ## - Set.of(Position.of(0, 0), Position.of(0, 1), Position.of(1, 0), Position.of(1, 1)) - ); - private final List jets; - private final Map> states; - - private AoC2022_17(final List input, final boolean debug) { + private AoC2022_17(final boolean debug) { super(debug); - this.jets = Utils.asCharacterStream(input.get(0)) - .map(Direction::fromChar) - .collect(toList()); - this.states = new HashMap<>(); } - public static final AoC2022_17 create(final List input) { - return new AoC2022_17(input, false); + public static final AoC2022_17 create() { + return new AoC2022_17(false); } - public static final AoC2022_17 createDebug(final List input) { - return new AoC2022_17(input, true); + public static final AoC2022_17 createDebug() { + return new AoC2022_17(true); } - public static final AoC2022_17 createTrace(final List input) { - final AoC2022_17 puzzle = new AoC2022_17(input, true); + public static final AoC2022_17 createTrace() { + final AoC2022_17 puzzle = new AoC2022_17(true); puzzle.setTrace(true); return puzzle; } + @Override + protected List parseInput(final List inputs) { + return Utils.asCharacterStream(inputs.get(0)) + .map(Direction::fromChar) + .collect(toList()); + } + private void draw(final Stack stack) { if (!this.trace) { return; @@ -97,10 +77,11 @@ private void draw(final Stack stack) { private State drop( final int dropIndex, final Stack stack, + final Map> states, final ShapeSupplier shapeSupplier, final JetSupplier jetSupplier ) { - final var shape = shapeSupplier.get(); + final Shape shape = shapeSupplier.get(); final var start = new Rock(shape.idx, shape.shape) .move(Vector.of(OFFSET_X, stack.getTop() + OFFSET_Y)); trace(() -> start); @@ -108,10 +89,11 @@ private State drop( State state; int cnt = 0; while (true) { - final var jet = jetSupplier.get(); + AssertUtils.assertTrue(cnt < 10000, () -> "infinite loop"); + final Direction jet = jetSupplier.get(); state = new State(rock.idx, stack.getTopsNormalised(), jet); if (cnt++ == 1) { - this.states.computeIfAbsent(state, k -> new ArrayList<>()) + states.computeIfAbsent(state, k -> new ArrayList<>()) .add(new Cycle(dropIndex, stack.getTop())); } trace(() -> "move " + jet.toString()); @@ -137,24 +119,25 @@ private State drop( return state; } - private Long solve(final long requestedDrops) { + private Long solve(final List jets, final long requestedDrops) { final var stack = new Stack(FLOOR); - final var jetSupplier = new JetSupplier(this.jets); + final var states = new HashMap>(); + final var jetSupplier = new JetSupplier(jets); final var shapeSupplier = new ShapeSupplier(); int drops = 0; State state; while (true) { - state = drop(drops++, stack, shapeSupplier, jetSupplier); + state = drop(drops++, stack, states, shapeSupplier, jetSupplier); if (drops == requestedDrops) { return (long) stack.getTop(); } if (drops >= LOOP_TRESHOLD - && this.states.getOrDefault(state, List.of()).size() > 1) { + && states.getOrDefault(state, List.of()).size() > 1) { break; } } log("drops: " + drops); - final List cycles = this.states.get(state); + final List cycles = states.get(state); final int loopsize = cycles.get(1).cycle - cycles.get(0).cycle; log("loopsize: " + loopsize); final int diff = cycles.get(1).top - cycles.get(0).top; @@ -162,36 +145,30 @@ private Long solve(final long requestedDrops) { final long loops = Math.floorDiv(requestedDrops - drops, loopsize); final long left = requestedDrops - (drops + loops * loopsize); for (int i = 0; i < left; i++) { - drop(drops++, stack, shapeSupplier, jetSupplier); + drop(drops++, stack, states, shapeSupplier, jetSupplier); } return stack.getTop() + loops * diff; } @Override - public Long solvePart1() { - return solve(2022L); + public Long solvePart1(final List jets) { + return solve(jets, 2022L); } @Override - public Long solvePart2() { - return solve(1_000_000_000_000L); + public Long solvePart2(final List jets) { + return solve(jets, 1_000_000_000_000L); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "3068"), + @Sample(method = "part2", input = TEST, expected = "1514285714288"), + }) public static void main(final String[] args) throws Exception { - assert AoC2022_17.createDebug(TEST).solvePart1() == 3068; - assert AoC2022_17.createDebug(TEST).solvePart2() == 1_514_285_714_288L; - - final Puzzle puzzle = Aocd.puzzle(2022, 17); - final List inputData = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2022_17.create(inputData)::solvePart1), - () -> lap("Part 2", AoC2022_17.create(inputData)::solvePart2) - ); + AoC2022_17.create().run(); } - private static final List TEST = splitLines( - ">>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>" - ); + private static final String TEST = ">>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>"; private static final class Stack { private Set positions; @@ -236,7 +213,7 @@ public Map getTops(final Set positions) { return positions.stream() .collect(groupingBy( Position::getX, - mapping(p -> p.getY(), reducing(Integer.MIN_VALUE, Math::max)))); + mapping(Position::getY, reducing(Integer.MIN_VALUE, Math::max)))); } public boolean contains(final Position p) { @@ -266,6 +243,28 @@ private Stream blocks() { private static final record Shape(int idx, Set shape) { } private static final class ShapeSupplier implements Supplier { + + private static final List> SHAPES = List.of( + // #### + Set.of(Position.of(0, 0), Position.of(1, 0), Position.of(2, 0), Position.of(3, 0)), + // # + // ### + // # + Set.of(Position.of(1, 2), Position.of(0, 1), Position.of(1, 1), Position.of(2, 1), Position.of(1, 0)), + // # + // # + // ### + Set.of(Position.of(2, 2), Position.of(2, 1), Position.of(0, 0), Position.of(1, 0), Position.of(2, 0)), + // # + // # + // # + // # + Set.of(Position.of(0, 0), Position.of(0, 1), Position.of(0, 2), Position.of(0, 3)), + // ## + // ## + Set.of(Position.of(0, 0), Position.of(0, 1), Position.of(1, 0), Position.of(1, 1)) + ); + private int idx = 0; @Override @@ -275,24 +274,44 @@ public Shape get() { } } - @RequiredArgsConstructor public static final class JetSupplier implements Supplier { private final List jets; private int idx = 0; + public JetSupplier(final List jets) { + this.jets = jets; + } + @Override public Direction get() { return this.jets.get(idx++ % jets.size()); } } - @RequiredArgsConstructor - @EqualsAndHashCode - @ToString - private static final class State { - private final int shape; - private final int[] tops; - private final Direction jet; + record State(int shape, int[] tops, Direction jet) { + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(tops); + return prime * result + Objects.hash(jet, shape); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final State other = (State) obj; + return jet == other.jet && shape == other.shape && Arrays.equals(tops, other.tops); + } } private static final record Cycle(int cycle, int top) { } diff --git a/src/main/java/AoC2022_19.java b/src/main/java/AoC2022_19.java index 4cd5768e..3b8b187f 100644 --- a/src/main/java/AoC2022_19.java +++ b/src/main/java/AoC2022_19.java @@ -9,37 +9,37 @@ import java.util.stream.IntStream; import com.github.pareronia.aoc.Utils; -import com.github.pareronia.aocd.Aocd; -import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; -import lombok.AccessLevel; -import lombok.EqualsAndHashCode; -import lombok.RequiredArgsConstructor; - -public class AoC2022_19 extends AoCBase { - - private final List blueprints; +public class AoC2022_19 + extends SolutionBase, Integer, Integer> { - private AoC2022_19(final List input, final boolean debug) { + private AoC2022_19(final boolean debug) { super(debug); - this.blueprints = input.stream() - .map(Utils::naturalNumbers) - .map(nums -> new Blueprint(nums[0], nums[1], nums[2], nums[3], - nums[4], nums[5], nums[6])) - .collect(toList()); } - public static final AoC2022_19 create(final List input) { - return new AoC2022_19(input, false); + public static final AoC2022_19 create() { + return new AoC2022_19(false); } - public static final AoC2022_19 createDebug(final List input) { - return new AoC2022_19(input, true); + public static final AoC2022_19 createDebug() { + return new AoC2022_19(true); } + @Override + protected List parseInput(final List inputs) { + return inputs.stream() + .map(Utils::naturalNumbers) + .map(nums -> Blueprint.create(nums[0], nums[1], nums[2], nums[3], + nums[4], nums[5], nums[6])) + .collect(toList()); + } + private int solve(final Blueprint blueprint, final int maxTime) { final Deque q = new ArrayDeque<>(); - q.add(new State(0, 0, 1, 0, 0, 0, 0, 0, 0)); + q.add(State.create(0, 0, 1, 0, 0, 0, 0, 0, 0)); final Set seen = new HashSet<>(); int best = Integer.MIN_VALUE; while (!q.isEmpty()) { @@ -79,33 +79,29 @@ private int solve(final Blueprint blueprint, final int maxTime) { } @Override - public Integer solvePart1() { - return this.blueprints.parallelStream() + public Integer solvePart1(final List blueprints) { + return blueprints.parallelStream() .mapToInt(bp -> bp.id * solve(bp, 24)) .sum(); } @Override - public Integer solvePart2() { - return this.blueprints.parallelStream() + public Integer solvePart2(final List blueprints) { + return blueprints.parallelStream() .limit(3) .mapToInt(bp -> solve(bp, 32)) .reduce(1, (a, b) -> a * b); } + @Samples({ + @Sample(method = "part1", input = TEST, expected = "33"), +// @Sample(method = "part2", input = TEST, expected = "3472"), + }) public static void main(final String[] args) throws Exception { - assert AoC2022_19.createDebug(TEST).solvePart1() == 33; -// assert AoC2022_19.createDebug(TEST).solvePart2() == 56 * 62; - - final Puzzle puzzle = Aocd.puzzle(2022, 19); - final List inputData = puzzle.getInputData(); - puzzle.check( - () -> lap("Part 1", AoC2022_19.create(inputData)::solvePart1), - () -> lap("Part 2", AoC2022_19.create(inputData)::solvePart2) - ); + AoC2022_19.create().run(); } - private static final List TEST = splitLines(""" + private static final String TEST = """ Blueprint 1:\ Each ore robot costs 4 ore.\ Each clay robot costs 2 ore.\ @@ -117,57 +113,53 @@ public static void main(final String[] args) throws Exception { Each clay robot costs 3 ore.\ Each obsidian robot costs 3 ore and 8 clay.\ Each geode robot costs 3 ore and 12 obsidian.\ - """); + """; - private static final class Blueprint { - private final int id; - private final int oreCost; - private final int clayCost; - private final int obisidanOreCost; - private final int obisidanClayCost; - private final int geodeOreCost; - private final int geodeObisidanCost; - private final int maxOre; + record Blueprint( + int id, + int oreCost, + int clayCost, + int obisidanOreCost, + int obisidanClayCost, + int geodeOreCost, + int geodeObisidanCost, + int maxOre + ) { - public Blueprint( + public static Blueprint create( final int id, final int oreCost, final int clayCost, final int obisidanOreCost, final int obisidanClayCost, final int geodeOreCost, final int geodeObisidanCost ) { - this.id = id; - this.oreCost = oreCost; - this.clayCost = clayCost; - this.obisidanOreCost = obisidanOreCost; - this.obisidanClayCost = obisidanClayCost; - this.geodeOreCost = geodeOreCost; - this.geodeObisidanCost = geodeObisidanCost; - this.maxOre = IntStream.of( + final int maxOre = IntStream.of( oreCost, clayCost, geodeOreCost, obisidanOreCost) .max().orElseThrow(); + return new Blueprint(id, oreCost, clayCost, obisidanOreCost, + obisidanClayCost, geodeOreCost, geodeObisidanCost, maxOre); } } - @RequiredArgsConstructor(access = AccessLevel.PRIVATE) - @EqualsAndHashCode - private static final class State { - private static final double CUSHION = 1.5d; - - private final byte time; - private final byte oreStore; - private final byte oreRobot; - private final byte clayStore; - private final byte clayRobot; - private final byte obisidianStore; - private final byte obsidianRobot; - private final byte geodeStore; - private final byte geodeRobot; + record State( + byte time, + byte oreStore, + byte oreRobot, + byte clayStore, + byte clayRobot, + byte obisidianStore, + byte obsidianRobot, + byte geodeStore, + byte geodeRobot + ) { + private static final double CUSHION = 1.51d; - public State(final int time, final int oreStore, final int oreRobot, + public static State create( + final int time, final int oreStore, final int oreRobot, final int clayStore, final int clayRobot, final int obisidianStore, final int obsidianRobot, final int geodStore, final int geodRobot ) { - this( (byte) time, + return new State( + (byte) time, (byte) oreStore, (byte) oreRobot, (byte) clayStore, @@ -213,7 +205,7 @@ public boolean canBuildClayRobot(final Blueprint bp) { } public State buildGeodeRobot(final Blueprint blueprint) { - return new State( + return State.create( this.time + 1, this.oreStore + this.oreRobot - blueprint.geodeOreCost, this.oreRobot, @@ -286,7 +278,7 @@ private State buildNext( final int oreStore, final int oreRobot, final int clayStore, final int clayRobot, final int obisidianStore, final int obsidianRobot, final int geodeStore, final int geodeRobot) { - return new State( + return State.create( this.time + 1, Math.min(oreStore, cushion(maxOre)), oreRobot, diff --git a/src/main/java/AoC2022_22.java b/src/main/java/AoC2022_22.java index e2e32346..23f8ae1e 100644 --- a/src/main/java/AoC2022_22.java +++ b/src/main/java/AoC2022_22.java @@ -14,9 +14,6 @@ import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public class AoC2022_22 extends AoCBase { private static final Pattern REGEX = Pattern.compile("([LR])([0-9]+)"); @@ -239,10 +236,5 @@ public static void main(final String[] args) throws Exception { "10R5L5R10L4R5L5" ); - @RequiredArgsConstructor - @ToString - private static final class Move { - private final Turn turn; - private final int steps; - } + record Move(Turn turn, int steps) {} } diff --git a/src/main/java/AoC2023_06.java b/src/main/java/AoC2023_06.java index e3d805c4..d3b26fef 100644 --- a/src/main/java/AoC2023_06.java +++ b/src/main/java/AoC2023_06.java @@ -7,7 +7,6 @@ import java.util.stream.IntStream; import java.util.stream.LongStream; -import com.github.pareronia.aoc.Utils; import com.github.pareronia.aoc.solution.Sample; import com.github.pareronia.aoc.solution.Samples; import com.github.pareronia.aoc.solution.SolutionBase; @@ -36,7 +35,7 @@ protected List parseInput(final List inputs) { .map(Long::parseLong) .toList()) .toArray(List[]::new); - return Utils.stream(zip(values[0], values[1]).iterator()) + return zip(values[0], values[1]).stream() .map(z -> new Race(z.first(), z.second())) .toList(); } @@ -73,7 +72,7 @@ public Long solvePart2(final List races) { final List> fs = List.> of(Race::time, Race::distance); final long[] a = IntStream.rangeClosed(0, 1) - .mapToLong(i -> Long.valueOf(races.stream() + .mapToLong(i -> Long.parseLong(races.stream() .map(r -> fs.get(i).apply(r)) .map(String::valueOf) .collect(joining()))) diff --git a/src/main/java/AoC2023_07.java b/src/main/java/AoC2023_07.java index 7bcb8b60..43d732c8 100644 --- a/src/main/java/AoC2023_07.java +++ b/src/main/java/AoC2023_07.java @@ -75,12 +75,12 @@ record Hand( public static Hand fromInput(final String line) { final Function getValue = cards -> { final List> mc - = new Counter<>(Utils.asCharacterStream(cards).toList()).mostCommon(); + = new Counter<>(Utils.asCharacterStream(cards)).mostCommon(); return (int) (2 * mc.get(0).count() + (mc.size() > 1 ? mc.get(1).count() : 0)); }; final Function withJokers = cards -> { final Counter c = new Counter<>( - Utils.asCharacterStream(cards).filter(ch -> ch != JOKER).toList()); + Utils.asCharacterStream(cards).filter(ch -> ch != JOKER)); if (c.isEmpty()) { return BEST; } diff --git a/src/main/java/AoC2023_09.java b/src/main/java/AoC2023_09.java index 259a3a52..8e471089 100644 --- a/src/main/java/AoC2023_09.java +++ b/src/main/java/AoC2023_09.java @@ -1,6 +1,5 @@ import static com.github.pareronia.aoc.IterTools.zip; import static com.github.pareronia.aoc.Utils.last; -import static com.github.pareronia.aoc.Utils.stream; import java.util.ArrayDeque; import java.util.ArrayList; @@ -40,7 +39,7 @@ private int solve(final List lineIn) { List line = new ArrayList<>(lineIn); final Deque tails = new ArrayDeque<>(List.of(last(line))); while (!line.stream().allMatch(tails.peekLast()::equals)) { - line = stream(zip(line, line.subList(1, line.size())).iterator()) + line = zip(line, line.subList(1, line.size())).stream() .map(z -> z.second() - z.first()) .toList(); tails.addLast(last(line)); diff --git a/src/main/java/AoC2023_14.java b/src/main/java/AoC2023_14.java new file mode 100644 index 00000000..447e49f1 --- /dev/null +++ b/src/main/java/AoC2023_14.java @@ -0,0 +1,148 @@ +import static com.github.pareronia.aoc.Utils.last; +import static java.util.stream.Collectors.toSet; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.github.pareronia.aoc.CharGrid; +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2023_14 + extends SolutionBase { + + private AoC2023_14(final boolean debug) { + super(debug); + } + + public static AoC2023_14 create() { + return new AoC2023_14(false); + } + + public static AoC2023_14 createDebug() { + return new AoC2023_14(true); + } + + @Override + protected CharGrid parseInput(final List inputs) { + return CharGrid.from(inputs); + } + + private Set tiltUp(final CharGrid grid) { + final Set os = new HashSet<>(); + for (final int c : grid.colIndices()) { + Cell cell = Cell.at(0, c); + final Iterator it = grid.getCellsS(cell).iterator(); + int lastCube = 0; + int count = 0; + while (true) { + if (grid.getValue(cell) == '#') { + lastCube = cell.getRow() + 1; + count = 0; + } else if (grid.getValue(cell) == 'O') { + os.add(Cell.at(lastCube + count, c)); + count++; + } + if (!it.hasNext()) { + break; + } + cell = it.next(); + } + } + return os; + } + + private void redraw(final CharGrid grid, final Set os) { + grid.getCells().forEach(cell -> { + final char val = grid.getValue(cell); + if (os.contains(cell)) { + grid.setValue(cell, 'O'); + } else if (val != '#') { + grid.setValue(cell, '.'); + } + }); + } + + private int calcLoad(final CharGrid grid) { + return grid.getAllEqualTo('O') + .mapToInt(o -> grid.getHeight() - o.getRow()) + .sum(); + } + + private CharGrid spinCycle(CharGrid grid) { + for (int i = 0; i < 4; i++) { + this.redraw(grid, this.tiltUp(grid)); + grid = grid.rotate(); + } + return grid; + } + + @Override + public Integer solvePart1(final CharGrid gridIn) { + final CharGrid grid = gridIn.doClone(); + this.redraw(grid, this.tiltUp(grid)); + return this.calcLoad(grid); + } + + @Override + public Integer solvePart2(final CharGrid grid) { + CharGrid g = grid.doClone(); + final Map, List> map = new HashMap<>(); + final int total = 1_000_000_000; + int cycles = 0; + while (true) { + cycles++; + g = this.spinCycle(g); + final Set os = g.getAllEqualTo('O').collect(toSet()); + map.computeIfAbsent(os, k -> new ArrayList<>()).add(cycles); + if (cycles > 100 && map.getOrDefault(os, List.of()).size() > 1) { + final List cycle = map.get(os); + final int period = last(cycle, 1) - last(cycle, 2); + final int loops = Math.floorDiv(total - cycles, period); + final int left = total - (cycles + loops * period); + log("cycles: %d, cycle: %s, period: %d, loops: %d, left: %d" + .formatted(cycles, cycle, period, loops, left)); + assert cycles + loops * period + left == total; + for (int i = 0; i < left; i++) { + g = this.spinCycle(g); + } + return this.calcLoad(g); + } + if (cycles > 1000) { + throw new IllegalStateException("Unsolvable"); + } + } + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "136"), + @Sample(method = "part2", input = TEST, expected = "64"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2023_14.create().run(); + } + + private static final String TEST = """ + O....#.... + O.OO#....# + .....##... + OO.#O....O + .O.....O#. + O.#..O.#.# + ..O..#O..O + .......O.. + #....###.. + #OO..#.... + """; +} diff --git a/src/main/java/AoC2023_15.java b/src/main/java/AoC2023_15.java new file mode 100644 index 00000000..ec2ec1ac --- /dev/null +++ b/src/main/java/AoC2023_15.java @@ -0,0 +1,106 @@ +import static com.github.pareronia.aoc.IterTools.enumerateFrom; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.Utils; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2023_15 + extends SolutionBase, Integer, Integer> { + + private AoC2023_15(final boolean debug) { + super(debug); + } + + public static AoC2023_15 create() { + return new AoC2023_15(false); + } + + public static AoC2023_15 createDebug() { + return new AoC2023_15(true); + } + + @Override + protected List parseInput(final List inputs) { + return Arrays.asList(inputs.get(0).split(",")); + } + + private int hash(final String s) { + return Utils.asCharacterStream(s) + .mapToInt(ch -> ch) + .reduce(0, (acc, ch) -> ((acc + ch) * 17) % 256); + } + + @Override + public Integer solvePart1(final List steps) { + return steps.stream() + .mapToInt(this::hash) + .sum(); + } + + @Override + public Integer solvePart2(final List steps) { + final Boxes boxes = new Boxes(); + steps.forEach(step -> { + if (step.contains("=")) { + final StringSplit split = StringOps.splitOnce(step, "="); + final String label = split.left(); + final int focalLength = Integer.parseInt(split.right()); + boxes.addLens(label, focalLength); + } else { + final String label = step.substring(0, step.length() - 1); + boxes.removeLens(label); + } + }); + return boxes.getTotalFocusingPower(); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "1320"), + @Sample(method = "part2", input = TEST, expected = "145"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2023_15.create().run(); + } + + + private final class Boxes { + @SuppressWarnings("unchecked") + private final Map[] boxes = new Map[256]; + + protected Boxes() { + for (int i = 0; i < this.boxes.length; i++) { + this.boxes[i] = new LinkedHashMap<>(); + } + } + + public void addLens(final String label, final int focalLength) { + this.boxes[AoC2023_15.this.hash(label)].put(label, focalLength); + } + + public void removeLens(final String label) { + this.boxes[AoC2023_15.this.hash(label)].remove(label); + } + + public int getTotalFocusingPower() { + return enumerateFrom(1, Arrays.stream(boxes)) + .flatMapToInt(box -> enumerateFrom(1, box.value().values().stream()) + .mapToInt(e -> box.index() * e.index() * e.value())) + .sum(); + } + } + + private static final String TEST = + "rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7"; +} diff --git a/src/main/java/AoC2023_16.java b/src/main/java/AoC2023_16.java new file mode 100644 index 00000000..d4cb892b --- /dev/null +++ b/src/main/java/AoC2023_16.java @@ -0,0 +1,128 @@ +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + +import com.github.pareronia.aoc.AssertUtils; +import com.github.pareronia.aoc.CharGrid; +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.geometry.Direction; +import com.github.pareronia.aoc.geometry.Turn; +import com.github.pareronia.aoc.graph.BFS; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2023_16 + extends SolutionBase { + + private AoC2023_16(final boolean debug) { + super(debug); + } + + public static AoC2023_16 create() { + return new AoC2023_16(false); + } + + public static AoC2023_16 createDebug() { + return new AoC2023_16(true); + } + + @Override + protected Contraption parseInput(final List inputs) { + return new Contraption(CharGrid.from(inputs)); + } + + @Override + public Integer solvePart1(final Contraption contraption) { + return contraption.getInitialEnergy(); + } + + @Override + public Integer solvePart2(final Contraption contraption) { + return contraption.getMaximalEnergy(); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "46"), + @Sample(method = "part2", input = TEST, expected = "51"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2023_16.create().run(); + } + + record Contraption(CharGrid grid) { + + record Beam(Cell cell, Direction dir) { } + + public int getInitialEnergy() { + return getEnergised(new Beam(Cell.at(0, 0), Direction.RIGHT)); + } + + public int getMaximalEnergy() { + return Stream.of( + grid.rowIndices().intStream() + .mapToObj(row -> new Beam(Cell.at(row, 0), Direction.RIGHT)), + grid.rowIndices().intStream() + .mapToObj(row -> new Beam(Cell.at(row, grid.getWidth() - 1), Direction.LEFT)), + grid.colIndices().intStream() + .mapToObj(col -> new Beam(Cell.at(0, col), Direction.DOWN)), + grid.colIndices().intStream() + .mapToObj(col -> new Beam(Cell.at(grid.getHeight() - 1, col), Direction.UP)) + ).reduce(Stream.empty(), Stream::concat) + .mapToInt(this::getEnergised) + .max().getAsInt(); + } + + private Stream stream(final Beam beam, final Direction...dirs) { + return Arrays.stream(dirs) + .map(d -> new Beam(beam.cell.at(d), d)) + .filter(t -> grid.isInBounds(t.cell)); + } + + private int getEnergised(final Beam initialBeam) { + final Function> adjacent = beam -> { + final char val = grid.getValue(beam.cell); + if (val == '.' + || val == '|' && beam.dir.isVertical() + || val == '-' && beam.dir.isHorizontal()) { + return stream(beam, beam.dir); + } else if (val == '/' && beam.dir.isHorizontal()) { + return stream(beam, beam.dir.turn(Turn.LEFT)); + } else if (val == '/' && beam.dir.isVertical()) { + return stream(beam, beam.dir.turn(Turn.RIGHT)); + } else if (val == '\\' && beam.dir.isHorizontal()) { + return stream(beam, beam.dir.turn(Turn.RIGHT)); + } else if (val == '\\' && beam.dir.isVertical()) { + return stream(beam, beam.dir.turn(Turn.LEFT)); + } else if (val == '|' && beam.dir.isHorizontal()) { + return stream(beam, Direction.UP, Direction.DOWN); + } else if (val == '-' && beam.dir.isVertical()) { + return stream(beam, Direction.LEFT, Direction.RIGHT); + } + throw AssertUtils.unreachable(); + }; + + final Set energised = BFS.floodFill(initialBeam, adjacent); + return (int) energised.stream().map(Beam::cell).distinct().count(); + } + } + + private static final String TEST = """ + .|...\\.... + |.-.\\..... + .....|-... + ........|. + .......... + .........\\ + ..../.\\\\.. + .-.-/..|.. + .|....-|.\\ + ..//.|.... + """; +} diff --git a/src/main/java/AoC2023_17.java b/src/main/java/AoC2023_17.java new file mode 100644 index 00000000..610448e8 --- /dev/null +++ b/src/main/java/AoC2023_17.java @@ -0,0 +1,115 @@ +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.Stream.Builder; + +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.IntGrid; +import com.github.pareronia.aoc.SetUtils; +import com.github.pareronia.aoc.geometry.Direction; +import com.github.pareronia.aoc.geometry.Turn; +import com.github.pareronia.aoc.graph.Dijkstra; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2023_17 + extends SolutionBase { + + private AoC2023_17(final boolean debug) { + super(debug); + } + + public static AoC2023_17 create() { + return new AoC2023_17(false); + } + + public static AoC2023_17 createDebug() { + return new AoC2023_17(true); + } + + @Override + protected IntGrid parseInput(final List inputs) { + return IntGrid.from(inputs); + } + + private int solve(final IntGrid grid, final int minMoves, final int maxMoves) { + record Move(Cell cell, Direction dir, int cost) {} + + final Function> adjacent = move -> { + final Set dirs = move.dir() == null + ? Direction.CAPITAL + : SetUtils.difference(Direction.CAPITAL, + Set.of(move.dir(), move.dir().turn(Turn.AROUND))); + final Builder moves = Stream.builder(); + for (final Direction dir : dirs) { + Cell cell = move.cell(); + int hl = 0; + for (int i = 1; i <= maxMoves; i++) { + cell = cell.at(dir); + if (!grid.isInBounds(cell)) { + break; + } + hl += grid.getValue(cell); + if (i >= minMoves) { + moves.add(new Move(cell, dir, hl)); + } + } + } + return moves.build(); + }; + final Cell end = Cell.at(grid.getMaxRowIndex(), grid.getMaxColIndex()); + return (int) Dijkstra.distance( + new Move(Cell.at(0, 0), null, 0), + move -> move.cell().equals(end), + adjacent, + (curr, next) -> next.cost); + } + + @Override + public Integer solvePart1(final IntGrid grid) { + return solve(grid, 1, 3); + } + + @Override + public Integer solvePart2(final IntGrid grid) { + return solve(grid, 4, 10); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "102"), + @Sample(method = "part2", input = TEST1, expected = "94"), + @Sample(method = "part2", input = TEST2, expected = "71"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2023_17.create().run(); + } + + private static final String TEST1 = """ + 2413432311323 + 3215453535623 + 3255245654254 + 3446585845452 + 4546657867536 + 1438598798454 + 4457876987766 + 3637877979653 + 4654967986887 + 4564679986453 + 1224686865563 + 2546548887735 + 4322674655533 + """; + private static final String TEST2 = """ + 111111111111 + 999999999991 + 999999999991 + 999999999991 + 999999999991 + """; +} diff --git a/src/main/java/AoC2023_18.java b/src/main/java/AoC2023_18.java new file mode 100644 index 00000000..25a695c8 --- /dev/null +++ b/src/main/java/AoC2023_18.java @@ -0,0 +1,129 @@ +import java.util.ArrayList; +import java.util.List; + +import com.github.pareronia.aoc.IntegerSequence.Range; +import com.github.pareronia.aoc.Utils; +import com.github.pareronia.aoc.geometry.Direction; +import com.github.pareronia.aoc.geometry.Position; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2023_18 + extends SolutionBase, Long, Long> { + + private static final Direction[] DIRS = { + Direction.RIGHT, + Direction.DOWN, + Direction.LEFT, + Direction.UP, + }; + + private AoC2023_18(final boolean debug) { + super(debug); + } + + public static AoC2023_18 create() { + return new AoC2023_18(false); + } + + public static AoC2023_18 createDebug() { + return new AoC2023_18(true); + } + + @Override + protected List parseInput(final List inputs) { + return inputs; + } + + private long solve(final List instructions) { + final List vertices = new ArrayList<>(List.of(Position.ORIGIN)); + instructions.forEach(instruction -> { + vertices.add(Utils.last(vertices) + .translate(instruction.direction(), instruction.amount())); + }); + return new Polygon(vertices).insideArea(); + } + + @Override + public Long solvePart1(final List input) { + final List instructions = input.stream() + .map(line -> { + final String[] splits = line.split(" "); + return new Instruction( + Direction.fromString(splits[0]), + Integer.parseInt(splits[1]) + ); + }) + .toList(); + return solve(instructions); + } + + @Override + public Long solvePart2(final List input) { + final List instructions = input.stream() + .map(line -> { + final String[] splits = line.split(" "); + return new Instruction( + DIRS[Integer.parseInt(splits[2].substring(7, 8))], + Integer.parseInt(splits[2].substring(2, 7), 16) + ); + }) + .toList(); + return solve(instructions); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "62"), + @Sample(method = "part2", input = TEST, expected = "952408144115"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2023_18.create().run(); + } + + record Instruction(Direction direction, int amount) { } + + record Polygon(List vertices) { + + private long shoelace() { + final int size = vertices.size(); + final long s = Range.range(vertices.size()).intStream() + .mapToLong(i -> ((long) vertices.get(i).getX()) + * (vertices.get((i + 1) % size).getY() + - vertices.get((size + i - 1) % size).getY())) + .sum(); + return Math.abs(s) / 2; + } + + private long circumference() { + return Range.range(1, vertices.size(), 1).intStream() + .mapToLong(i -> vertices.get(i).manhattanDistance(vertices.get(i - 1))) + .sum(); + } + + public long insideArea() { + return this.shoelace() + this.circumference() / 2 + 1; + } + } + + private static final String TEST = """ + R 6 (#70c710) + D 5 (#0dc571) + L 2 (#5713f0) + D 2 (#d2c081) + R 2 (#59c680) + D 2 (#411b91) + L 5 (#8ceee2) + U 2 (#caa173) + L 1 (#1b58a2) + U 2 (#caa171) + R 2 (#7807d2) + U 3 (#a77fa3) + L 2 (#015232) + U 2 (#7a21e3) + """; +} diff --git a/src/main/java/AoC2023_19.java b/src/main/java/AoC2023_19.java new file mode 100644 index 00000000..9ca09390 --- /dev/null +++ b/src/main/java/AoC2023_19.java @@ -0,0 +1,299 @@ +import static java.util.stream.Collectors.toMap; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import com.github.pareronia.aoc.RangeInclusive; +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2023_19 + extends SolutionBase { + + private static final String IN = "in"; + + private AoC2023_19(final boolean debug) { + super(debug); + } + + public static AoC2023_19 create() { + return new AoC2023_19(false); + } + + public static AoC2023_19 createDebug() { + return new AoC2023_19(true); + } + + @Override + protected System parseInput(final List inputs) { + final List> blocks = StringOps.toBlocks(inputs); + final Map workflows = blocks.get(0).stream() + .map(Workflow::fromString) + .collect(toMap(w -> w.name, w -> w)); + final List parts = blocks.get(1).stream() + .map(Part::fromString) + .toList(); + return new System(workflows, parts); + } + + @Override + public Long solvePart1(final System system) { + final List prs = system.parts.stream() + .map(p -> new Rule.Result(PartRange.fromPart(p), IN)) + .toList(); + final Map> solution = this.solve( + system.workflows, prs); + return system.parts.stream() + .filter(part -> solution.getOrDefault(Rule.Result.ACCEPT, List.of()).stream() + .anyMatch(acc -> acc.matches(part))) + .mapToLong(Part::score) + .sum(); + } + + @Override + public Long solvePart2(final System system) { + final Rule.Result pr = new Rule.Result( + new PartRange( + RangeInclusive.between(1, 4000), + RangeInclusive.between(1, 4000), + RangeInclusive.between(1, 4000), + RangeInclusive.between(1, 4000)), + IN); + final Map> solution = this.solve( + system.workflows, List.of(pr)); + return solution.entrySet().stream() + .filter(e -> Rule.Result.ACCEPT.equals(e.getKey())) + .mapToLong(e -> e.getValue().stream() + .mapToLong(PartRange::score) + .sum()) + .sum(); + } + + private Map> solve( + final Map workflows, + List prs + ) { + final Map> solution = new HashMap<>(); + while (!prs.isEmpty()) { + final List newprs = new ArrayList<>(); + for (final Rule.Result pr : prs) { + for(final Rule.Result res : workflows.get(pr.result).eval(pr.range)) { + if (Rule.Result.ACCEPT.equals(res.result) + || Rule.Result.REJECT.equals(res.result)) { + solution.computeIfAbsent(res.result, k -> new ArrayList<>()).add(res.range); + } else { + newprs.add(new Rule.Result(res.range, res.result)); + } + } + } + prs = newprs; + } + return solution; + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "19114"), + @Sample(method = "part2", input = TEST, expected = "167409079868000"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2023_19.create().run(); + } + + private static final String TEST = """ + px{a<2006:qkq,m>2090:A,rfg} + pv{a>1716:R,A} + lnx{m>1548:A,A} + rfg{s<537:gd,x>2440:R,A} + qs{s>3448:A,lnx} + qkq{x<1416:A,crn} + crn{x>2662:A,R} + in{s<1351:px,qqz} + qqz{s>2770:qs,m<1801:hdj,R} + gd{a>3333:R,R} + hdj{m>838:A,pv} + + {x=787,m=2655,a=1222,s=2876} + {x=1679,m=44,a=2067,s=496} + {x=2036,m=264,a=79,s=2244} + {x=2461,m=1339,a=466,s=291} + {x=2127,m=1623,a=2188,s=1013} + """; + + private record Part(int x, int m, int a, int s) { + + public static Part fromString(final String string) { + final String[] splits + = string.substring(1, string.length() - 1).split(","); + final int[] a = Arrays.stream(splits) + .map(sp -> sp.split("=")[1]) + .mapToInt(Integer::parseInt) + .toArray(); + return new Part(a[0], a[1], a[2], a[3]); + } + + public int score() { + return x + m + a + s; + } + } + + private record PartRange( + RangeInclusive x, + RangeInclusive m, + RangeInclusive a, + RangeInclusive s + ) { + + public static PartRange fromPart(final Part part) { + return new PartRange( + RangeInclusive.between(part.x, part.x), + RangeInclusive.between(part.m, part.m), + RangeInclusive.between(part.a, part.a), + RangeInclusive.between(part.s, part.s)); + } + + public PartRange copyWith( + final char prop, final RangeInclusive value + ) { + return new PartRange( + 'x' == prop ? value : this.x, + 'm' == prop ? value : this.m, + 'a' == prop ? value : this.a, + 's' == prop ? value : this.s); + } + + public PartRange copy() { + return new PartRange(x, m, a, s); + } + + public boolean matches(final Part part) { + return x.contains(part.x) && m.contains(part.m) + && a.contains(part.a) && s.contains(part.s); + } + + public long score() { + return List.of(x, m, a, s).stream() + .mapToLong(r -> r.getMaximum() - r.getMinimum() + 1) + .reduce(1L, (a, b) -> a * b); + } + } + + private record Rule( + Optional operand1, + Character operation, + Optional operand2, + String result + ) { + + public static final char CATCHALL = '_'; + + public static Rule fromString(final String string) { + if (string.contains(":")) { + final StringSplit sp = StringOps.splitOnce(string, ":"); + return new Rule( + Optional.of(sp.left().charAt(0)), + sp.left().charAt(1), + Optional.of(Integer.parseInt(sp.left().substring(2))), + sp.right()); + } else { + return new Rule( + Optional.empty(), CATCHALL, Optional.empty(), string); + } + } + + public List eval(final PartRange range) { + if (operation == CATCHALL) { + return List.of(new Result(range.copy(), result)); + } else { + assert operand1.isPresent() && operand2.isPresent(); + final RangeInclusive r = switch (operand1.get()) { + case 'x' -> range.x(); + case 'm' -> range.m(); + case 'a' -> range.a(); + case 's' -> range.s(); + default -> throw new IllegalArgumentException( + "Unexpected value: " + operand1.get()); + }; + final int[] match; + final int[] noMatch; + if (operation == '<') { + match = new int[] { r.getMinimum(), operand2.get() - 1 }; + noMatch = new int[] { operand2.get(), r.getMaximum() }; + } else { + match = new int[] { operand2.get() + 1, r.getMaximum() }; + noMatch = new int[] { r.getMinimum(), operand2.get() }; + } + final List ans = new ArrayList<>(); + if (match[0] <= match[1]) { + final PartRange nr = range.copyWith( + operand1.get(), + RangeInclusive.between(match[0], match[1])); + ans.add(new Result(nr, result)); + } + if (noMatch[0] <= noMatch[1]) { + final PartRange nr = range.copyWith( + operand1.get(), + RangeInclusive.between(noMatch[0], noMatch[1])); + ans.add(new Result(nr, Result.CONTINUE)); + } + return ans; + } + } + + public record Result(PartRange range, String result) { + + public static final String CONTINUE = "=continue="; + public static final String ACCEPT = "A"; + public static final String REJECT = "R"; + } + } + + + private record Workflow(String name, List rules) { + + public static Workflow fromString(final String string) { + final int i = string.indexOf('{'); + final String name = string.substring(0, i); + final List rules = Arrays.stream( + string + .substring(0, string.length() - 1) + .substring(i + 1) + .split(",")) + .map(Rule::fromString) + .toList(); + return new Workflow(name, rules); + } + + public List eval(final PartRange range) { + final List ans = new ArrayList<>(); + List ranges = List.of(range); + for (final Rule rule : rules) { + final List newRanges = new ArrayList<>(); + for (final PartRange r : ranges) { + final List ress = rule.eval(r); + for (final Rule.Result res : ress) { + if (!Rule.Result.CONTINUE.equals(res.result)) { + ans.add(new Rule.Result(res.range, res.result)); + } else { + newRanges.add(res.range); + } + } + } + ranges = newRanges; + } + return ans; + } + } + + record System(Map workflows, List parts) {} +} diff --git a/src/main/java/AoC2023_20.java b/src/main/java/AoC2023_20.java new file mode 100644 index 00000000..06eebfb3 --- /dev/null +++ b/src/main/java/AoC2023_20.java @@ -0,0 +1,262 @@ +import java.math.BigInteger; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; + +import com.github.pareronia.aoc.MutableInt; +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2023_20 + extends SolutionBase { + + private AoC2023_20(final boolean debug) { + super(debug); + } + + public static AoC2023_20 create() { + return new AoC2023_20(false); + } + + public static AoC2023_20 createDebug() { + return new AoC2023_20(true); + } + + @Override + protected Modules parseInput(final List inputs) { + return Modules.fromInput(inputs); + } + + @Override + public Integer solvePart1(final Modules modules) { + final MutableInt hi = new MutableInt(0); + final MutableInt lo = new MutableInt(0); + final PulseListener listener = pulse -> { + if (pulse.isLow()) { + lo.increment(); + } else { + hi.increment(); + } + }; + for (int i = 0; i < 1000; i++) { + modules.pushButton(listener); + } + return hi.intValue() * lo.intValue(); + } + + @Override + public Long solvePart2(final Modules modules) { + final Map> memo = new HashMap<>(); + final String to_rx = modules.getModuleWithOutput_rx(); + final MutableInt pushes = new MutableInt(); + final PulseListener listener = pulse -> { + if (pulse.dest.equals(to_rx) && pulse.isHigh()) { + memo.computeIfAbsent(pulse.src, k -> new ArrayList<>()) + .add(pushes.intValue()); + } + }; + for (int i = 1; i <= 10000; i++) { + pushes.increment(); + modules.pushButton(listener); + if (!memo.isEmpty() + && memo.values().stream().allMatch(v -> v.size() > 1)) { + return memo.values().stream() + .map(v -> v.get(1) - v.get(0)) + .map(BigInteger::valueOf) + .reduce((a, b) -> a.multiply(b).divide(a.gcd(b))) + .get().longValue(); + } + } + throw new IllegalStateException("Unsolvable"); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "32000000"), + @Sample(method = "part1", input = TEST2, expected = "11687500"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2023_20.createDebug().run(); + } + + record Pulse(String src, String dest, Pulse.Type value) { + public enum Type { + HIGH, LOW; + + public Type flipped() { + return this == HIGH ? LOW : HIGH; + } + } + + public static Pulse high(final String src, final String dest) { + return new Pulse(src, dest, Type.HIGH); + } + + public static Pulse low(final String src, final String dest) { + return new Pulse(src, dest, Type.LOW); + } + + public boolean isHigh() { + return value == Type.HIGH; + } + + public boolean isLow() { + return value == Type.LOW; + } + } + + private interface PulseListener { + void onPulse(Pulse pulse); + } + + private static final class Module { + private static final String BROADCASTER = "broadcaster"; + private static final String BUTTON = "button"; + private static final String SELF = "self"; + + private final String type; + private final List outputs; + private final Map state = new HashMap<>(); + + public Module(final String type, final List outputs) { + this.type = type; + this.outputs = outputs; + } + + public List getOutputs() { + return outputs; + } + + public Map getState() { + return state; + } + + private boolean isConjunction() { + return this.type.equals("&"); + } + + public boolean isFlipFlop() { + return this.type.equals("%"); + } + + private boolean isBroadcaster() { + return this.type.equals(BROADCASTER); + } + + public List process(final Pulse pulse) { + if (this.isBroadcaster()) { + return this.getOutputs().stream() + .map(o -> new Pulse(BROADCASTER, o, pulse.value)) + .toList(); + } else if (this.isFlipFlop()) { + if (pulse.isLow()) { + final Pulse.Type newState + = this.getState().get(Module.SELF).flipped(); + this.getState().put(Module.SELF, newState); + return this.getOutputs().stream() + .map(o -> newState == Pulse.Type.LOW + ? Pulse.low(pulse.dest, o) + : Pulse.high(pulse.dest, o)) + .toList(); + } else { + return List.of(); + } + } else { + this.getState().put(pulse.src, pulse.value()); + final boolean allHigh = this.getState().values().stream() + .allMatch(v -> v == Pulse.Type.HIGH); + return this.getOutputs().stream() + .map(o -> allHigh + ? Pulse.low(pulse.dest, o) + : Pulse.high(pulse.dest, o)) + .toList(); + } + } + } + + static final class Modules { + private final Map modules; + + private Modules(final Map modules) { + this.modules = modules; + } + + public static Modules fromInput(final List inputs) { + final Map modules = new HashMap<>(); + inputs.stream().forEach(line -> { + final StringSplit split = StringOps.splitOnce(line, " -> "); + final List outputs = Arrays.asList(split.right().split(", ")); + if (split.left().equals(Module.BROADCASTER)) { + modules.put( + Module.BROADCASTER, + new Module(Module.BROADCASTER, outputs)); + } else { + final char type = split.left().charAt(0); + modules.put( + split.left().substring(1), + new Module(String.valueOf(type), outputs)); + } + }); + for (final String m : modules.keySet()) { + final Module module = modules.get(m); + for (final String output : module.getOutputs()) { + final Module m1 = modules.get(output); + if (m1 != null && m1.isConjunction()) { + m1.getState().put(m, Pulse.Type.LOW); + } + } + if (module.isFlipFlop()) { + module.getState().put(Module.SELF, Pulse.Type.LOW); + } + } + return new Modules(modules); + } + + public String getModuleWithOutput_rx() { + return modules.entrySet().stream() + .filter(e -> e.getValue().getOutputs().stream() + .anyMatch(o -> o.equals("rx"))) + .map(Entry::getKey) + .findFirst().orElseThrow(); + } + + public void pushButton(final PulseListener listener) { + final Deque q = new ArrayDeque<>(); + q.add(Pulse.low(Module.BUTTON, Module.BROADCASTER)); + while (!q.isEmpty()) { + final Pulse pulse = q.pop(); + listener.onPulse(pulse); + Optional.ofNullable(this.modules.get(pulse.dest)) + .ifPresent(target -> + target.process(pulse).forEach(q::add)); + } + } + } + + private static final String TEST1 = """ + broadcaster -> a, b, c + %a -> b + %b -> c + %c -> inv + &inv -> a + """; + private static final String TEST2 = """ + broadcaster -> a + %a -> inv, con + &inv -> b + %b -> con + &con -> output + """; +} diff --git a/src/main/java/AoC2023_21.java b/src/main/java/AoC2023_21.java new file mode 100644 index 00000000..c7de9df0 --- /dev/null +++ b/src/main/java/AoC2023_21.java @@ -0,0 +1,95 @@ +import static com.github.pareronia.aoc.IntegerSequence.Range.range; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.github.pareronia.aoc.CharGrid; +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.geometry.Direction; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2023_21 extends SolutionBase { + + private static final int STEPS = 26_501_365; + + private AoC2023_21(final boolean debug) { + super(debug); + } + + public static AoC2023_21 create() { + return new AoC2023_21(false); + } + + public static AoC2023_21 createDebug() { + return new AoC2023_21(true); + } + + @Override + protected CharGrid parseInput(final List inputs) { + return CharGrid.from(inputs); + } + + @Override + public Long solvePart1(final CharGrid grid) { + return (long) this.solve(grid, List.of(64)).get(0); + } + + @Override + public Long solvePart2(final CharGrid grid) { + final int w = grid.getWidth(); + final int mod = STEPS % w; + final int x = STEPS / w; + // https://old.reddit.com/r/adventofcode/comments/18nni6t/2023_21_part_2_optimization_hint/ + final List steps = new ArrayList<>(); + steps.add(w - mod - 2); + range(2).intStream().map(i -> i * w + mod).forEach(steps::add); + log(steps); + final List values = this.solve(grid, steps); + log(values); + final long a = (values.get(2) + values.get(0)) / 2 - values.get(1); + final long b = (values.get(2) - values.get(0)) / 2; + final long c = values.get(1); + log("a: %d, b: %d, c: %d".formatted(a, b, c)); + return a * x * x + b * x + c; + } + + private record Plot(long r, long c) {} + + private List solve(final CharGrid grid, final List steps) { + final int w = grid.getWidth(); + Set plots = new HashSet<>(); + plots.add(new Plot(w / 2, w / 2)); + final List ans = new ArrayList<>(); + final int max = steps.stream().mapToInt(Integer::intValue).max().getAsInt(); + for (int i = 1; i <= max; i++) { + final Set newPlots = new HashSet<>(); + for (final Plot p : plots) { + for (final Direction d : Direction.CAPITAL) { + final long rr = p.r() + d.getY(); + final long cc = p.c() + d.getX(); + final int wr = Math.floorMod(rr, w); + final int wc = Math.floorMod(cc, w); + if (0 <= wr && wr < w && 0 <= wc && wc < w + && grid.getValue(Cell.at(wr, wc)) != '#') { + newPlots.add(new Plot(rr, cc)); + } + } + } + plots = newPlots; + if (steps.contains(i)) { + ans.add(plots.size()); + } + } + return ans; + } + + @Override + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2023_21.create().run(); + } +} \ No newline at end of file diff --git a/src/main/java/AoC2023_22.java b/src/main/java/AoC2023_22.java new file mode 100644 index 00000000..0447cec2 --- /dev/null +++ b/src/main/java/AoC2023_22.java @@ -0,0 +1,247 @@ +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import com.github.pareronia.aoc.MutableBoolean; +import com.github.pareronia.aoc.SetUtils; +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.geometry.Position; +import com.github.pareronia.aoc.geometry3d.Cuboid; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2023_22 + extends SolutionBase { + + private AoC2023_22(final boolean debug) { + super(debug); + } + + public static AoC2023_22 create() { + return new AoC2023_22(false); + } + + public static AoC2023_22 createDebug() { + return new AoC2023_22(true); + } + + @Override + protected Stack parseInput(final List inputs) { + return Stack.fromInput(inputs); + } + + @Override + public Integer solvePart1(final Stack stack) { + return stack.getDeletable().size(); + } + + @Override + public Integer solvePart2(final Stack stack) { + return stack.getNotDeletable().stream() + .map(stack::delete) + .mapToInt(Set::size) + .sum(); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "5"), + @Sample(method = "part2", input = TEST, expected = "7"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2023_22.create().run(); + } + + static final class Stack { + private final Set bricks; + private final Map> supportees; + private final Map> supporters; + private final Map> bricksByZ1; + private final Map> bricksByZ2; + + protected Stack(final Set bricks) { + this.bricks = bricks; + this.bricksByZ1 = this.bricks.stream() + .collect(groupingBy(Cuboid::z1)); + this.bricksByZ2 = this.bricks.stream() + .collect(groupingBy(Cuboid::z2)); + this.stack(); + this.supportees = this.bricks.stream() + .collect(toMap( + brick -> brick, + brick -> this.getBricksByZ1(brick.z2() + 1).stream() + .filter(b -> Stack.overlapsXY(b, brick)) + .collect(toSet()))); + this.supporters = this.bricks.stream() + .collect(toMap( + brick -> brick, + brick -> this.getBricksByZ2(brick.z1() - 1).stream() + .filter(b -> Stack.overlapsXY(b, brick)) + .collect(toSet()))); + } + + public static Stack fromInput(final List inputs) { + final Set bricks = new HashSet<>(); + for (final String line : inputs) { + final StringSplit split = StringOps.splitOnce(line, "~"); + final String[] xyz1 = split.left().split(","); + final String[] xyz2 = split.right().split(","); + bricks.add(Cuboid.of( + Integer.parseInt(xyz1[0]), + Integer.parseInt(xyz2[0]), + Integer.parseInt(xyz1[1]), + Integer.parseInt(xyz2[1]), + Integer.parseInt(xyz1[2]), + Integer.parseInt(xyz2[2]) + )); + } + return new Stack(bricks); + } + + public List getBricksByZ1(final int z1) { + return this.bricksByZ1.getOrDefault(z1, new ArrayList<>()); + } + + public List getBricksByZ2(final int z2) { + return this.bricksByZ2.getOrDefault(z2, new ArrayList<>()); + } + + public Set getViewY() { + return this.bricks.stream() + .flatMap(Cuboid::positions) + .map(p -> Position.of(p.getX(), p.getZ())) + .collect(toSet()); + } + + private static Cuboid moveToZ(final Cuboid block, final int dz) { + return new Cuboid( + block.x1(), block.x2(), + block.y1(), block.y2(), + block.z1() + dz, block.z2() + dz + ); + } + + private static boolean overlapsXY(final Cuboid lhs, final Cuboid rhs) { + return Cuboid.overlapX(lhs, rhs) && Cuboid.overlapY(lhs, rhs); + } + + private static boolean overlapsXY( + final Collection cuboids, + final Cuboid brick + ) { + return cuboids.stream().anyMatch(c -> Stack.overlapsXY(c, brick)); + } + + private void stack() { + final MutableBoolean moved = new MutableBoolean(true); + final Predicate isNotSupported = brick -> !overlapsXY( + this.bricksByZ2.getOrDefault(brick.z1() - 1, List.of()), + brick); + final Consumer moveDown = brick -> { + final Cuboid movedBrick = moveToZ(brick, -1); + this.getBricksByZ1(brick.z1()).remove(brick); + this.getBricksByZ2(brick.z2()).remove(brick); + this.bricksByZ1.computeIfAbsent( + movedBrick.z1(), k -> new ArrayList<>()) + .add(movedBrick); + this.bricksByZ2.computeIfAbsent( + movedBrick.z2(), k -> new ArrayList<>()) + .add(movedBrick); + moved.setTrue(); + }; + while (moved.isTrue()) { + moved.setFalse(); + this.bricksByZ1.keySet().stream() + .sorted() + .filter(z -> z > 1) + .forEach(z -> { + new ArrayList<>(this.getBricksByZ1(z)).stream() + .filter(isNotSupported) + .forEach(moveDown); + }); + } + this.bricks.clear(); + bricksByZ2.values().stream().forEach(this.bricks::addAll); + } + + public List display() { + return Stack.displayBricks(bricks); + } + + public static List displayBricks(final Collection bricks) { + return bricks.stream() + .map(Stack::displayBrick) + .toList(); + } + + public static String displayBrick(final Cuboid brick) { + return "%d,%d,%d->%d,%d,%d".formatted( + brick.x1(), brick.y1(), brick.z1(), + brick.x2(), brick.y2(), brick.z2() + ); + } + + public Set getDeletable() { + return this.bricks.stream() + .filter(this::isNotSingleSupporter) + .collect(toSet()); + } + + private boolean isNotSingleSupporter(final Cuboid brick) { + return this.supportees.get(brick).stream() + .map(this.supporters::get) + .noneMatch(Set.of(brick)::equals); + } + + public Set getNotDeletable() { + return SetUtils.disjunction(this.bricks, this.getDeletable()); + } + + public Set delete(final Cuboid brick) { + final Deque q = new ArrayDeque<>(); + final Set falling = new HashSet<>(); + falling.add(brick); + q.add(brick); + while (!q.isEmpty()) { + final Cuboid b = q.poll(); + for (final Cuboid s : this.supportees.get(b)) { + if (this.supporters.get(s).stream() + .allMatch(falling::contains)) { + if (!falling.contains(s)) { + q.add(s); + } + falling.add(s); + } + } + } + falling.remove(brick); + return falling; + } + } + + private static final String TEST = """ + 1,0,1~1,2,1 + 0,0,2~2,0,2 + 0,2,3~2,2,3 + 0,0,4~0,2,4 + 2,0,5~2,2,5 + 0,1,6~2,1,6 + 1,1,8~1,1,9 + """; +} diff --git a/src/main/java/AoC2023_23.java b/src/main/java/AoC2023_23.java new file mode 100644 index 00000000..61c98b18 --- /dev/null +++ b/src/main/java/AoC2023_23.java @@ -0,0 +1,218 @@ +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.github.pareronia.aoc.CharGrid; +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.geometry.Direction; +import com.github.pareronia.aoc.solution.Logger; +import com.github.pareronia.aoc.solution.LoggerEnabled; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2023_23 + extends SolutionBase { + + private AoC2023_23(final boolean debug) { + super(debug); + } + + public static AoC2023_23 create() { + return new AoC2023_23(false); + } + + public static AoC2023_23 createDebug() { + return new AoC2023_23(true); + } + + @Override + protected CharGrid parseInput(final List inputs) { + return CharGrid.from(inputs); + } + + @Override + public Integer solvePart1(final CharGrid grid) { + return new PathFinder(grid, this.logger) + .findLongestHikeLengthWithDownwardSlopesOnly(); + } + + @Override + public Integer solvePart2(final CharGrid grid) { + return new PathFinder(grid, this.logger).findLongestHikeLength(); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "94"), + @Sample(method = "part2", input = TEST, expected = "154"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2023_23.create().run(); + } + + static final class PathFinder implements LoggerEnabled { + private final CharGrid grid; + private final Cell start; + private final Cell end; + private final Logger logger; + + protected PathFinder(final CharGrid grid, final Logger logger) { + this.grid = grid; + this.start = Cell.at(0, 1); + this.end = Cell.at(grid.getMaxRowIndex(), grid.getMaxColIndex() - 1); + this.logger = logger; + } + + @Override + public Logger getLogger() { + return this.logger; + } + + public int findLongestHikeLength() { + final Set pois = this.findPois(); + log(pois); + final Map> graph = this.buildGraph(pois, false); + log(graph); + final int distFromStart = graph.get(this.start).iterator().next().weight; + final int distToEnd = graph.get(this.end).iterator().next().weight; + final Cell newStart = graph.entrySet().stream() + .filter(e -> e.getValue().contains(new Edge(this.start, distFromStart))) + .map(Entry::getKey) + .findFirst().orElseThrow(); + final Cell newEnd = graph.entrySet().stream() + .filter(e -> e.getValue().contains(new Edge(this.end, distToEnd))) + .map(Entry::getKey) + .findFirst().orElseThrow(); + return distFromStart + distToEnd + + this.findLongest(graph, newStart, newEnd, new HashSet<>()); + } + + public int findLongestHikeLengthWithDownwardSlopesOnly() { + final Set pois = this.findPois(); + log(pois); + final Map> graph = this.buildGraph(pois, true); + log(graph); + return this.findLongest(graph, this.start, this.end, new HashSet<>()); + } + + private int findLongest( + final Map> graph, + final Cell curr, + final Cell end, + final Set seen + ) { + if (curr.equals(end)) { + return 0; + } + int ans = (int) -1e9; + seen.add(curr); + for (final Edge e : graph.get(curr)) { + if (seen.contains(e.vertex)) { + continue; + } + ans = Math.max( + ans, + e.weight + this.findLongest(graph, e.vertex, end, seen)); + } + seen.remove(curr); + return ans; + } + + private Set findPois() { + final Set ans = new HashSet<>(); + this.grid.getCells().forEach(cell -> { + if (cell.equals(this.start) || cell.equals(this.end)) { + ans.add(cell); + return; + } + if (this.grid.getValue(cell) == '#') { + return; + } + if (this.grid.getCapitalNeighbours(cell) + .filter(n -> this.grid.getValue(n) != '#') + .count() > 2) { + ans.add(cell); + } + }); + return ans; + } + + record Edge(Cell vertex, int weight) {} + + private Map> buildGraph( + final Set pois, final boolean downwardSlopesOnly + ) { + record State(Cell cell, int distance) {} + + final Map> edges = new HashMap<>(); + for (final Cell poi : pois) { + final Deque q = new ArrayDeque<>(Set.of(new State(poi, 0))); + final Set seen = new HashSet<>(Set.of(poi)); + while (!q.isEmpty()) { + final State node = q.poll(); + if (pois.contains(node.cell) && !node.cell.equals(poi)) { + edges.computeIfAbsent(poi, k -> new HashSet<>()) + .add(new Edge(node.cell, node.distance)); + continue; + } + for (final Direction d : Direction.CAPITAL) { + final Cell n = node.cell.at(d); + if (!this.grid.isInBounds(n)) { + continue; + } + final char val = this.grid.getValue(n); + if (val == '#') { + continue; + } + if (downwardSlopesOnly + && Set.of('<', '>', 'v', '^').contains(val) + && Direction.fromChar(val) != d) { + continue; + } + if (seen.contains(n)) { + continue; + } + seen.add(n); + q.add(new State(n, node.distance + 1)); + } + } + } + return edges; + } + } + + private static final String TEST = """ + #.##################### + #.......#########...### + #######.#########.#.### + ###.....#.>.>.###.#.### + ###v#####.#v#.###.#.### + ###.>...#.#.#.....#...# + ###v###.#.#.#########.# + ###...#.#.#.......#...# + #####.#.#.#######.#.### + #.....#.#.#.......#...# + #.#####.#.#.#########v# + #.#...#...#...###...>.# + #.#.#v#######v###.###v# + #...#.>.#...>.>.#.###.# + #####v#.#.###v#.#.###.# + #.....#...#...#.#.#...# + #.#########.###.#.#.### + #...###...#...#...#.### + ###.###.#.###v#####v### + #...#...#.#.>.>.#.>.### + #.###.###.#.###.#.#v### + #.....###...###...#...# + #####################.# + """; +} diff --git a/src/main/java/AoC2023_25.java b/src/main/java/AoC2023_25.java new file mode 100644 index 00000000..54349507 --- /dev/null +++ b/src/main/java/AoC2023_25.java @@ -0,0 +1,133 @@ +import static com.github.pareronia.aoc.Utils.concatAll; +import static java.util.stream.Collectors.toMap; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2023_25 + extends SolutionBase { + + private AoC2023_25(final boolean debug) { + super(debug); + } + + public static AoC2023_25 create() { + return new AoC2023_25(false); + } + + public static AoC2023_25 createDebug() { + return new AoC2023_25(true); + } + + @Override + protected Graph parseInput(final List inputs) { + return Graph.fromInput(inputs); + } + + @Override + public Integer solvePart1(final Graph graph) { + final Random rand = new Random(); + while (true) { + Graph g = graph.copy(); + final Map counts = g.edges.keySet().stream() + .collect(toMap(k -> k, k -> 1)); + while (g.edges.size() > 2) { + final String a = g.edges.keySet().stream() + .skip(rand.nextInt(g.edges.size() - 1)) + .findFirst().orElseThrow(); + final String b = g.edges.get(a).get(rand.nextInt(g.edges.get(a).size())); + final String newNode = "%s-%s".formatted(a, b); + counts.put(newNode, counts.get(a) + counts.get(b)); + counts.remove(a); + counts.remove(b); + g = g.combineNodes(a, b, newNode); + } + final List nodes = List.copyOf(g.edges.keySet()); + if (g.edges.get(nodes.get(0)).size() == 3) { + return counts.get(nodes.get(0)) * counts.get(nodes.get(1)); + } + } + } + + @Override + public String solvePart2(final Graph graph) { + return "🎄"; + } + + @Samples({ + @Sample(method = "part1", input = TEST, expected = "54"), + }) + public static void main(final String[] args) throws Exception { + AoC2023_25.create().run(); + } + + private static final String TEST = """ + jqt: rhn xhk nvd + rsh: frs pzl lsr + xhk: hfx + cmg: qnr nvd lhk bvb + rhn: xhk bvb hfx + bvb: xhk hfx + pzl: lsr hfx nvd + qnr: nvd + ntq: jqt hfx bvb xhk + nvd: lhk + lsr: lhk + rzs: qnr cmg lsr rsh + frs: qnr lhk lsr + """; + + record Graph(Map> edges) { + + public static Graph fromInput(final List input) { + final HashMap> edges = new HashMap<>(); + for (final String line : input) { + final StringSplit splits = StringOps.splitOnce(line, ": "); + final List lst + = new ArrayList<>(Arrays.asList(splits.right().split(" "))); + edges.computeIfAbsent(splits.left(), s -> new ArrayList<>()).addAll(lst); + lst.forEach(dst -> + edges.computeIfAbsent(dst, d -> new ArrayList()) + .add(splits.left())); + } + return new Graph(edges); + } + + public Graph copy() { + return new Graph(this.edges.keySet().stream() + .collect(toMap( + k -> k, + k -> this.edges.get(k).stream().toList()))); + } + + public Graph combineNodes( + final String node1, final String node2, final String newNode + ) { + this.edges.put(newNode, concatAll( + this.edges.get(node1).stream().filter(d -> !d.equals(node2)).toList(), + this.edges.get(node2).stream().filter(d -> !d.equals(node1)).toList() + )); + for (final String node : Set.of(node1, node2)) { + for (final String dst : this.edges.get(node)) { + final List lst = this.edges.get(dst).stream() + .map(d -> d.equals(node) ? newNode : d) + .toList(); + this.edges.put(dst, lst); + } + this.edges.remove(node); + } + return new Graph(this.edges); + } + } +} diff --git a/src/main/java/AoC2024_01.java b/src/main/java/AoC2024_01.java new file mode 100644 index 00000000..96f9ad31 --- /dev/null +++ b/src/main/java/AoC2024_01.java @@ -0,0 +1,77 @@ +import static com.github.pareronia.aoc.IterTools.zip; +import static com.github.pareronia.aoc.ListUtils.sorted; + +import java.util.List; + +import com.github.pareronia.aoc.Counter; +import com.github.pareronia.aoc.ListUtils; +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_01 + extends SolutionBase { + + private AoC2024_01(final boolean debug) { + super(debug); + } + + public static AoC2024_01 create() { + return new AoC2024_01(false); + } + + public static AoC2024_01 createDebug() { + return new AoC2024_01(true); + } + + @Override + protected Lists parseInput(final List inputs) { + final List> lists = inputs.stream() + .map(line -> StringOps.splitOnce(line, "\s+")) + .map(splits -> List.of( + Integer.valueOf(splits.left()), + Integer.valueOf(splits.right()))) + .toList(); + final List> transpose = ListUtils.transpose(lists); + return new Lists(transpose.get(0), transpose.get(1)); + } + + @Override + public Integer solvePart1(final Lists lists) { + return zip(sorted(lists.left), sorted(lists.right)).stream() + .mapToInt(z -> Math.abs(z.first() - z.second())) + .sum(); + } + + @Override + public Integer solvePart2(final Lists lists) { + final Counter counter = new Counter<>(lists.right); + return lists.left.stream() + .mapToInt(n -> n * counter.get(n).intValue()) + .sum(); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "11"), + @Sample(method = "part2", input = TEST, expected = "31"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_01.create().run(); + } + + private static final String TEST = """ + 3 4 + 4 3 + 2 5 + 1 3 + 3 9 + 3 3 + """; + + record Lists(List left, List right) {} +} \ No newline at end of file diff --git a/src/main/java/AoC2024_02.java b/src/main/java/AoC2024_02.java new file mode 100644 index 00000000..191d0afa --- /dev/null +++ b/src/main/java/AoC2024_02.java @@ -0,0 +1,83 @@ +import static com.github.pareronia.aoc.IntegerSequence.Range.range; +import static com.github.pareronia.aoc.IterTools.windows; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_02 + extends SolutionBase>, Integer, Integer> { + + private AoC2024_02(final boolean debug) { + super(debug); + } + + public static AoC2024_02 create() { + return new AoC2024_02(false); + } + + public static AoC2024_02 createDebug() { + return new AoC2024_02(true); + } + + @Override + protected List> parseInput(final List inputs) { + return inputs.stream() + .map(line -> Arrays.stream(line.split(" ")) + .map(Integer::parseInt) + .toList()) + .toList(); + } + + private boolean safe(final List levels) { + final List diffs = windows(levels).stream() + .map(w -> w.second() - w.first()) + .toList(); + return diffs.stream().allMatch(diff -> 1 <= diff && diff <= 3) + || diffs.stream().allMatch(diff -> -1 >= diff && diff >= -3); + } + + @Override + public Integer solvePart1(final List> reports) { + return (int) reports.stream().filter(this::safe).count(); + } + + @Override + public Integer solvePart2(final List> reports) { + return (int) reports.stream() + .filter(report -> + range(report.size()).intStream() + .mapToObj(i -> { + final List tmp = new ArrayList<>(report); + tmp.remove(i); + return tmp; + }) + .anyMatch(this::safe) + ).count(); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "2"), + @Sample(method = "part2", input = TEST, expected = "4"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_02.create().run(); + } + + private static final String TEST = """ + 7 6 4 2 1 + 1 2 7 8 9 + 9 7 6 2 1 + 1 3 2 4 5 + 8 6 4 4 1 + 1 3 6 7 9 + """; +} \ No newline at end of file diff --git a/src/main/java/AoC2024_03.java b/src/main/java/AoC2024_03.java new file mode 100644 index 00000000..5450ceaa --- /dev/null +++ b/src/main/java/AoC2024_03.java @@ -0,0 +1,84 @@ +import static java.lang.Integer.parseInt; +import static java.util.stream.Collectors.joining; + +import java.util.List; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_03 + extends SolutionBase { + + private final Pattern REGEX = Pattern.compile + ("(do(n't)?)\\(\\)|mul\\((\\d{1,3}),(\\d{1,3})\\)"); + + private AoC2024_03(final boolean debug) { + super(debug); + } + + public static AoC2024_03 create() { + return new AoC2024_03(false); + } + + public static AoC2024_03 createDebug() { + return new AoC2024_03(true); + } + + @Override + protected String parseInput(final List inputs) { + return inputs.stream().collect(joining()); + } + + private int solve(final String input, final boolean useConditionals) { + int ans = 0; + boolean enabled = true; + final Matcher matcher = REGEX.matcher(input); + while (matcher.find()) { + final MatchResult m = matcher.toMatchResult(); + if (m.group(0).equals("do()")) { + enabled = true; + } else if (m.group(0).equals("don't()")) { + enabled = false; + } else { + if (!useConditionals || enabled) { + ans += parseInt(m.group(3)) * parseInt(m.group(4)); + } + } + } + return ans; + } + + @Override + public Integer solvePart1(final String input) { + return solve(input, false); + } + + @Override + public Integer solvePart2(final String input) { + return solve(input, true); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "161"), + @Sample(method = "part2", input = TEST2, expected = "48"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_03.create().run(); + } + + private static final String TEST1 = """ + xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5)) + """; + + private static final String TEST2 = """ + xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5)) + """; +} \ No newline at end of file diff --git a/src/main/java/AoC2024_04.java b/src/main/java/AoC2024_04.java new file mode 100644 index 00000000..6213a3e1 --- /dev/null +++ b/src/main/java/AoC2024_04.java @@ -0,0 +1,85 @@ +import static com.github.pareronia.aoc.Utils.toAString; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import com.github.pareronia.aoc.CharGrid; +import com.github.pareronia.aoc.geometry.Direction; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_04 + extends SolutionBase { + + private AoC2024_04(final boolean debug) { + super(debug); + } + + public static AoC2024_04 create() { + return new AoC2024_04(false); + } + + public static AoC2024_04 createDebug() { + return new AoC2024_04(true); + } + + @Override + protected CharGrid parseInput(final List inputs) { + return CharGrid.from(inputs); + } + + @Override + public Integer solvePart1(final CharGrid grid) { + return (int) grid.getAllEqualTo('X') + .flatMap(cell -> Direction.OCTANTS.stream() + .map(d -> grid.getCells(cell, d).iterator())) + .filter(it -> Stream.of('M', 'A', 'S').allMatch( + ch -> it.hasNext() && grid.getValue(it.next()) == ch)) + .count(); + } + + @Override + public Integer solvePart2(final CharGrid grid) { + final List dirs = List.of( + Direction.LEFT_AND_UP, + Direction.RIGHT_AND_DOWN, + Direction.RIGHT_AND_UP, + Direction.LEFT_AND_DOWN + ); + final Set matches = Set.of("MSMS", "SMSM", "MSSM", "SMMS"); + return (int) grid.getCellsWithoutBorder() + .filter(cell -> grid.getValue(cell) == 'A') + .filter(cell -> matches.contains( + dirs.stream() + .map(d -> grid.getValue(cell.at(d))) + .collect(toAString()))) + .count(); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "18"), + @Sample(method = "part2", input = TEST, expected = "9"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_04.create().run(); + } + + private static final String TEST = """ + MMMSXXMASM + MSAMXMSMSA + AMXSXMAAMM + MSAMASMSMX + XMASAMXAMM + XXAMMXXAMA + SMSMSASXSS + SAXAMASAAA + MAMMMXMMMM + MXMXAXMASX + """; +} \ No newline at end of file diff --git a/src/main/java/AoC2024_05.java b/src/main/java/AoC2024_05.java new file mode 100644 index 00000000..f610f3f2 --- /dev/null +++ b/src/main/java/AoC2024_05.java @@ -0,0 +1,127 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_05 + extends SolutionBase { + + private AoC2024_05(final boolean debug) { + super(debug); + } + + public static AoC2024_05 create() { + return new AoC2024_05(false); + } + + public static AoC2024_05 createDebug() { + return new AoC2024_05(true); + } + + @Override + protected Input parseInput(final List inputs) { + final List> blocks = StringOps.toBlocks(inputs); + final Map> order = new HashMap<>(); + for (final String line : blocks.get(0)) { + final StringSplit split = StringOps.splitOnce(line, "\\|"); + order.computeIfAbsent( + Integer.valueOf(split.left()), k -> new ArrayList<>()) + .add(Integer.valueOf(split.right())); + } + final List> updates = blocks.get(1).stream() + .map(line -> Arrays.stream(line.split(",")) + .map(Integer::valueOf) + .toList()) + .toList(); + return new Input(order, updates); + } + + private int solve(final Input input, final Mode mode) { + final Comparator comparator + = (a, b) -> ( + input.order.getOrDefault(a, new ArrayList<>()).contains(b) + ? -1 : 1); + int ans = 0; + for (final List update : input.updates) { + final List correct = new ArrayList<>(update); + Collections.sort(correct, comparator); + if (!(mode == Mode.USE_CORRECT ^ update.equals(correct))) { + ans += correct.get(correct.size() / 2); + } + } + return ans; + } + + @Override + public Integer solvePart1(final Input input) { + return solve(input, Mode.USE_CORRECT); + } + + @Override + public Integer solvePart2(final Input input) { + return solve(input, Mode.USE_INCORRECT); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "143"), + @Sample(method = "part2", input = TEST, expected = "123"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_05.create().run(); + } + + private static final String TEST = """ + 47|53 + 97|13 + 97|61 + 97|47 + 75|29 + 61|13 + 75|53 + 29|13 + 97|29 + 53|29 + 61|53 + 97|53 + 61|29 + 47|13 + 75|47 + 97|75 + 47|61 + 75|61 + 47|29 + 75|13 + 53|13 + + 75,47,61,53,29 + 97,61,53,29,13 + 75,29,13 + 75,97,47,61,53 + 61,13,29 + 97,13,75,29,47 + """; + + record Input( + Map> order, + List> updates + ) { + } + + private enum Mode { + USE_CORRECT, + USE_INCORRECT; + } +} \ No newline at end of file diff --git a/src/main/java/AoC2024_06.java b/src/main/java/AoC2024_06.java new file mode 100644 index 00000000..17f2f560 --- /dev/null +++ b/src/main/java/AoC2024_06.java @@ -0,0 +1,226 @@ +import static com.github.pareronia.aoc.IntegerSequence.Range.range; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.function.ToIntFunction; +import java.util.stream.Stream; + +import com.github.pareronia.aoc.CharGrid; +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.geometry.Direction; +import com.github.pareronia.aoc.geometry.Turn; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_06 + extends SolutionBase { + + private AoC2024_06(final boolean debug) { + super(debug); + } + + public static AoC2024_06 create() { + return new AoC2024_06(false); + } + + public static AoC2024_06 createDebug() { + return new AoC2024_06(true); + } + + @Override + protected CharGrid parseInput(final List inputs) { + return new CharGrid(inputs); + } + + private LinkedHashMap> visited( + final CharGrid grid, Direction dir + ) { + Cell pos = grid.getAllEqualTo('^').findFirst().orElseThrow(); + final LinkedHashMap> visited + = new LinkedHashMap<>(Map.of(pos, new ArrayList<>(List.of(dir)))); + while (true) { + final Cell nxt = pos.at(dir); + if (!grid.isInBounds(nxt)) { + return visited; + } + if (grid.getValue(nxt) == '#') { + dir = dir.turn(Turn.RIGHT); + } else { + pos = nxt; + } + visited.computeIfAbsent(pos, k -> new ArrayList<>()).add(dir); + } + } + + @Override + public Integer solvePart1(final CharGrid grid) { + return visited(grid, Direction.UP).size(); + } + + private boolean isLoop( + Cell pos, Direction dir, final Obstacles obs, final Cell extra + ) { + final ToIntFunction bits = d -> switch (d) { + case UP -> 1; + case RIGHT -> 1 << 1; + case DOWN -> 1 << 2; + case LEFT -> 1 << 3; + default -> throw new IllegalArgumentException(); + }; + final Map seen = new HashMap<>(); + seen.put(pos, bits.applyAsInt(dir)); + while (true) { + final Optional nextObs = obs.getNext(pos, dir, extra); + if (nextObs.isEmpty()) { + return false; + } + pos = nextObs.get().at(dir.turn(Turn.AROUND)); + dir = dir.turn(Turn.RIGHT); + final int bDir = bits.applyAsInt(dir); + if (seen.containsKey(pos) && (seen.get(pos) & bDir) != 0) { + return true; + } + seen.merge(pos, bDir, (a, b) -> a |= b); + } + } + + @Override + public Integer solvePart2(final CharGrid grid) { + final Obstacles obs = Obstacles.from(grid); + final LinkedHashMap> visited + = visited(grid, Direction.UP); + final Iterator>> it + = visited.entrySet().iterator(); + Entry> prev = it.next(); + int ans = 0; + while (it.hasNext()) { + final Entry> curr = it.next(); + final Cell start = prev.getKey(); + final Direction dir = prev.getValue().remove(0); + if (isLoop(start, dir, obs, curr.getKey())) { + ans += 1; + } + prev = curr; + } + return ans; + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "41"), + @Sample(method = "part2", input = TEST, expected = "6"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_06.create().run(); + } + + private static final String TEST = """ + ....#..... + .........# + .......... + ..#....... + .......#.. + .......... + .#..^..... + ........#. + #......... + ......#... + """; + + record Obstacles(Map obs) { + private static Cell[][] obstacles( + final CharGrid grid, + final Stream starts, + final Direction dir + ) { + final Cell[][] obs = new Cell[grid.getHeight()][grid.getWidth()]; + starts.forEach(start -> { + Cell last = grid.getValue(start) == '#' ? start : null; + final Iterator it + = grid.getCells(start, dir).iterator(); + while (it.hasNext()) { + final Cell cell = it.next(); + obs[cell.getRow()][cell.getCol()] = last; + if (grid.getValue(cell) == '#') { + last = cell; + } + } + }); + return obs; + } + + public static Obstacles from(final CharGrid grid) { + final Map obs = new HashMap<>(); + obs.put(Direction.RIGHT, Obstacles.obstacles( + grid, + range(grid.getHeight()).intStream() + .mapToObj(r -> Cell.at(r, grid.getMaxColIndex())), + Direction.LEFT)); + obs.put(Direction.LEFT, Obstacles.obstacles( + grid, + range(grid.getHeight()).intStream() + .mapToObj(r -> Cell.at(r, 0)), + Direction.RIGHT)); + obs.put(Direction.UP, Obstacles.obstacles( + grid, + range(grid.getWidth()).intStream() + .mapToObj(c -> Cell.at(0, c)), + Direction.DOWN)); + obs.put(Direction.DOWN, Obstacles.obstacles( + grid, + range(grid.getWidth()).intStream() + .mapToObj(c -> Cell.at(grid.getMaxRowIndex(), c)), + Direction.UP)); + return new Obstacles(obs); + } + + public Optional getNext( + final Cell start, final Direction dir, final Cell extra + ) { + final Cell obs = this.obs.get(dir)[start.getRow()][start.getCol()]; + if (obs == null) { + if (dir.isHorizontal() + && start.getRow() == extra.getRow() + && start.to(extra) == dir + ) { + return Optional.of(extra); + } else if (dir.isVertical() + && start.getCol() == extra.getCol() + && start.to(extra) == dir + ) { + return Optional.of(extra); + } + return Optional.empty(); + } else { + int dExtra = 0; + int dObs = 0; + if (dir.isHorizontal() + && obs.getRow() == extra.getRow() + && start.to(extra) == dir + ) { + dExtra = Math.abs(extra.getCol() - start.getCol()); + dObs = Math.abs(obs.getCol() - start.getCol()); + return Optional.of(dObs < dExtra ? obs : extra); + } else if (dir.isVertical() + && obs.getCol() == extra.getCol() + && start.to(extra) == dir + ) { + dExtra = Math.abs(extra.getRow() - start.getRow()); + dObs = Math.abs(obs.getRow() - start.getRow()); + return Optional.of(dObs < dExtra ? obs : extra); + } + return Optional.of(obs); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/AoC2024_07.java b/src/main/java/AoC2024_07.java new file mode 100644 index 00000000..945b11a3 --- /dev/null +++ b/src/main/java/AoC2024_07.java @@ -0,0 +1,135 @@ +import static com.github.pareronia.aoc.Utils.last; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_07 + extends SolutionBase, Long, Long> { + + private AoC2024_07(final boolean debug) { + super(debug); + } + + public static AoC2024_07 create() { + return new AoC2024_07(false); + } + + public static AoC2024_07 createDebug() { + return new AoC2024_07(true); + } + + @Override + protected List parseInput(final List inputs) { + final List equations = new ArrayList<>(); + for (final String line : inputs) { + final StringSplit splits = StringOps.splitOnce(line, ": "); + final Long sol = Long.valueOf(splits.left()); + final List terms = Arrays.stream(splits.right() + .split(" ")) + .map(Long::valueOf).toList(); + equations.add(new Equation(sol, terms)); + } + return equations; + } + + private long solve(final List equations, final Set ops) { + class Test { + static boolean canObtain( + final long sol, + final List terms, + final Set ops + ) { + if (terms.size() == 1) { + return sol == terms.get(0); + } + if (ops.contains(Op.ADD) + && sol % last(terms) == 0 + && canObtain( + sol / last(terms), + terms.subList(0, terms.size() - 1), + ops)) { + return true; + } + if (ops.contains(Op.MULTIPLY) + && sol > last(terms) + && canObtain( + sol - last(terms), + terms.subList(0, terms.size() - 1), + ops)) { + return true; + } + if (ops.contains(Op.CONCATENATE)) { + final String sSol = String.valueOf(sol); + final String sLast = String.valueOf(last(terms)); + if (sSol.length() > sLast.length() + && sSol.endsWith(sLast)) { + final String newSol = sSol.substring( + 0, sSol.length() - sLast.length()); + if (canObtain( + Long.parseLong(newSol), + terms.subList(0, terms.size() - 1), + ops)) { + return true; + } + } + } + return false; + } + } + + return equations.stream() + .filter(eq -> Test.canObtain(eq.sol, eq.terms, ops)) + .mapToLong(Equation::sol) + .sum(); + } + + @Override + public Long solvePart1(final List equations) { + return solve(equations, Set.of(Op.ADD, Op.MULTIPLY)); + } + + @Override + public Long solvePart2(final List equations) { + return solve(equations, Set.of(Op.ADD, Op.MULTIPLY, Op.CONCATENATE)); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "3749"), + @Sample(method = "part2", input = TEST, expected = "11387"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_07.create().run(); + } + + private static final String TEST = """ + 190: 10 19 + 3267: 81 40 27 + 83: 17 5 + 156: 15 6 + 7290: 6 8 6 15 + 161011: 16 10 13 + 192: 17 8 14 + 21037: 9 7 18 13 + 292: 11 6 16 20 + """; + + private enum Op { + ADD, + MULTIPLY, + CONCATENATE; + } + + record Equation(Long sol, List terms) {} +} \ No newline at end of file diff --git a/src/main/java/AoC2024_08.java b/src/main/java/AoC2024_08.java new file mode 100644 index 00000000..121d2f2c --- /dev/null +++ b/src/main/java/AoC2024_08.java @@ -0,0 +1,159 @@ +import static com.github.pareronia.aoc.IterTools.combinations; +import static com.github.pareronia.aoc.IterTools.product; +import static com.github.pareronia.aoc.SetUtils.difference; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + +import com.github.pareronia.aoc.SetUtils; +import com.github.pareronia.aoc.geometry.Position; +import com.github.pareronia.aoc.geometry.Vector; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_08 + extends SolutionBase { + + private AoC2024_08(final boolean debug) { + super(debug); + } + + public static AoC2024_08 create() { + return new AoC2024_08(false); + } + + public static AoC2024_08 createDebug() { + return new AoC2024_08(true); + } + + @Override + protected Input parseInput(final List inputs) { + final int h = inputs.size(); + final int w = inputs.get(0).length(); + final Map> antennae = new HashMap<>(); + for (int r = 0; r < h; r++) { + for (int c = 0; c < w; c++) { + final char ch = inputs.get(r).charAt(c); + if (ch != '.') { + antennae.computeIfAbsent(ch, k -> new ArrayList<>()) + .add(Position.of(c, h - r - 1)); + } + } + } + return new Input(w, h, antennae.values().stream().toList()); + } + + private int solve( + final int h, + final int w, + final List> antennae, + final Mode mode + ) { + final Function, Stream> antennaPairs = + sameFrequency -> + combinations(sameFrequency.size(), 2).stream() + .map(comb_idx -> new AntennaPair( + sameFrequency.get(comb_idx[0]), + sameFrequency.get(comb_idx[1]))); + return antennae.stream() + .flatMap(antennaPairs) + .map(pair -> mode.collectAntinodes(h, w, pair)) + .reduce(SetUtils::union) + .map(Set::size).orElseThrow(); + } + + @Override + public Integer solvePart1(final Input input) { + return solve(input.h, input.w, input.antennae, Mode.MODE_1); + } + + @Override + public Integer solvePart2(final Input input) { + return solve(input.h, input.w, input.antennae, Mode.MODE_2); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "14"), + @Sample(method = "part2", input = TEST, expected = "34"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_08.create().run(); + } + + enum Mode { + MODE_1, MODE_2; + + private Set getAntinodes( + final AntennaPair pair, + final int h, + final int w, + final int maxCount + ) { + final Vector vec = Vector.of( + pair.first.getX() - pair.second.getX(), + pair.first.getY() - pair.second.getY()); + final Set antinodes = new HashSet<>(); + product(pair, Set.of(1, -1)).stream().forEach(pp -> { + for (int a = 1; a <= maxCount; a++) { + final Position antinode + = pp.first().translate(vec, pp.second() * a); + if (0 <= antinode.getX() && antinode.getX() < w + && 0 <= antinode.getY() && antinode.getY() < h) { + antinodes.add(antinode); + } else { + break; + } + } + }); + return antinodes; + } + + public Set collectAntinodes( + final int h, final int w, final AntennaPair pair + ) { + return switch (this) { + case MODE_1 -> difference( + getAntinodes(pair, h, w, 1), + Set.of(pair.first, pair.second)); + case MODE_2 -> getAntinodes(pair, h, w, Integer.MAX_VALUE); + }; + } + } + + private static final String TEST = """ + ............ + ........0... + .....0...... + .......0.... + ....0....... + ......A..... + ............ + ............ + ........A... + .........A.. + ............ + ............ + """; + + record Input(int w, int h, List> antennae) {} + + record AntennaPair(Position first, Position second) implements Iterable { + + @Override + public Iterator iterator() { + return Set.of(first, second).iterator(); + } + } +} \ No newline at end of file diff --git a/src/main/java/AoC2024_09.java b/src/main/java/AoC2024_09.java new file mode 100644 index 00000000..d35ac38f --- /dev/null +++ b/src/main/java/AoC2024_09.java @@ -0,0 +1,135 @@ +import static com.github.pareronia.aoc.IntegerSequence.Range.range; +import static com.github.pareronia.aoc.IterTools.enumerateFrom; + +import java.util.ArrayList; +import java.util.List; +import java.util.PriorityQueue; + +import com.github.pareronia.aoc.Utils; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_09 + extends SolutionBase { + + public static final long[] TRIANGLE = {0, 0, 1, 3, 6, 10, 15, 21, 28, 36}; + + private AoC2024_09(final boolean debug) { + super(debug); + } + + public static AoC2024_09 create() { + return new AoC2024_09(false); + } + + public static AoC2024_09 createDebug() { + return new AoC2024_09(true); + } + + @Override + protected int[] parseInput(final List inputs) { + return Utils.asCharacterStream(inputs.get(0)) + .mapToInt(ch -> ch - '0') + .toArray(); + } + + public long solve(final int[] input, final Mode mode) { + final List files = new ArrayList<>(input.length * 10); + final List> freeBySize = new ArrayList<>(10); + for (int i = 0; i < 10; i++) { + freeBySize.add(new PriorityQueue<>()); + } + boolean isFree = false; + int id = 0; + int pos = 0; + for (final int n : input) { + if (isFree) { + freeBySize.get(n).add(pos); + } else { + files.addAll(mode.createFiles(id, pos, n)); + id++; + } + pos += n; + isFree = !isFree; + } + long ans = 0; + for (int j = files.size() - 1; j >= 0; j--) { + final File file = files.get(j); + enumerateFrom(file.size, freeBySize.stream().skip(file.size)) + .filter(e -> !e.value().isEmpty()) + .min((e1, e2) -> Integer.compare(e1.value().peek(), e2.value().peek())) + .filter(e -> e.value().peek() < file.pos) + .ifPresent(e -> { + final int free_size = e.index(); + final int free_pos = e.value().poll(); + file.pos = free_pos; + if (file.size < free_size) { + freeBySize.get(free_size - file.size) + .add(file.pos + file.size); + } + }); + ans += mode.fileChecksum(file); + } + return ans; + } + + @Override + public Long solvePart1(final int[] input) { + return solve(input, Mode.MODE_1); + } + + @Override + public Long solvePart2(final int[] input) { + return solve(input, Mode.MODE_2); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "1928"), + @Sample(method = "part2", input = TEST, expected = "2858"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_09.create().run(); + } + + private static final String TEST = "2333133121414131402"; + + private static class File { + int id; + int pos; + int size; + + public File(final int id, final int pos, final int size) { + this.id = id; + this.pos = pos; + this.size = size; + } + } + + private enum Mode { + MODE_1, + MODE_2; + + public List createFiles( + final int id, final int pos, final int size + ) { + return switch (this) { + case MODE_1 -> range(size).intStream() + .mapToObj(i -> new File(id, pos + i, 1)) + .toList(); + case MODE_2 -> List.of(new File(id, pos, size)); + }; + } + + public long fileChecksum(final File f) { + return switch (this) { + case MODE_1 -> (long) f.id * f.pos; + case MODE_2 -> f.id * (f.pos * f.size + TRIANGLE[f.size]); + }; + } + } +} \ No newline at end of file diff --git a/src/main/java/AoC2024_10.java b/src/main/java/AoC2024_10.java new file mode 100644 index 00000000..8f1324a9 --- /dev/null +++ b/src/main/java/AoC2024_10.java @@ -0,0 +1,109 @@ +import static com.github.pareronia.aoc.Utils.concat; +import static com.github.pareronia.aoc.Utils.last; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.IntGrid; +import com.github.pareronia.aoc.Utils; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_10 extends SolutionBase { + + private AoC2024_10(final boolean debug) { + super(debug); + } + + public static AoC2024_10 create() { + return new AoC2024_10(false); + } + + public static AoC2024_10 createDebug() { + return new AoC2024_10(true); + } + + @Override + protected IntGrid parseInput(final List inputs) { + return IntGrid.from(inputs); + } + + private int solve(final IntGrid grid, final Grading grading) { + class BFS { + static List> bfs( + final IntGrid grid, final Cell trailHead + ) { + final List> trails = new ArrayList<>(); + final Deque> q + = new ArrayDeque<>(List.of(List.of(trailHead))); + while (!q.isEmpty()) { + final List curr = q.pop(); + if (curr.size() == 10) { + trails.add(curr); + continue; + } + final int next = grid.getValue(last(curr)) + 1; + grid.getCapitalNeighbours(last(curr)) + .filter(n -> grid.getValue(n) == next) + .forEach(n -> q.add(concat(curr, n))); + } + return trails; + } + } + + return grid.getAllEqualTo(0) + .mapToInt(trailhead -> grading.get(BFS.bfs(grid, trailhead))) + .sum(); + } + + @Override + public Integer solvePart1(final IntGrid grid) { + return solve(grid, Grading.SCORE); + } + + @Override + public Integer solvePart2(final IntGrid grid) { + return solve(grid, Grading.RATING); + } + + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "36"), + @Sample(method = "part2", input = TEST, expected = "81"), + }) + public void samples() { + } + + enum Grading { + SCORE, RATING; + + public int get(final List> trails) { + return switch (this) { + case SCORE -> (int) trails.stream() + .map(Utils::last) + .distinct().count(); + case RATING -> trails.size(); + }; + } + } + + public static void main(final String[] args) throws Exception { + AoC2024_10.create().run(); + } + + private static final String TEST = """ + 89010123 + 78121874 + 87430965 + 96549874 + 45678903 + 32019012 + 01329801 + 10456732 + """; +} \ No newline at end of file diff --git a/src/main/java/AoC2024_11.java b/src/main/java/AoC2024_11.java new file mode 100644 index 00000000..c6821379 --- /dev/null +++ b/src/main/java/AoC2024_11.java @@ -0,0 +1,88 @@ +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_11 extends SolutionBase, Long, Long> { + + private AoC2024_11(final boolean debug) { + super(debug); + } + + public static AoC2024_11 create() { + return new AoC2024_11(false); + } + + public static AoC2024_11 createDebug() { + return new AoC2024_11(true); + } + + @Override + protected List parseInput(final List inputs) { + return Arrays.stream(inputs.get(0).split(" ")) + .map(Long::valueOf) + .toList(); + } + + private long solve(final List stones, final int blinks) { + class Counter { + record State(long stone, int cnt) {} + + static final Map memo = new HashMap<>(); + + static long count(final long s, final int cnt) { + final State state = new State(s, cnt); + if (memo.containsKey(state)) { + return memo.get(state); + } + long ans; + if (cnt == 0) { + ans = 1; + } else if (s == 0) { + ans = count(1, cnt - 1); + } else if (String.valueOf(s).length() % 2 == 0) { + final String ss = String.valueOf(s); + final int length = ss.length(); + final long ss1 = Long.parseLong(ss.substring(0, length / 2)); + final long ss2 = Long.parseLong(ss.substring(length / 2)); + ans = count(ss1, cnt - 1) + count(ss2, cnt - 1); + } else { + ans = count(s * 2024, cnt - 1); + } + memo.put(state, ans); + return ans; + } + } + + return stones.stream().mapToLong(s -> Counter.count(s, blinks)).sum(); + } + + @Override + public Long solvePart1(final List stones) { + return solve(stones, 25); + } + + @Override + public Long solvePart2(final List stones) { + return solve(stones, 75); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "55312") + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_11.create().run(); + } + + private static final String TEST = """ + 125 17 + """; +} \ No newline at end of file diff --git a/src/main/java/AoC2024_12.java b/src/main/java/AoC2024_12.java new file mode 100644 index 00000000..07a89f00 --- /dev/null +++ b/src/main/java/AoC2024_12.java @@ -0,0 +1,174 @@ +import static com.github.pareronia.aoc.IntegerSequence.Range.range; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.IterTools.IterToolsIterator; +import com.github.pareronia.aoc.geometry.Direction; +import com.github.pareronia.aoc.graph.BFS; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_12 extends SolutionBase, Integer, Integer> { + + private AoC2024_12(final boolean debug) { + super(debug); + } + + public static AoC2024_12 create() { + return new AoC2024_12(false); + } + + public static AoC2024_12 createDebug() { + return new AoC2024_12(true); + } + + @Override + protected List parseInput(final List inputs) { + return inputs; + } + + private int solve(final List input, final Pricing pricing) { + final Map> plotsByPlant = new HashMap<>(); + range(input.size()).forEach(r -> + range(input.get(r).length()).forEach(c -> + plotsByPlant + .computeIfAbsent(input.get(r).charAt(c), k -> new HashSet<>()) + .add(Cell.at(r, c)))); + final IterToolsIterator> regions = new IterToolsIterator<>() { + final Iterator keys = plotsByPlant.keySet().iterator(); + char key = keys.next(); + Set allPlotsWithPlant = plotsByPlant.get(key); + + @Override + public Set next() { + if (allPlotsWithPlant.isEmpty()) { + key = keys.next(); + allPlotsWithPlant = plotsByPlant.get(key); + } + final Set region = BFS.floodFill( + allPlotsWithPlant.iterator().next(), + cell -> cell.capitalNeighbours() + .filter(allPlotsWithPlant::contains)); + allPlotsWithPlant.removeAll(region); + return region; + } + + @Override + public boolean hasNext() { + return !allPlotsWithPlant.isEmpty() || keys.hasNext(); + } + }; + return regions.stream() + .mapToInt(region -> region.stream() + .mapToInt(plot -> pricing.calculate(plot, region)) + .sum() * region.size()) + .sum(); + } + + @Override + public Integer solvePart1(final List input) { + return solve(input, Pricing.PERIMETER); + } + + @Override + public Integer solvePart2(final List input) { + return solve(input, Pricing.NUMBER_OF_SIDES); + } + + private enum Pricing { + PERIMETER, NUMBER_OF_SIDES; + + private static final List> CORNER_DIRS = List.of( + List.of(Direction.LEFT_AND_UP, Direction.LEFT, Direction.UP), + List.of(Direction.RIGHT_AND_UP, Direction.RIGHT, Direction.UP), + List.of(Direction.RIGHT_AND_DOWN, Direction.RIGHT, Direction.DOWN), + List.of(Direction.LEFT_AND_DOWN, Direction.LEFT, Direction.DOWN) + ); + private static final Set MATCHES = Set.of( + new int[] {0, 0, 0}, new int[] {1, 0, 0}, new int[] {0, 1, 1}); + + public int calculate(final Cell plot, final Set region) { + return switch (this) { + case PERIMETER -> 4 - (int) plot.capitalNeighbours() + .filter(region::contains) + .count(); + case NUMBER_OF_SIDES -> (int) CORNER_DIRS.stream() + .filter(d -> { + final int[] test = range(3).intStream() + .map(i -> region.contains(plot.at(d.get(i))) ? 1 : 0) + .toArray(); + return MATCHES.stream() + .anyMatch(m -> Arrays.equals(m, test)); + }) + .count(); + }; + } + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "140"), + @Sample(method = "part1", input = TEST2, expected = "772"), + @Sample(method = "part1", input = TEST3, expected = "1930"), + @Sample(method = "part2", input = TEST1, expected = "80"), + @Sample(method = "part2", input = TEST2, expected = "436"), + @Sample(method = "part2", input = TEST3, expected = "1206"), + @Sample(method = "part2", input = TEST4, expected = "236"), + @Sample(method = "part2", input = TEST5, expected = "368"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_12.create().run(); + } + + private static final String TEST1 = """ + AAAA + BBCD + BBCC + EEEC + """; + private static final String TEST2 = """ + OOOOO + OXOXO + OOOOO + OXOXO + OOOOO + """; + private static final String TEST3 = """ + RRRRIICCFF + RRRRIICCCF + VVRRRCCFFF + VVRCCCJFFF + VVVVCJJCFE + VVIVCCJJEE + VVIIICJJEE + MIIIIIJJEE + MIIISIJEEE + MMMISSJEEE + """; + private static final String TEST4 = """ + EEEEE + EXXXX + EEEEE + EXXXX + EEEEE + """; + private static final String TEST5 = """ + AAAAAA + AAABBA + AAABBA + ABBAAA + ABBAAA + AAAAAA + """; +} \ No newline at end of file diff --git a/src/main/java/AoC2024_13.java b/src/main/java/AoC2024_13.java new file mode 100644 index 00000000..8f8c5c76 --- /dev/null +++ b/src/main/java/AoC2024_13.java @@ -0,0 +1,106 @@ +import static com.github.pareronia.aoc.StringOps.splitOnce; +import static java.util.stream.Collectors.summingLong; + +import java.util.List; +import java.util.Optional; + +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_13 + extends SolutionBase, Long, Long> { + + private AoC2024_13(final boolean debug) { + super(debug); + } + + public static AoC2024_13 create() { + return new AoC2024_13(false); + } + + public static AoC2024_13 createDebug() { + return new AoC2024_13(true); + } + + @Override + protected List parseInput(final List inputs) { + return StringOps.toBlocks(inputs).stream() + .map(Machine::fromInput) + .toList(); + } + + private long solve(final List machines, final long offset) { + return machines.stream() + .map(m -> m.calcTokens(offset)) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(summingLong(Long::longValue)); + } + + @Override + public Long solvePart1(final List machines) { + return solve(machines, 0); + } + + @Override + public Long solvePart2(final List machines) { + return solve(machines, 10_000_000_000_000L); + } + + @Override + @Samples({ @Sample(method = "part1", input = TEST, expected = "480") }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_13.create().run(); + } + + private static final String TEST = """ + Button A: X+94, Y+34 + Button B: X+22, Y+67 + Prize: X=8400, Y=5400 + + Button A: X+26, Y+66 + Button B: X+67, Y+21 + Prize: X=12748, Y=12176 + + Button A: X+17, Y+86 + Button B: X+84, Y+37 + Prize: X=7870, Y=6450 + + Button A: X+69, Y+23 + Button B: X+27, Y+71 + Prize: X=18641, Y=10279 + """; + + record Machine(long ax, long bx, long ay, long by, long px, long py) { + + public static Machine fromInput(final List input) { + final long ax = Long.parseLong(input.get(0).substring(12, 14)); + final long ay = Long.parseLong(input.get(0).substring(18, 20)); + final long bx = Long.parseLong(input.get(1).substring(12, 14)); + final long by = Long.parseLong(input.get(1).substring(18, 20)); + final StringSplit sp = splitOnce(input.get(2), ", "); + final long px = Long.parseLong(splitOnce(sp.left(), "=").right()); + final long py = Long.parseLong((sp.right().substring(2))); + return new Machine(ax, bx, ay, by, px, py); + } + + public Optional calcTokens(final long offset) { + final long px = this.px + offset; + final long py = this.py + offset; + final double div = this.bx * this.ay - this.ax * this.by; + final double a = (py * this.bx - px * this.by) / div; + final double b = (px * this.ay - py * this.ax) / div; + if ((a % 1) == 0 && (b % 1) == 0) { + return Optional.of((long) a * 3 + (long) b); + } else { + return Optional.empty(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/AoC2024_14.java b/src/main/java/AoC2024_14.java new file mode 100644 index 00000000..b792e2b1 --- /dev/null +++ b/src/main/java/AoC2024_14.java @@ -0,0 +1,160 @@ +import static com.github.pareronia.aoc.StringOps.splitLines; +import static com.github.pareronia.aoc.StringOps.splitOnce; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import com.github.pareronia.aoc.IntegerSequence.Range; +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.geometry.Position; +import com.github.pareronia.aoc.geometry.Vector; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_14 + extends SolutionBase, Integer, Integer> { + + private static final int W = 101; + private static final int H = 103; + + private AoC2024_14(final boolean debug) { + super(debug); + } + + public static AoC2024_14 create() { + return new AoC2024_14(false); + } + + public static AoC2024_14 createDebug() { + return new AoC2024_14(true); + } + + @Override + protected List parseInput(final List inputs) { + return inputs.stream().map(Robot::fromInput).toList(); + } + + private int getSafetyFactor( + final List robots, + final int w, + final int h, + final int rounds + ) { + final int midW = w / 2; + final int midH = h / 2; + final int[] q = {0, 0, 0, 0}; + robots.stream() + .map(r -> r.position.translate(r.velocity, rounds)) + .forEach(p -> { + final int px = Math.floorMod(p.getX(), w); + final int py = Math.floorMod(p.getY(), h); + if (px < midW) { + if (py < midH) { + q[0] += 1; + } else if (midH < py) { + q[1] += 1; + } + } else if (midW < px) { + if (py < midH) { + q[2] += 1; + } else if (midH < py) { + q[3] += 1; + } + } + }); + return Arrays.stream(q).reduce(1, (a, b) -> a * b); + } + + private int solve1(final List robots, final int w, final int h) { + return getSafetyFactor(robots, w, h, 100); + } + + @Override + public Integer solvePart1(final List robots) { + return solve1(robots, W, H); + } + + @Override + public Integer solvePart2(final List robots) { + int ans = 0; + int best = Integer.MAX_VALUE; + int round = 1; + final List sfs = new ArrayList<>(); + while (round <= W + H) { + final int safetyFactor = getSafetyFactor(robots, W, H, round); + sfs.add(safetyFactor); + if (safetyFactor < best) { + best = safetyFactor; + ans = round; + } + round++; + } + final List mins = new ArrayList<>(); + Range.between(2, sfs.size() - 3).forEach(i -> { + final int avg = Set.of(-2, -1, 1, 2).stream() + .mapToInt(j -> sfs.get(i + j)).sum() / 4; + if (sfs.get(i) < avg * 9 / 10) { + mins.add(i + 1); + } + }); + final int period = mins.get(2) - mins.get(0); + round = mins.get(2) + period; + while (round <= W * H) { + final int safetyFactor = getSafetyFactor(robots, W, H, round); + if (safetyFactor < best) { + best = safetyFactor; + ans = round; + } + round += period; + } + return ans; + } + + @Override + public void samples() { + final AoC2024_14 test = AoC2024_14.createDebug(); + final List input = test.parseInput(splitLines(TEST)); + assert test.solve1(input, 11, 7) == 12; + } + + public static void main(final String[] args) throws Exception { + AoC2024_14.create().run(); + } + + private static final String TEST = """ + p=0,4 v=3,-3 + p=6,3 v=-1,-3 + p=10,3 v=-1,2 + p=2,0 v=2,-1 + p=0,0 v=1,3 + p=3,0 v=-2,-2 + p=7,6 v=-1,-3 + p=3,0 v=-1,-2 + p=9,3 v=2,3 + p=7,3 v=-1,2 + p=2,4 v=2,-3 + p=9,5 v=-3,-3 + """; + + record Robot(Position position, Vector velocity) { + + public static Robot fromInput(final String line) { + final StringSplit split = splitOnce(line, " "); + final StringSplit p = splitOnce( + splitOnce(split.left(), "=").right(), + ","); + final StringSplit v = splitOnce( + splitOnce(split.right(), "=").right(), + ","); + return new Robot( + Position.of( + Integer.parseInt(p.left()), + Integer.parseInt(p.right())), + Vector.of( + Integer.parseInt(v.left()), + Integer.parseInt(v.right())) + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/AoC2024_15.java b/src/main/java/AoC2024_15.java new file mode 100644 index 00000000..0a9cb1ea --- /dev/null +++ b/src/main/java/AoC2024_15.java @@ -0,0 +1,224 @@ +import static com.github.pareronia.aoc.IntegerSequence.Range.range; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toMap; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import com.github.pareronia.aoc.CharGrid; +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.StringOps; +import com.github.pareronia.aoc.Utils; +import com.github.pareronia.aoc.geometry.Direction; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_15 + extends SolutionBase { + + private static final char FLOOR = '.'; + private static final char WALL = '#'; + private static final char ROBOT = '@'; + private static final char BOX = 'O'; + private static final char BIG_BOX_LEFT = '['; + private static final char BIG_BOX_RIGHT = ']'; + + private AoC2024_15(final boolean debug) { + super(debug); + } + + public static AoC2024_15 create() { + return new AoC2024_15(false); + } + + public static AoC2024_15 createDebug() { + return new AoC2024_15(true); + } + + @Override + protected Input parseInput(final List inputs) { + final List> blocks = StringOps.toBlocks(inputs); + final List dirs = Utils.asCharacterStream( + blocks.get(1).stream().collect(joining())) + .map(Direction::fromChar) + .toList(); + return new Input(blocks.get(0), dirs); + } + + private int solve( + final Warehouse warehouse, final List dirs + ) { + Cell robot = warehouse.grid().getAllEqualTo(ROBOT) + .findFirst().orElseThrow(); + for (final Direction dir : dirs) { + final List toMove = warehouse.getToMove(robot, dir); + if (toMove.isEmpty()) { + continue; + } + final Map vals = toMove.stream() + .collect(toMap(tm -> tm, warehouse.grid::getValue)); + robot = robot.at(dir); + for (final Cell cell : toMove) { + warehouse.grid.setValue(cell, FLOOR); + } + for (final Cell cell : toMove) { + warehouse.grid.setValue(cell.at(dir), vals.get(cell)); + } + } + return warehouse.getBoxes() + .mapToInt(cell -> cell.getRow() * 100 + cell.getCol()) + .sum(); + } + + @Override + public Integer solvePart1(final Input input) { + return solve( + Warehouse.create(Warehouse.Type.WAREHOUSE_1, input.grid), + input.dirs); + } + + @Override + public Integer solvePart2(final Input input) { + return solve( + Warehouse.create(Warehouse.Type.WAREHOUSE_2, input.grid), + input.dirs); + } + + record Warehouse(Type type, CharGrid grid) { + private static final Map SCALE_UP = Map.of( + FLOOR, new char[] { FLOOR, FLOOR }, + WALL, new char[] { WALL, WALL }, + ROBOT, new char[] { ROBOT, FLOOR }, + BOX, new char[] { BIG_BOX_LEFT, BIG_BOX_RIGHT } + ); + + enum Type { + WAREHOUSE_1, WAREHOUSE_2; + } + + public static Warehouse create( + final Type type, final List gridIn + ) { + final CharGrid grid = switch (type) { + case WAREHOUSE_1: yield CharGrid.from(gridIn); + case WAREHOUSE_2 : { + final char[][] chars = new char[gridIn.size()][]; + for (final int r : range(gridIn.size())) { + final String line = gridIn.get(r); + final char[] row = new char[2 * line.length()]; + for(final int c : range(line.length())) { + final char[] s = SCALE_UP.get(line.charAt(c)); + row[2 * c] = s[0]; + row[2 * c + 1] = s[1]; + } + chars[r] = row; + } + yield new CharGrid(chars); + } + }; + return new Warehouse(type, grid); + } + + public List getToMove(final Cell robot, final Direction dir) { + final List toMove = new ArrayList<>(List.of(robot)); + final Deque q = new ArrayDeque<>(toMove); + while (!q.isEmpty()) { + final Cell cell = q.pop(); + final Cell nxt = cell.at(dir); + if (q.contains(nxt)) { + continue; + } + final char nxtVal = grid.getValue(nxt); + if (nxtVal == WALL) { + return List.of(); + } + switch(this.type) { + case WAREHOUSE_1: { + if (nxtVal == BOX) { + q.add(nxt); + toMove.add(nxt); + } + } + case WAREHOUSE_2: { + final Cell also; + if (nxtVal == BIG_BOX_LEFT) { + also = nxt.at(Direction.RIGHT); + } else if (nxtVal == BIG_BOX_RIGHT) { + also = nxt.at(Direction.LEFT); + } else { + continue; + } + q.add(nxt); + q.add(also); + toMove.add(nxt); + toMove.add(also); + } + } + } + return toMove; + } + + public Stream getBoxes() { + return switch(this.type) { + case WAREHOUSE_1 -> grid.getAllEqualTo(BOX); + case WAREHOUSE_2 -> grid.getAllEqualTo(BIG_BOX_LEFT); + }; + } + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "2028"), + @Sample(method = "part1", input = TEST2, expected = "10092"), + @Sample(method = "part2", input = TEST2, expected = "9021"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_15.create().run(); + } + + private static final String TEST1 = """ + ######## + #..O.O.# + ##@.O..# + #...O..# + #.#.O..# + #...O..# + #......# + ######## + + <^^>>>vv>v<< + """; + private static final String TEST2 = """ + ########## + #..O..O.O# + #......O.# + #.OO..O.O# + #..O@..O.# + #O#..O...# + #O..O..O.# + #.OO.O.OO# + #....O...# + ########## + + ^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ + vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< + <>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ + ^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< + ^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ + <><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> + ^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< + v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ + """; + + record Input(List grid, List dirs) {} +} \ No newline at end of file diff --git a/src/main/java/AoC2024_16.java b/src/main/java/AoC2024_16.java new file mode 100644 index 00000000..070b4114 --- /dev/null +++ b/src/main/java/AoC2024_16.java @@ -0,0 +1,135 @@ +import static com.github.pareronia.aoc.IterTools.product; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import com.github.pareronia.aoc.Grid.Cell; +import com.github.pareronia.aoc.geometry.Direction; +import com.github.pareronia.aoc.geometry.Turn; +import com.github.pareronia.aoc.graph.Dijkstra; +import com.github.pareronia.aoc.graph.Dijkstra.AllResults; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public final class AoC2024_16 + extends SolutionBase { + + private AoC2024_16(final boolean debug) { + super(debug); + } + + public static AoC2024_16 create() { + return new AoC2024_16(false); + } + + public static AoC2024_16 createDebug() { + return new AoC2024_16(true); + } + + @Override + protected ReindeerMaze parseInput(final List inputs) { + return ReindeerMaze.fromInput(inputs); + } + + @Override + public Integer solvePart1(final ReindeerMaze maze) { + return (int) Dijkstra.distance( + new State(maze.start, Direction.RIGHT), + state -> state.cell.equals(maze.end), + state -> maze.adjacent(state), + (curr, next) -> curr.direction == next.direction ? 1 : 1001); + } + + @Override + public Integer solvePart2(final ReindeerMaze maze) { + final AllResults result = Dijkstra.all( + new State(maze.start, Direction.RIGHT), + state -> state.cell.equals(maze.end), + state -> maze.adjacent(state), + (curr, next) -> curr.direction == next.direction ? 1 : 1001); + return (int) product(List.of(maze.end), Direction.CAPITAL).stream() + .flatMap(pp -> result.getPaths(new State(pp.first(), pp.second())).stream()) + .flatMap(List::stream) + .map(State::cell) + .distinct() + .count(); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "7036"), + @Sample(method = "part1", input = TEST2, expected = "11048"), + @Sample(method = "part2", input = TEST1, expected = "45"), + @Sample(method = "part2", input = TEST2, expected = "64"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_16.create().run(); + } + + private static final String TEST1 = """ + ############### + #.......#....E# + #.#.###.#.###.# + #.....#.#...#.# + #.###.#####.#.# + #.#.#.......#.# + #.#.#####.###.# + #...........#.# + ###.#.#####.#.# + #...#.....#.#.# + #.#.#.###.#.#.# + #.....#...#.#.# + #.###.#.#.#.#.# + #S..#.....#...# + ############### + """; + private static final String TEST2 = """ + ################# + #...#...#...#..E# + #.#.#.#.#.#.#.#.# + #.#.#.#...#...#.# + #.#.#.#.###.#.#.# + #...#.#.#.....#.# + #.#.#.#.#.#####.# + #.#...#.#.#.....# + #.#.#####.#.###.# + #.#.#.......#...# + #.#.###.#####.### + #.#.#...#.....#.# + #.#.#.#####.###.# + #.#.#.........#.# + #.#.#.#########.# + #S#.............# + ################# + """; + + record State(Cell cell, Direction direction) {} + + record ReindeerMaze(char[][] grid, Cell start, Cell end) { + + public static ReindeerMaze fromInput(final List strings) { + final int h = strings.size(); + final int w = strings.get(0).length(); + final char[][] cells = new char[h][w]; + for (int i = 0; i < h; i++) { + cells[i] = strings.get(i).toCharArray(); + } + return new ReindeerMaze(cells, Cell.at(h - 2, 1), Cell.at(1, w - 2)); + } + + public Stream adjacent(final State state) { + return Set.of( + state.direction, + state.direction.turn(Turn.RIGHT), + state.direction.turn(Turn.LEFT) + ).stream() + .map(dir -> new State(state.cell.at(dir), dir)) + .filter(st -> grid[st.cell.getRow()][st.cell.getCol()] != '#'); + } + } +} \ No newline at end of file diff --git a/src/main/java/AoC2024_21.java b/src/main/java/AoC2024_21.java new file mode 100644 index 00000000..f6fccec9 --- /dev/null +++ b/src/main/java/AoC2024_21.java @@ -0,0 +1,164 @@ +import static com.github.pareronia.aoc.StringOps.zip; +import static java.util.Comparator.comparingLong; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.ToLongFunction; + +import com.github.pareronia.aoc.IterTools; +import com.github.pareronia.aoc.IterTools.ZippedPair; +import com.github.pareronia.aoc.Utils; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public class AoC2024_21 extends SolutionBase, Long, Long> { + + public static final KeyPad NUMERIC + = new KeyPad(new String[] {"789", "456", "123", " 0A"}); + public static final KeyPad DIRECTIONAL + = new KeyPad(new String[] {" ^A", ""}); + + private final Map cache = new HashMap<>(); + + private AoC2024_21(final boolean debug) { + super(debug); + } + + public static final AoC2024_21 create() { + return new AoC2024_21(false); + } + + public static final AoC2024_21 createDebug() { + return new AoC2024_21(true); + } + + @Override + protected List parseInput(final List inputs) { + return inputs; + } + + private Iterator paths( + final KeyPad keypad, + final Position from, + final Position to, + final String s + ) { + if (from.equals(to)) { + return List.of(s + "A").iterator(); + } + Iterator ans = List.of().iterator(); + Position newFrom = new Position(from.x - 1, from.y); + if (to.x < from.x && keypad.getChar(newFrom) != ' ') { + ans = IterTools.chain(ans, paths(keypad, newFrom, to, s + "<")); + } + newFrom = new Position(from.x, from.y - 1); + if (to.y < from.y && keypad.getChar(newFrom) != ' ') { + ans = IterTools.chain(ans, paths(keypad, newFrom, to, s + "^")); + } + newFrom = new Position(from.x, from.y + 1); + if (to.y > from.y && keypad.getChar(newFrom) != ' ') { + ans = IterTools.chain(ans, paths(keypad, newFrom, to, s + "v")); + } + newFrom = new Position(from.x + 1, from.y); + if (to.x > from.x && keypad.getChar(newFrom) != ' ') { + ans = IterTools.chain(ans, paths(keypad, newFrom, to, s + ">")); + } + return ans; + } + + private long count( + final String path, final int level, final int maxLevel + ) { + if (level > maxLevel) { + return path.length(); + } + final KeyPad keypad = level > 0 ? DIRECTIONAL : NUMERIC; + final ToLongFunction countConsecutiveSameChars = s -> + zip(s, s.substring(1)).stream() + .filter(zp -> zp.first() == zp.second()) + .count(); + final Function, String> bestPath = zp -> { + final Position from = keypad.getPosition(zp.first()); + final Position to = keypad.getPosition(zp.second()); + return Utils.stream(paths(keypad, from, to, "")) + .max(comparingLong(countConsecutiveSameChars)) + .orElseThrow();}; + return zip("A" + path, path).stream() + .map(bestPath) + .mapToLong(best -> cachedCount(best, level + 1, maxLevel)) + .sum(); + } + + private long cachedCount( + final String path, final int level, final int maxLevel + ) { + final String key = "%s%d".formatted(path, level); + if (cache.containsKey(key)) { + return cache.get(key); + } else { + final long ans = count(path, level, maxLevel); + cache.put(key, ans); + return ans; + } + } + + private long solve(final List input, final int levels) { + cache.clear(); + return input.stream() + .mapToLong(s -> cachedCount(s, 0, levels) + * Long.parseLong(s.substring(0, s.length() - 1))) + .sum(); + } + + @Override + public Long solvePart1(final List input) { + return solve(input, 2); + } + + @Override + public Long solvePart2(final List input) { + return solve(input, 25); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST, expected = "126384"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_21.create().run(); + } + + private static final String TEST = """ + 029A + 980A + 179A + 456A + 379A + """; + + record Position(int x, int y) {} + + record KeyPad(String[] layout) { + public Position getPosition(final char ch) { + for (int y = 0; y < layout.length; y++) { + for (int x = 0; x < layout[y].length(); x++) { + if (layout[y].charAt(x) == ch) { + return new Position(x, y); + } + } + } + throw new IllegalStateException("Unsolvable"); + } + + public char getChar(final Position pos) { + return layout[pos.y].charAt(pos.x); + } + } +} \ No newline at end of file diff --git a/src/main/java/AoC2024_24.java b/src/main/java/AoC2024_24.java new file mode 100644 index 00000000..2e665eeb --- /dev/null +++ b/src/main/java/AoC2024_24.java @@ -0,0 +1,290 @@ +import static com.github.pareronia.aoc.IntegerSequence.Range.rangeClosed; +import static com.github.pareronia.aoc.StringOps.splitOnce; +import static java.util.Objects.requireNonNull; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toMap; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +import com.github.pareronia.aoc.StringOps.StringSplit; +import com.github.pareronia.aoc.StringUtils; +import com.github.pareronia.aoc.solution.Sample; +import com.github.pareronia.aoc.solution.Samples; +import com.github.pareronia.aoc.solution.SolutionBase; + +public class AoC2024_24 extends SolutionBase { + + private AoC2024_24(final boolean debug) { + super(debug); + } + + public static final AoC2024_24 create() { + return new AoC2024_24(false); + } + + public static final AoC2024_24 createDebug() { + return new AoC2024_24(true); + } + + @Override + protected Circuit parseInput(final List inputs) { + return Circuit.fromInput(inputs); + } + + @Override + public Long solvePart1(final Circuit circuit) { + final int maxZ = circuit.getGates().stream() + .filter(gate -> gate.getName().startsWith("z")) + .map(gate -> gate.getName().substring(1)) + .mapToInt(Integer::parseInt) + .max().getAsInt(); + return rangeClosed(maxZ, 0, -1).intStream() + .mapToLong(i -> ((1L << i) + * circuit.getValue("z%02d".formatted(i)))) + .sum(); + } + + @Override + public String solvePart2(final Circuit circuit) { + final Predicate outputsToZExceptFirstOneAndNotXOR + = gate -> (gate.name.startsWith("z") + && gate.op != Gate.Op.XOR + && !gate.name.equals("z45")); + final Predicate isXORNotConnectedToXorYorZ + = gate -> gate.op == Gate.Op.XOR + && Set.of(gate.name, gate.in1, gate.in2).stream() + .noneMatch(n -> n.startsWith("x") + || n.startsWith("y") + || n.startsWith("z")); + final BiPredicate> isANDExceptLastWithAnOutputNotToOR + = (gate, others) -> (gate.op == Gate.Op.AND + && !(gate.in1.equals("x00") || gate.in2.equals("x00")) + && others.stream().anyMatch(other -> other.op != Gate.Op.OR + && (Set.of(other.in1, other.in2).contains(gate.name)))); + final BiPredicate> isXORWithAnOutputToOR + = (gate, others) -> (gate.op == Gate.Op.XOR + && others.stream().anyMatch(other -> other.op == Gate.Op.OR + && (Set.of(other.in1, other.in2).contains(gate.name)))); + final List gates = circuit.getGates().stream() + .filter(gate -> gate.op != Gate.Op.SET) + .toList(); + return gates.stream() + .filter(gate -> outputsToZExceptFirstOneAndNotXOR.test(gate) + || isXORNotConnectedToXorYorZ.test(gate) + || isANDExceptLastWithAnOutputNotToOR.test(gate, gates) + || isXORWithAnOutputToOR.test(gate, gates)) + .map(Gate::getName) + .sorted() + .collect(joining(",")); + } + + @Override + @Samples({ + @Sample(method = "part1", input = TEST1, expected = "4"), + @Sample(method = "part1", input = TEST2, expected = "2024"), + }) + public void samples() { + } + + public static void main(final String[] args) throws Exception { + AoC2024_24.create().run(); + } + + private static final String TEST1 = """ + x00: 1 + x01: 1 + x02: 1 + y00: 0 + y01: 1 + y02: 0 + + x00 AND y00 -> z00 + x01 XOR y01 -> z01 + x02 OR y02 -> z02 + """; + private static final String TEST2 = """ + x00: 1 + x01: 0 + x02: 1 + x03: 1 + x04: 0 + y00: 1 + y01: 1 + y02: 1 + y03: 1 + y04: 1 + + ntg XOR fgs -> mjb + y02 OR x01 -> tnw + kwq OR kpj -> z05 + x00 OR x03 -> fst + tgd XOR rvg -> z01 + vdt OR tnw -> bfw + bfw AND frj -> z10 + ffh OR nrd -> bqk + y00 AND y03 -> djm + y03 OR y00 -> psh + bqk OR frj -> z08 + tnw OR fst -> frj + gnj AND tgd -> z11 + bfw XOR mjb -> z00 + x03 OR x00 -> vdt + gnj AND wpb -> z02 + x04 AND y00 -> kjc + djm OR pbm -> qhw + nrd AND vdt -> hwm + kjc AND fst -> rvg + y04 OR y02 -> fgs + y01 AND x02 -> pbm + ntg OR kjc -> kwq + psh XOR fgs -> tgd + qhw XOR tgd -> z09 + pbm OR djm -> kpj + x03 XOR y03 -> ffh + x00 XOR y04 -> ntg + bfw OR bqk -> z06 + nrd XOR fgs -> wpb + frj XOR qhw -> z04 + bqk OR frj -> z07 + y03 OR x01 -> nrd + hwm AND bqk -> z03 + tgd XOR rvg -> z12 + tnw OR pbm -> gnj + """; + + static final class Gate { + + enum Op { SET, AND, OR, XOR } + + final String name; + final String in1; + final String in2; + final Op op; + final Integer arg; + private Integer result; + + protected Gate( + final String name, + final String in1, + final String in2, + final Op op, + final Integer arg + ) { + this.name = name; + this.in1 = in1; + this.in2 = in2; + this.op = op; + this.arg = arg; + } + + public static Gate fromInput(final String input) { + if (input.contains(": ")) { + final StringSplit splits = splitOnce(input, ": "); + return Gate.set(splits.left(), splits.right()); + } + final StringSplit splits = splitOnce(input, " -> "); + if (splits.left().contains("AND")) { + final StringSplit andSplits = splitOnce(splits.left(), " AND "); + return Gate.and(splits.right(), andSplits.left(), andSplits.right()); + } else if (splits.left().contains("XOR")) { + final StringSplit xorSplits = splitOnce(splits.left(), " XOR "); + return Gate.xor(splits.right(), xorSplits.left(), xorSplits.right()); + } else if (splits.left().contains("OR") && !splits.left().contains("XOR")) { + final StringSplit orSplits = splitOnce(splits.left(), " OR "); + return Gate.or(splits.right(), orSplits.left(), orSplits.right()); + } else { + throw new IllegalArgumentException(); + } + } + + public static Gate xor(final String name, final String in) { + return new Gate(name, in, null, Op.XOR, null); + } + + public static Gate and(final String name, final String in1, final String in2) { + return new Gate(name, in1, in2, Op.AND, null); + } + + public static Gate xor(final String name, final String in1, final String in2) { + return new Gate(name, in1, in2, Op.XOR, null); + } + + public static Gate or(final String name, final String in1, final String in2) { + return new Gate(name, in1, in2, Op.OR, null); + } + + public static Gate set(final String name, final String in) { + return new Gate(name, in, null, Op.SET, null); + } + + public String getName() { + return name; + } + + public Integer getResult() { + return result; + } + + public Integer updateResult(final Integer in1, final Integer in2) { + switch (this.op) { + case SET -> this.result = in1; + case AND -> this.result = in1 & in2; + case XOR -> this.result = in1 ^ in2; + case OR -> this.result = in1 | in2; + } + return this.result; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + switch (this.op) { + case SET -> sb.append(this.in1); + case AND -> sb.append(this.in1).append(" AND ").append(this.in2); + case OR -> sb.append(this.in1).append(" OR ").append(this.in2); + case XOR -> sb.append(this.in1).append(" XOR ").append(this.in2); + } + return sb.toString(); + } + } + + record Circuit(Map gates) { + + public static Circuit fromInput(final List inputs) { + return new Circuit(inputs.stream() + .filter(s -> !s.isBlank()) + .map(Gate::fromInput) + .collect(toMap(Gate::getName, identity()))); + } + + public Collection getGates() { + return this.gates.values(); + } + + public Gate getGate(final String name) { + return this.gates.get(requireNonNull(name)); + } + + public int getValue(final String name) { + assert name != null && !name.isEmpty(): "name is empty"; + if (StringUtils.isNumeric(name)) { + return Integer.parseInt(name); + } + final Gate gate = getGate(name); + assert gate != null : "Gate '" + name + "' not found"; + final Integer result = gate.getResult(); + if (result != null) { + return result; + } + final Integer in1 = getValue(gate.in1); + final Integer in2 = gate.in2 != null ? getValue(gate.in2) : null; + return gate.updateResult(in1, in2); + } + } +} \ No newline at end of file diff --git a/src/main/java/AoCBase.java b/src/main/java/AoCBase.java index 7f329c48..ff4a205a 100644 --- a/src/main/java/AoCBase.java +++ b/src/main/java/AoCBase.java @@ -1,23 +1,17 @@ -import java.time.Duration; import java.util.List; import java.util.concurrent.Callable; -import java.util.function.Supplier; import com.github.pareronia.aoc.StringOps; import com.github.pareronia.aoc.solution.Logger; +import com.github.pareronia.aoc.solution.LoggerEnabled; import com.github.pareronia.aoc.solution.SolutionUtils; -import com.github.pareronia.aocd.Puzzle; -public abstract class AoCBase { +public abstract class AoCBase implements LoggerEnabled { protected final Logger logger; protected final boolean debug; protected boolean trace; - protected static Puzzle puzzle(final Class klass) { - return SolutionUtils.puzzle(klass); - } - protected static List splitLines(final String input) { return StringOps.splitLines(input); } @@ -26,10 +20,6 @@ protected static List> toBlocks(final List inputs) { return StringOps.toBlocks(inputs); } - protected static String printDuration(final Duration duration) { - return SolutionUtils.printDuration(duration); - } - protected static V lap(final String prefix, final Callable callable) throws Exception { return SolutionUtils.lap(prefix, callable); } @@ -48,22 +38,12 @@ public Object solvePart2() { } protected void setTrace(final boolean trace) { + this.trace = true; this.logger.setTrace(trace); } - - protected void log(final Object obj) { - this.logger.log(obj); - } - protected void trace(final Object obj) { - this.logger.trace(obj); - } - - protected void log(final Supplier supplier) { - this.logger.log(supplier); - } - - protected void trace(final Supplier supplier) { - this.logger.trace(supplier); - } + @Override + public Logger getLogger() { + return this.logger; + } } \ No newline at end of file diff --git a/src/main/java/IntCodeDaysRunner.java b/src/main/java/IntCodeDaysRunner.java index 5c42edff..ede8428a 100644 --- a/src/main/java/IntCodeDaysRunner.java +++ b/src/main/java/IntCodeDaysRunner.java @@ -3,9 +3,11 @@ import com.github.pareronia.aocd.MultipleDaysRunner; import com.github.pareronia.aocd.MultipleDaysRunner.Day; import com.github.pareronia.aocd.MultipleDaysRunner.Listener; +import com.github.pareronia.aocd.User; public class IntCodeDaysRunner { + private static final Set ALL_USERS = User.builder().getAllUsers(); private static final Set DAYS = Set.of( Day.at(2019, 2), Day.at(2019, 5), @@ -13,10 +15,12 @@ public class IntCodeDaysRunner { Day.at(2019, 9), Day.at(2019, 11), Day.at(2019, 13), - Day.at(2019, 15) + Day.at(2019, 15), + Day.at(2019, 17), + Day.at(2019, 19) ); public static void main(final String[] args) throws Exception { - new MultipleDaysRunner().run(DAYS, new Listener() {}); + new MultipleDaysRunner().run(DAYS, ALL_USERS, new Listener() {}); } } diff --git a/src/main/java/com/github/pareronia/aoc/CharGrid.java b/src/main/java/com/github/pareronia/aoc/CharGrid.java index 648c207d..48ab6256 100644 --- a/src/main/java/com/github/pareronia/aoc/CharGrid.java +++ b/src/main/java/com/github/pareronia/aoc/CharGrid.java @@ -14,7 +14,7 @@ import java.util.Set; import java.util.stream.Stream; -public class CharGrid implements Grid { +public class CharGrid implements Grid, Cloneable { private final char[][] cells; public CharGrid(final char[][] cells) { @@ -41,7 +41,20 @@ public static CharGrid from(final String string, final int width) { .collect(toList())); } - public CharGrid addRow(final String string ) { + @Override + protected CharGrid clone() throws CloneNotSupportedException { + return new CharGrid(this.cells.clone()); + } + + public CharGrid doClone() { + try { + return this.clone(); + } catch (final CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + public CharGrid addRow(final String string ) { assertTrue(string.length() == getWidth(), () -> "Invalid row length."); final List list = new ArrayList<>(getRowsAsStringList()); list.add(string); @@ -345,9 +358,8 @@ public String toString() { @Override public int hashCode() { final int prime = 31; - int result = 1; - result = prime * result + Arrays.deepHashCode(cells); - return result; + final int result = 1; + return prime * result + Arrays.deepHashCode(cells); } @Override diff --git a/src/main/java/com/github/pareronia/aoc/Counter.java b/src/main/java/com/github/pareronia/aoc/Counter.java index b9f69d1e..0090d417 100644 --- a/src/main/java/com/github/pareronia/aoc/Counter.java +++ b/src/main/java/com/github/pareronia/aoc/Counter.java @@ -3,28 +3,68 @@ import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.groupingBy; +import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; public class Counter { private final Map counts; + public Counter(final Stream stream) { + this.counts = stream.collect(groupingBy(o -> o, counting())); + } + public Counter(final Iterable iterable) { - this.counts = Utils.stream(iterable.iterator()) - .collect(groupingBy(o -> o, counting())); + this(Utils.stream(iterable.iterator())); } public boolean isEmpty() { return this.counts.isEmpty(); } + + public boolean containsValue(final Long value) { + return this.counts.containsValue(value); + } + + public Long get(final T value) { + return this.counts.getOrDefault(value, 0L); + } + + public Collection values() { + return this.counts.values(); + } public List> mostCommon() { return this.counts.entrySet().stream() - .sorted((e1, e2) -> Long.compare(e2.getValue(), e1.getValue())) + .sorted(Comparator.comparing(Map.Entry::getValue).reversed()) .map(e -> new Entry(e.getKey(), e.getValue())) .toList(); } + @Override + public int hashCode() { + return Objects.hash(counts); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + @SuppressWarnings("rawtypes") + final Counter other = (Counter) obj; + return Objects.equals(counts, other.counts); + } + public record Entry(T value, long count) {} } diff --git a/src/main/java/com/github/pareronia/aoc/Grid.java b/src/main/java/com/github/pareronia/aoc/Grid.java index cd649f7b..4d30a7fd 100644 --- a/src/main/java/com/github/pareronia/aoc/Grid.java +++ b/src/main/java/com/github/pareronia/aoc/Grid.java @@ -8,17 +8,14 @@ import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Stream; +import java.util.stream.Stream.Builder; import com.github.pareronia.aoc.IntegerSequence.Range; import com.github.pareronia.aoc.geometry.Direction; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - public interface Grid { Cell ORIGIN = Cell.at(0, 0); @@ -112,6 +109,30 @@ default Stream getCells() { new GridIterator<>(this, ORIGIN, GridIterator.IterDir.FORWARD)); } + default Stream getCellsWithoutBorder() { + final Builder builder = Stream.builder(); + for (int r = 1; r < this.getHeight() - 1; r++) { + for (int c = 1; c < this.getWidth() - 1; c++) { + builder.add(new Cell(r, c)); + } + } + return builder.build(); + } + + default Stream getCells(final Cell cell, final Direction dir) { + return switch (dir) { + case UP -> getCellsN(cell); + case RIGHT_AND_UP -> getCellsNE(cell); + case RIGHT -> getCellsE(cell); + case RIGHT_AND_DOWN -> getCellsSE(cell); + case DOWN -> getCellsS(cell); + case LEFT_AND_DOWN -> getCellsSW(cell); + case LEFT -> getCellsW(cell); + case LEFT_AND_UP -> getCellsNW(cell); + default -> throw new IllegalArgumentException(); + }; + } + default Stream getCellsN(final Cell cell) { return Utils.stream( new GridIterator<>(this, cell, GridIterator.IterDir.UP)); @@ -200,14 +221,19 @@ default String asString() { return this.getRowsAsStrings().collect(joining(System.lineSeparator())); } - @RequiredArgsConstructor(staticName = "at") - @Getter - @EqualsAndHashCode - @ToString public static final class Cell { final int row; final int col; + private Cell(final int row, final int col) { + this.row = row; + this.col = col; + } + + public static Cell at(final int row, final int col) { + return new Cell(row, col); + } + public static Cell fromString(final String string) { final String[] splits = string.split(","); assertTrue(splits.length == 2 && StringUtils.isNumeric(splits[0]) && StringUtils.isNumeric(splits[1]), @@ -219,6 +245,14 @@ public Cell at(final Direction direction) { return Cell.at( this.row - direction.getY(), this.col + direction.getX()); } + + public int getRow() { + return row; + } + + public int getCol() { + return col; + } public Stream allNeighbours() { return Direction.OCTANTS.stream().map(this::at); @@ -240,5 +274,32 @@ public Direction to(final Cell other) { throw new UnsupportedOperationException(); } } + + @Override + public int hashCode() { + return Objects.hash(col, row); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Cell other = (Cell) obj; + return col == other.col && row == other.row; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Cell [row=").append(row).append(", col=").append(col).append("]"); + return builder.toString(); + } } } diff --git a/src/main/java/com/github/pareronia/aoc/GridIterator.java b/src/main/java/com/github/pareronia/aoc/GridIterator.java index cb144956..01791b55 100644 --- a/src/main/java/com/github/pareronia/aoc/GridIterator.java +++ b/src/main/java/com/github/pareronia/aoc/GridIterator.java @@ -5,9 +5,6 @@ import com.github.pareronia.aoc.Grid.Cell; import com.github.pareronia.aoc.geometry.Direction; -import lombok.AllArgsConstructor; - -@AllArgsConstructor final class GridIterator implements Iterator { enum IterDir { @@ -28,6 +25,16 @@ enum IterDir { } } + protected GridIterator( + final Grid grid, + final Cell next, + final IterDir direction + ) { + this.grid = grid; + this.next = next; + this.direction = direction; + } + private final Grid grid; private Cell next; private final GridIterator.IterDir direction; @@ -37,36 +44,36 @@ public boolean hasNext() { if (this.next == null) { return false; } - switch (this.direction) { - case FORWARD: - return true; - default: - final Cell next = this.next.at(this.direction.delegate); - if (this.grid.isInBounds(next)) { - this.next = next; - return true; - } else { - this.next = null; - return false; + return switch (this.direction) { + case FORWARD -> true; + default -> { + final Cell next = this.next.at(this.direction.delegate); + if (this.grid.isInBounds(next)) { + this.next = next; + yield true; + } else { + this.next = null; + yield false; + } } - } + }; } @Override public Cell next() { final Cell prev = this.next; - switch (this.direction) { - case FORWARD: - if (prev.col + 1 < this.grid.getWidth()) { - this.next = Cell.at(prev.row, prev.col + 1); - } else if (prev.row + 1 < this.grid.getHeight()) { - this.next = Cell.at(prev.row + 1, 0); - } else { - this.next = null; + return switch (this.direction) { + case FORWARD -> { + if (prev.col + 1 < this.grid.getWidth()) { + this.next = Cell.at(prev.row, prev.col + 1); + } else if (prev.row + 1 < this.grid.getHeight()) { + this.next = Cell.at(prev.row + 1, 0); + } else { + this.next = null; + } + yield prev; } - return prev; - default: - return this.next; - } + default -> this.next; + }; } } \ No newline at end of file diff --git a/src/main/java/com/github/pareronia/aoc/IntGrid.java b/src/main/java/com/github/pareronia/aoc/IntGrid.java index 55b294ad..2696b0a1 100644 --- a/src/main/java/com/github/pareronia/aoc/IntGrid.java +++ b/src/main/java/com/github/pareronia/aoc/IntGrid.java @@ -5,14 +5,13 @@ import java.util.Arrays; import java.util.List; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -@Getter public final class IntGrid implements Grid { final int[][] values; + public IntGrid(final int[][] values) { + this.values = values; + } + public static IntGrid from(final List strings) { final int[][] values = new int[strings.size()][strings.get(0).length()]; strings.stream() @@ -22,6 +21,10 @@ public static IntGrid from(final List strings) { return new IntGrid(values); } + public int[][] getValues() { + return values; + } + @Override public int getWidth() { assert this.values.length > 0; diff --git a/src/main/java/com/github/pareronia/aoc/IntegerSequence.java b/src/main/java/com/github/pareronia/aoc/IntegerSequence.java index 94b2146d..2420eafb 100644 --- a/src/main/java/com/github/pareronia/aoc/IntegerSequence.java +++ b/src/main/java/com/github/pareronia/aoc/IntegerSequence.java @@ -9,17 +9,11 @@ import java.util.stream.IntStream; import java.util.stream.Stream; -import lombok.ToString; - public class IntegerSequence { - @ToString(onlyExplicitlyIncluded = true) public static class Range implements Iterable { - @ToString.Include private final int from; - @ToString.Include private final int to; - @ToString.Include private final int step; private int minimum; private int maximum; @@ -59,7 +53,7 @@ public static Range range(final int from, final int to, final int step) { } public static Range rangeClosed(final int from, final int to, final int step) { - return new Range(from, from == to ? to : (from < to ? to + 1 : to - 1), step); + return new Range(from, from == to ? to + 1 : (from < to ? to + 1 : to - 1), step); } public static Range between(final int fromInclusive, final int toInclusive) { @@ -108,5 +102,13 @@ public Integer next() { } }; } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Range [from=").append(from).append(", to=") + .append(to).append(", step=").append(step).append("]"); + return builder.toString(); + } } } \ No newline at end of file diff --git a/src/main/java/com/github/pareronia/aoc/IterTools.java b/src/main/java/com/github/pareronia/aoc/IterTools.java index f731cbd6..39be4646 100644 --- a/src/main/java/com/github/pareronia/aoc/IterTools.java +++ b/src/main/java/com/github/pareronia/aoc/IterTools.java @@ -4,44 +4,15 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Set; +import java.util.NoSuchElementException; import java.util.function.Consumer; import java.util.stream.Stream; import org.apache.commons.math3.util.CombinatoricsUtils; public class IterTools { - - @SafeVarargs - public static Set> product(final Iterator... iterators) { - Set> ans = new HashSet<>(Set.of(List.of())); - for (final Iterator range : iterators) { - final Set> set = new HashSet<>(); - for (final T i : (Iterable) () -> range) { - for (final List tmp : ans) { - final List lst = new ArrayList<>(); - lst.add(i); - lst.addAll(tmp); - set.add(lst); - } - } - ans = set; - } - return ans; - } - - @SafeVarargs - @SuppressWarnings("unchecked") - public static Set> product(final Iterable... iterables) { - final Iterator[] iterators = new Iterator[iterables.length]; - for (int i = 0; i < iterables.length; i++) { - iterators[i] = iterables[i].iterator(); - } - return IterTools.product(iterators); - } // TODO potentially huge storage cost -> make iterative version public static Stream> permutations(final Iterable iterable) { @@ -70,14 +41,24 @@ public static Stream permutations(final int[] a) { return builder.build(); } - public static Iterator combinationsIterator(final int n, final int k) { - return CombinatoricsUtils.combinationsIterator(n, k); - } + public static IterToolsIterator combinations( + final int n, final int k + ) { + final Iterator ans + = CombinatoricsUtils.combinationsIterator(n, k); + return new IterToolsIterator<>() { + @Override + public boolean hasNext() { + return ans.hasNext(); + } - public static Iterable combinations(final int n, final int k) { - return () -> combinationsIterator(n, k); + @Override + public int[] next() { + return ans.next(); + } + }; } - + public static Stream> enumerate(final Stream stream) { return enumerateFrom(0, stream); } @@ -103,12 +84,12 @@ public Enumerated next() { }); } - private static Iterable> - doZip( + private static IterToolsIterator> + zip( final Iterator iterator1, final Iterator iterator2 ) { - return () -> new Iterator<>() { + return new IterToolsIterator<>() { @Override public boolean hasNext() { @@ -122,21 +103,21 @@ public ZippedPair next() { }; } - public static Iterable> zip( + public static IterToolsIterator> zip( final Iterable iterable1, final Iterable iterable2 ) { - return doZip(iterable1.iterator(), iterable2.iterator()); + return zip(iterable1.iterator(), iterable2.iterator()); } - public static Iterator cycle(final Iterator iterator) { - return new Iterator<>() { + private static IterToolsIterator cycle(final Iterator iterator) { + return new IterToolsIterator<>() { List saved = new ArrayList<>(); int i = 0; @Override public boolean hasNext() { - return iterator.hasNext(); + return true; } @Override @@ -151,10 +132,79 @@ public T next() { }; } - public static Iterator cycle(final Iterable iterable) { + public static IterToolsIterator cycle(final Iterable iterable) { return cycle(iterable.iterator()); } + public static IterToolsIterator> windows( + final List list + ) { + return new IterToolsIterator<>() { + int i = 0; + + @Override + public boolean hasNext() { + return i < list.size() - 1; + } + + @Override + public WindowPair next() { + final WindowPair next + = new WindowPair<>(list.get(i), list.get(i + 1)); + i++; + return next; + } + }; + } + + public static IterToolsIterator> product( + final Iterable first, + final Iterable second + ) { + return product(first.iterator(), second.iterator()); + } + + public static IterToolsIterator> product( + final Iterator first, + final Iterator second + ) { + final List lstU = Utils.stream(second).toList(); + final Iterator> ans = Utils.stream(first) + .flatMap(a -> lstU.stream() + .map(b -> new ProductPair<>(a, b))) + .iterator(); + return new IterToolsIterator<>() { + @Override + public boolean hasNext() { + return ans.hasNext(); + } + + @Override + public ProductPair next() { + return ans.next(); + } + }; + } + + public static IterToolsIterator chain(final Iterator iterator1, final Iterator iterator2) { + return new IterToolsIterator<>() { + @Override + public boolean hasNext() { + return iterator1.hasNext() || iterator2.hasNext(); + } + + @Override + public T next() { + if (iterator1.hasNext()) { + return iterator1.next(); + } else if (iterator2.hasNext()) { + return iterator2.next(); + } + throw new NoSuchElementException(); + } + }; + } + private static final class Heap { public static void accept(final int[] a, final Consumer consumer) { @@ -193,5 +243,23 @@ private static void swap(final int[] a, final int i, final int j) { public record ZippedPair(T first, T second) {} + public record WindowPair(T first, T second) {} + + public record ProductPair(T first, U second) { + public static ProductPair of(final T first, final U second) { + return new ProductPair<>(first, second); + } + } + public record Enumerated(int index, T value) {} + + public interface IterToolsIterator extends Iterator { + default Stream stream() { + return Utils.stream(this); + } + + default Iterable iterable() { + return () -> this; + } + } } diff --git a/src/main/java/com/github/pareronia/aoc/ListUtils.java b/src/main/java/com/github/pareronia/aoc/ListUtils.java index 221ccf6c..3dbbebf9 100644 --- a/src/main/java/com/github/pareronia/aoc/ListUtils.java +++ b/src/main/java/com/github/pareronia/aoc/ListUtils.java @@ -7,9 +7,67 @@ public class ListUtils { + public static > List sorted(final List list) { + Collections.sort(list); + return list; + } + public static List reversed(final List list) { final List ans = new ArrayList<>(Objects.requireNonNull(list)); Collections.reverse(ans); return ans; } + + public static List indexesOfSubList(final List list, final List subList) { + Objects.requireNonNull(list); + Objects.requireNonNull(subList); + if (list.isEmpty() || subList.isEmpty()) { + return List.of(); + } + final List ans = new ArrayList<>(); + int p0 = 0; + while (p0 + subList.size() <= list.size()) { + final int idx = Collections.indexOfSubList(list.subList(p0, list.size()), subList); + if (idx == -1) { + break; + } else { + ans.add(p0 + idx); + p0 += idx + subList.size(); + } + } + return ans; + } + + public static List subtractAll(final List list, final List subList) { + final List ans = new ArrayList<>(); + int i = 0; + for (final int idx : ListUtils.indexesOfSubList(list, subList)) { + while (i < idx) { + ans.add(list.get(i)); + i++; + } + i += subList.size(); + } + ans.addAll(list.subList(i, list.size())); + return ans; + } + + public static List> transpose(final List> lists) { + AssertUtils.assertTrue( + lists.stream().noneMatch(List::isEmpty), + () -> "Expect lists to be not empty"); + AssertUtils.assertTrue( + lists.stream().map(List::size).distinct().count() == 1, + () -> "Expect lists to be same size"); + final List> ans = new ArrayList<>(); + for (int i = 0; i < lists.get(0).size(); i++) { + ans.add(new ArrayList<>()); + } + for (int i = 0; i < lists.get(0).size(); i++) { + for (int j = 0; j < lists.size(); j++) { + ans.get(i).add(lists.get(j).get(i)); + } + } + return ans; + } } diff --git a/src/main/java/com/github/pareronia/aoc/RangeInclusive.java b/src/main/java/com/github/pareronia/aoc/RangeInclusive.java index 4c99a3d2..584d8982 100644 --- a/src/main/java/com/github/pareronia/aoc/RangeInclusive.java +++ b/src/main/java/com/github/pareronia/aoc/RangeInclusive.java @@ -1,6 +1,11 @@ package com.github.pareronia.aoc; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.Comparator; +import java.util.List; import java.util.Objects; public final class RangeInclusive { @@ -40,6 +45,36 @@ public static RangeInclusive between(final T fromInclusive, final T toInc return new RangeInclusive<>(fromInclusive, toInclusive); } + public static List> mergeRanges( + final Collection> ranges + ) { + final var merged = new ArrayDeque>(); + final var sorted = new ArrayList<>(ranges); + Collections.sort(sorted, (r1, r2) -> { + final int first = Integer.compare(r1.getMinimum(), r2.getMinimum()); + if (first == 0) { + return Integer.compare(r1.getMaximum(), r2.getMaximum()); + } + return first; + }); + for (final var range : sorted) { + if (merged.isEmpty()) { + merged.addLast(range); + continue; + } + final var last = merged.peekLast(); + if (range.isOverlappedBy(last)) { + merged.removeLast(); + merged.add(RangeInclusive.between( + last.getMinimum(), + Math.max(last.getMaximum(), range.getMaximum()))); + } else { + merged.addLast(range); + } + } + return merged.stream().toList(); + } + public T getMinimum() { return minimum; } diff --git a/src/main/java/com/github/pareronia/aoc/StringOps.java b/src/main/java/com/github/pareronia/aoc/StringOps.java index af4decc1..11bcfef4 100644 --- a/src/main/java/com/github/pareronia/aoc/StringOps.java +++ b/src/main/java/com/github/pareronia/aoc/StringOps.java @@ -10,6 +10,9 @@ import java.util.List; import java.util.Objects; +import com.github.pareronia.aoc.IterTools.IterToolsIterator; +import com.github.pareronia.aoc.IterTools.ZippedPair; + public class StringOps { public static List splitLines(final String input) { @@ -43,6 +46,14 @@ public static List> toBlocks(final List inputs) { } return blocks; } + + public static IterToolsIterator> zip( + final String s1, final String s2 + ) { + return IterTools.zip( + () -> Utils.asCharacterStream(s1).iterator(), + () -> Utils.asCharacterStream(s2).iterator()); + } public static Integer[] getDigits(final String s, final int expected) { final Integer[] digits = Utils.asCharacterStream(s) diff --git a/src/main/java/com/github/pareronia/aoc/Utils.java b/src/main/java/com/github/pareronia/aoc/Utils.java index 0734e40e..efa25755 100644 --- a/src/main/java/com/github/pareronia/aoc/Utils.java +++ b/src/main/java/com/github/pareronia/aoc/Utils.java @@ -1,6 +1,7 @@ package com.github.pareronia.aoc; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -34,7 +35,11 @@ public static Stream stream(final Iterator iterator) { } public static T last(final List list) { - return Objects.requireNonNull(list).get(list.size() - 1); + return Utils.last(list, 1); + } + + public static T last(final List list, final int index) { + return Objects.requireNonNull(list).get(list.size() - index); } public static T last(final T[] list) { @@ -51,6 +56,13 @@ public static List concat(final List list1, final T item) { return ans; } + @SafeVarargs + public static List concatAll(final List... lists) { + final List ans = new ArrayList<>(lists[0]); + Arrays.stream(lists).skip(1).forEach(ans::addAll); + return ans; + } + private static final Pattern REGEX_N = Pattern.compile("[0-9]+"); public static final int[] naturalNumbers(final String string) { return REGEX_N.matcher(string).results() diff --git a/src/main/java/com/github/pareronia/aoc/assembunny/Assembunny.java b/src/main/java/com/github/pareronia/aoc/assembunny/Assembunny.java index a0f79323..c7744a54 100644 --- a/src/main/java/com/github/pareronia/aoc/assembunny/Assembunny.java +++ b/src/main/java/com/github/pareronia/aoc/assembunny/Assembunny.java @@ -11,8 +11,6 @@ import com.github.pareronia.aoc.StringUtils; import com.github.pareronia.aoc.vm.Instruction; -import lombok.Value; - /** * 'Assembunny' util class. * @@ -76,9 +74,21 @@ private static boolean isNumeric(final String s) { return StringUtils.isNumeric(s.replace("-", "")); } - @Value public static final class AssembunnyInstruction { private final String operator; private final List operands; + + public AssembunnyInstruction(final String operator, final List operands) { + this.operator = operator; + this.operands = operands; + } + + public String getOperator() { + return operator; + } + + public List getOperands() { + return operands; + } } } diff --git a/src/main/java/com/github/pareronia/aoc/game_of_life/GameOfLife.java b/src/main/java/com/github/pareronia/aoc/game_of_life/GameOfLife.java index 2f417d0d..c105aabd 100644 --- a/src/main/java/com/github/pareronia/aoc/game_of_life/GameOfLife.java +++ b/src/main/java/com/github/pareronia/aoc/game_of_life/GameOfLife.java @@ -2,26 +2,14 @@ import static java.util.stream.Collectors.toSet; -import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import lombok.Getter; -import lombok.With; +public record GameOfLife(GameOfLife.Type type, GameOfLife.Rules rules, Set alive) { -public class GameOfLife { - - private final Type type; - private final Rules rules; - @Getter - @With - private final Set alive; - - public GameOfLife(final Type type, final Rules rules, final Set alive) { - this.type = type; - this.rules = rules; - this.alive = Collections.unmodifiableSet(alive); + public GameOfLife withAlive(final Set alive) { + return new GameOfLife<>(this.type, this.rules, alive); } public GameOfLife nextGeneration() { diff --git a/src/main/java/com/github/pareronia/aoc/game_of_life/InfiniteGrid.java b/src/main/java/com/github/pareronia/aoc/game_of_life/InfiniteGrid.java index 1ff6cfb8..8337cbc7 100644 --- a/src/main/java/com/github/pareronia/aoc/game_of_life/InfiniteGrid.java +++ b/src/main/java/com/github/pareronia/aoc/game_of_life/InfiniteGrid.java @@ -1,14 +1,17 @@ package com.github.pareronia.aoc.game_of_life; +import static java.util.stream.Collectors.counting; +import static java.util.stream.Collectors.groupingBy; + import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import com.github.pareronia.aoc.IntegerSequence.Range; -import com.github.pareronia.aoc.IterTools; import com.github.pareronia.aoc.game_of_life.GameOfLife.Type; public final class InfiniteGrid implements Type> { @@ -17,13 +20,9 @@ public final class InfiniteGrid implements Type> { @Override public Map, Long> getNeighbourCounts(final Set> alive) { - final Map, Long> neighbourCounts = new HashMap<>(); - for (final List cell : alive) { - for (final List n : neighbours(cell)) { - neighbourCounts.merge(n, 1L, Long::sum); - } - } - return neighbourCounts; + return alive.stream() + .flatMap(cell -> neighbours(cell).stream()) + .collect(groupingBy(cell -> cell, counting())); } private Set> neighbours(final List cell) { @@ -52,6 +51,23 @@ private Set> product(final List ranges) { final Iterator[] iterators = ranges.stream() .map(Range::iterator) .toArray(Iterator[]::new); - return IterTools.product(iterators); + return product(iterators); + } + + public Set> product(final Iterator... iterators) { + Set> ans = new HashSet<>(Set.of(List.of())); + for (final Iterator range : iterators) { + final Set> set = new HashSet<>(); + for (final T i : (Iterable) () -> range) { + for (final List tmp : ans) { + final List lst = new ArrayList<>(); + lst.addAll(tmp); + lst.add(i); + set.add(lst); + } + } + ans = set; + } + return ans; } } \ No newline at end of file diff --git a/src/main/java/com/github/pareronia/aoc/geometry/Direction.java b/src/main/java/com/github/pareronia/aoc/geometry/Direction.java index dabe957d..ec1640e3 100644 --- a/src/main/java/com/github/pareronia/aoc/geometry/Direction.java +++ b/src/main/java/com/github/pareronia/aoc/geometry/Direction.java @@ -9,8 +9,6 @@ import com.github.pareronia.aoc.AssertUtils; -import lombok.Getter; - public enum Direction { NONE(Vector.of(0, 0), Optional.empty(), Optional.empty(), Optional.empty()), @@ -44,7 +42,6 @@ public enum Direction { public static final Set CAPITAL_ARROWS = CAPITAL.stream() .map(d -> d.arrow.get()).collect(toSet()); - @Getter private final Vector vector; private final Optional letter; private final Optional geo; @@ -81,6 +78,10 @@ public static final Direction fromString(final String string) { } } + public Vector getVector() { + return vector; + } + public Integer getX() { return this.vector.getX(); } @@ -89,19 +90,22 @@ public Integer getY() { return this.vector.getY(); } + public boolean isHorizontal() { + return this == Direction.LEFT || this == Direction.RIGHT; + } + + public boolean isVertical() { + return this == Direction.UP || this == Direction.DOWN; + } + public Direction turn(final Turn turn) { AssertUtils.assertNotNull(turn, () -> "Expected turn be non-null"); - switch (this) { - case UP: - return turn == Turn.AROUND ? DOWN : turn == Turn.LEFT ? LEFT : RIGHT; - case RIGHT: - return turn == Turn.AROUND ? LEFT : turn == Turn.LEFT ? UP : DOWN; - case DOWN: - return turn == Turn.AROUND ? UP : turn == Turn.LEFT ? RIGHT : LEFT; - case LEFT: - return turn == Turn.AROUND ? RIGHT : turn == Turn.LEFT ? DOWN : UP; - default: - throw new UnsupportedOperationException(); - } + return switch (this) { + case UP -> turn == Turn.AROUND ? DOWN : turn == Turn.LEFT ? LEFT : RIGHT; + case RIGHT -> turn == Turn.AROUND ? LEFT : turn == Turn.LEFT ? UP : DOWN; + case DOWN -> turn == Turn.AROUND ? UP : turn == Turn.LEFT ? RIGHT : LEFT; + case LEFT -> turn == Turn.AROUND ? RIGHT : turn == Turn.LEFT ? DOWN : UP; + default -> throw new UnsupportedOperationException(); + }; } } diff --git a/src/main/java/com/github/pareronia/aoc/geometry/Point.java b/src/main/java/com/github/pareronia/aoc/geometry/Point.java index 69cad232..7c6d3b07 100644 --- a/src/main/java/com/github/pareronia/aoc/geometry/Point.java +++ b/src/main/java/com/github/pareronia/aoc/geometry/Point.java @@ -1,12 +1,56 @@ package com.github.pareronia.aoc.geometry; -import lombok.Value; -import lombok.With; -import lombok.experimental.NonFinal; +import java.util.Objects; -@Value -@NonFinal public class Point { - @With private final Integer x; - @With private final Integer y; + private final Integer x; + private final Integer y; + + protected Point(final Integer x, final Integer y) { + this.x = x; + this.y = y; + } + + public Integer getX() { + return x; + } + + public Integer getY() { + return y; + } + + public Point withX(final Integer x) { + return new Point(x, this.y); + } + + public Point withY(final Integer y) { + return new Point(this.x, y); + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Point other = (Point) obj; + return Objects.equals(x, other.x) && Objects.equals(y, other.y); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Point [x=").append(x).append(", y=").append(y).append("]"); + return builder.toString(); + } } diff --git a/src/main/java/com/github/pareronia/aoc/geometry/Position.java b/src/main/java/com/github/pareronia/aoc/geometry/Position.java index 2f0ceb7a..043c0f81 100644 --- a/src/main/java/com/github/pareronia/aoc/geometry/Position.java +++ b/src/main/java/com/github/pareronia/aoc/geometry/Position.java @@ -2,11 +2,6 @@ import java.util.stream.Stream; -import lombok.EqualsAndHashCode; -import lombok.Value; - -@Value -@EqualsAndHashCode(callSuper = true) public class Position extends Point { public static final Position ORIGIN = Position.of(0, 0); diff --git a/src/main/java/com/github/pareronia/aoc/geometry/Turn.java b/src/main/java/com/github/pareronia/aoc/geometry/Turn.java index 86d67f0b..5a0b10ed 100644 --- a/src/main/java/com/github/pareronia/aoc/geometry/Turn.java +++ b/src/main/java/com/github/pareronia/aoc/geometry/Turn.java @@ -7,17 +7,12 @@ import com.github.pareronia.aoc.AssertUtils; -import lombok.AccessLevel; -import lombok.Getter; - -@Getter public enum Turn { LEFT(270, Optional.of('L')), RIGHT(90, Optional.of('R')), AROUND(180, Optional.empty()); - @Getter(AccessLevel.PACKAGE) private final int degrees; private final Optional letter; @@ -62,4 +57,12 @@ public static Turn fromDirections(final Direction dir1, final Direction dir2) { System.err.println(String.format("%s -> %s", dir1, dir2)); throw unreachable(); } + + protected int getDegrees() { + return degrees; + } + + public Optional getLetter() { + return letter; + } } diff --git a/src/main/java/com/github/pareronia/aoc/geometry/Vector.java b/src/main/java/com/github/pareronia/aoc/geometry/Vector.java index 9fbada08..f9993f6f 100644 --- a/src/main/java/com/github/pareronia/aoc/geometry/Vector.java +++ b/src/main/java/com/github/pareronia/aoc/geometry/Vector.java @@ -2,13 +2,6 @@ import com.github.pareronia.aoc.AssertUtils; -import lombok.EqualsAndHashCode; -import lombok.Value; -import lombok.experimental.NonFinal; - -@Value -@EqualsAndHashCode(callSuper = true) -@NonFinal public class Vector extends Point { protected Vector(final Integer x, final Integer y) { diff --git a/src/main/java/com/github/pareronia/aoc/geometry3d/Cuboid.java b/src/main/java/com/github/pareronia/aoc/geometry3d/Cuboid.java index 18ddc941..dff9324c 100644 --- a/src/main/java/com/github/pareronia/aoc/geometry3d/Cuboid.java +++ b/src/main/java/com/github/pareronia/aoc/geometry3d/Cuboid.java @@ -7,22 +7,7 @@ import com.github.pareronia.aoc.RangeInclusive; import com.github.pareronia.aoc.geometry.Position; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -@RequiredArgsConstructor -@EqualsAndHashCode -@ToString -@Getter -public class Cuboid { - final int x1; - final int x2; - final int y1; - final int y2; - final int z1; - final int z2; +public record Cuboid(int x1, int x2, int y1, int y2, int z1, int z2) { public static Cuboid of( final int x1, final int x2, @@ -58,12 +43,12 @@ public static boolean overlap(final Cuboid cuboid1, final Cuboid cuboid2) { || !overlapZ(cuboid1, cuboid2)); } - private static boolean overlapX(final Cuboid cuboid1, final Cuboid cuboid2) { + public static boolean overlapX(final Cuboid cuboid1, final Cuboid cuboid2) { return RangeInclusive.between(cuboid1.x1, cuboid1.x2) .isOverlappedBy(RangeInclusive.between(cuboid2.x1, cuboid2.x2)); } - private static boolean overlapY(final Cuboid cuboid1, final Cuboid cuboid2) { + public static boolean overlapY(final Cuboid cuboid1, final Cuboid cuboid2) { return RangeInclusive.between(cuboid1.y1, cuboid1.y2) .isOverlappedBy(RangeInclusive.between(cuboid2.y1, cuboid2.y2)); } diff --git a/src/main/java/com/github/pareronia/aoc/geometry3d/Direction3D.java b/src/main/java/com/github/pareronia/aoc/geometry3d/Direction3D.java index 6716d56e..92bd5ecc 100644 --- a/src/main/java/com/github/pareronia/aoc/geometry3d/Direction3D.java +++ b/src/main/java/com/github/pareronia/aoc/geometry3d/Direction3D.java @@ -2,9 +2,6 @@ import java.util.EnumSet; -import lombok.Getter; - -@Getter public enum Direction3D { UP(Vector3D.of(0, 1, 0)), @@ -28,4 +25,8 @@ public enum Direction3D { Direction3D(final Vector3D value) { this.value = value; } + + public Vector3D getValue() { + return value; + } } diff --git a/src/main/java/com/github/pareronia/aoc/geometry3d/Point3D.java b/src/main/java/com/github/pareronia/aoc/geometry3d/Point3D.java index fbf3f265..f697071a 100644 --- a/src/main/java/com/github/pareronia/aoc/geometry3d/Point3D.java +++ b/src/main/java/com/github/pareronia/aoc/geometry3d/Point3D.java @@ -1,17 +1,66 @@ package com.github.pareronia.aoc.geometry3d; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import lombok.With; - -@RequiredArgsConstructor -@Getter -@ToString -@EqualsAndHashCode +import java.util.Objects; + public class Point3D { - @With private final int x; - @With private final int y; - @With private final int z; + private final int x; + private final int y; + private final int z; + + protected Point3D(final int x, final int y, final int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + public Point3D withX(final int x) { + return new Point3D(x, this.y, this.z); + } + + public Point3D withY(final int y) { + return new Point3D(this.x, y, this.z); + } + + public Point3D withZ(final int z) { + return new Point3D(this.x, this.y, z); + } + + @Override + public int hashCode() { + return Objects.hash(x, y, z); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Point3D other = (Point3D) obj; + return x == other.x && y == other.y && z == other.z; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Point3D [x=").append(x).append(", y=").append(y).append(", z=").append(z).append("]"); + return builder.toString(); + } } diff --git a/src/main/java/com/github/pareronia/aoc/geometry3d/Position3D.java b/src/main/java/com/github/pareronia/aoc/geometry3d/Position3D.java index e04d1af1..c41d597a 100644 --- a/src/main/java/com/github/pareronia/aoc/geometry3d/Position3D.java +++ b/src/main/java/com/github/pareronia/aoc/geometry3d/Position3D.java @@ -2,9 +2,6 @@ import java.util.stream.Stream; -import lombok.EqualsAndHashCode; - -@EqualsAndHashCode(callSuper = true) public class Position3D extends Point3D { public Position3D(final int x, final int y, final int z) { diff --git a/src/main/java/com/github/pareronia/aoc/geometry3d/Vector3D.java b/src/main/java/com/github/pareronia/aoc/geometry3d/Vector3D.java index eb275443..35b2e11c 100644 --- a/src/main/java/com/github/pareronia/aoc/geometry3d/Vector3D.java +++ b/src/main/java/com/github/pareronia/aoc/geometry3d/Vector3D.java @@ -1,8 +1,5 @@ package com.github.pareronia.aoc.geometry3d; -import lombok.EqualsAndHashCode; - -@EqualsAndHashCode(callSuper = true) public class Vector3D extends Point3D { public Vector3D(final int x, final int y, final int z) { diff --git a/src/main/java/com/github/pareronia/aoc/graph/AStar.java b/src/main/java/com/github/pareronia/aoc/graph/AStar.java deleted file mode 100644 index 9cf5cc3a..00000000 --- a/src/main/java/com/github/pareronia/aoc/graph/AStar.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.github.pareronia.aoc.graph; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.PriorityQueue; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -public class AStar { - - public static Result execute( - final T start, - final Predicate end, - final Function> adjacent, - final Function cost - ) { - final PriorityQueue> q = new PriorityQueue<>(); - q.add(new State<>(start, 0)); - final Map best = new HashMap<>(); - best.put(start, 0L); - final Map parent = new HashMap<>(); - while (!q.isEmpty()) { - final State state = q.poll(); - if (end.test(state.node)) { - break; - } - final long cTotal = best.getOrDefault(state.node, Long.MAX_VALUE); - adjacent.apply(state.node) - .forEach(n -> { - final long newRisk = cTotal + cost.apply(n); - if (newRisk < best.getOrDefault(n, Long.MAX_VALUE)) { - best.put(n, newRisk); - parent.put(n, state.node); - q.add(new State<>(n, newRisk)); - } - }); - } - return new Result<>(start, best, parent); - } - - @RequiredArgsConstructor - @ToString - private static final class State implements Comparable> { - private final T node; - private final long cost; - - @Override - public int compareTo(final State other) { - return Long.compare(this.cost, other.cost); - } - } - - @RequiredArgsConstructor(access = AccessLevel.PRIVATE) - @Getter - public static class Result { - private final T source; - private final Map distances; - private final Map paths; - - public long getDistance(final T v) { - return distances.get(v); - } - - public List getPath(final T v) { - final List p = new ArrayList<>(); - T parent = v; - if (v != this.source) { - while (parent != this.source) { - p.add(0, parent); - parent = this.paths.get(parent); - } - p.add(0, this.source); - } else { - p.add(this.source); - } - return p; - } - } -} diff --git a/src/main/java/com/github/pareronia/aoc/graph/BFS.java b/src/main/java/com/github/pareronia/aoc/graph/BFS.java index 64b7ed50..e6d279c2 100644 --- a/src/main/java/com/github/pareronia/aoc/graph/BFS.java +++ b/src/main/java/com/github/pareronia/aoc/graph/BFS.java @@ -7,9 +7,6 @@ import java.util.function.Predicate; import java.util.stream.Stream; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor public final class BFS { public static int execute( @@ -52,9 +49,13 @@ public static Set floodFill( return seen; } - @RequiredArgsConstructor private static final class State { private final T node; private final int distance; + + protected State(final T node, final int distance) { + this.node = node; + this.distance = distance; + } } } \ No newline at end of file diff --git a/src/main/java/com/github/pareronia/aoc/graph/Dijkstra.java b/src/main/java/com/github/pareronia/aoc/graph/Dijkstra.java new file mode 100644 index 00000000..c8dc03c7 --- /dev/null +++ b/src/main/java/com/github/pareronia/aoc/graph/Dijkstra.java @@ -0,0 +1,173 @@ +package com.github.pareronia.aoc.graph; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import com.github.pareronia.aoc.Utils; + +public class Dijkstra { + + public static Result execute( + final T start, + final Predicate end, + final Function> adjacent, + final BiFunction cost + ) { + final PriorityQueue> q = new PriorityQueue<>(); + q.add(new State<>(start, 0)); + final Map best = new HashMap<>(); + best.put(start, 0L); + final Map parent = new HashMap<>(); + while (!q.isEmpty()) { + final State state = q.poll(); + if (end.test(state.node)) { + break; + } + final long cTotal = best.getOrDefault(state.node, Long.MAX_VALUE); + adjacent.apply(state.node) + .forEach(n -> { + final long newRisk = cTotal + cost.apply(state.node, n); + if (newRisk < best.getOrDefault(n, Long.MAX_VALUE)) { + best.put(n, newRisk); + parent.put(n, state.node); + q.add(new State<>(n, newRisk)); + } + }); + } + return new Result<>(start, best, parent); + } + + public static AllResults all( + final T start, + final Predicate end, + final Function> adjacent, + final BiFunction cost + ) { + final PriorityQueue> q = new PriorityQueue<>(); + q.add(new State<>(start, 0)); + final Map distances = new HashMap<>(); + distances.put(start, 0L); + final Map> predecessors = new HashMap<>(); + while (!q.isEmpty()) { + final State state = q.poll(); + if (end.test(state.node)) { + break; + } + final long total = distances.getOrDefault(state.node, Long.MAX_VALUE); + adjacent.apply(state.node) + .forEach(n -> { + final long newDistance = total + cost.apply(state.node, n); + final Long dist_n = distances.getOrDefault(n, Long.MAX_VALUE); + if (newDistance < dist_n) { + distances.put(n, newDistance); + predecessors.put(n, new ArrayList<>(List.of(state.node))); + q.add(new State<>(n, newDistance)); + } else if (newDistance == dist_n) { + predecessors.computeIfAbsent(n, k -> new ArrayList<>()) + .add(state.node); + } + }); + } + return new AllResults<>(start, distances, predecessors); + } + + public static long distance( + final T start, + final Predicate end, + final Function> adjacent, + final BiFunction cost + ) { + final PriorityQueue> q = new PriorityQueue<>(); + q.add(new State<>(start, 0)); + final Map best = new HashMap<>(); + best.put(start, 0L); + while (!q.isEmpty()) { + final State state = q.poll(); + if (end.test(state.node)) { + return state.cost; + } + final long cTotal = best.getOrDefault(state.node, Long.MAX_VALUE); + adjacent.apply(state.node) + .forEach(n -> { + final long newRisk = cTotal + cost.apply(state.node, n); + if (newRisk < best.getOrDefault(n, Long.MAX_VALUE)) { + best.put(n, newRisk); + q.add(new State<>(n, newRisk)); + } + }); + } + throw new IllegalStateException("Unsolvable"); + } + + private static final class State implements Comparable> { + private final T node; + private final long cost; + + protected State(final T node, final long cost) { + this.node = node; + this.cost = cost; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("State [node=").append(node).append(", cost=").append(cost).append("]"); + return builder.toString(); + } + + @Override + public int compareTo(final State other) { + return Long.compare(this.cost, other.cost); + } + } + + public record Result(T source, Map distances, Map paths) { + + public Map getDistances() { + return distances; + } + + public long getDistance(final T v) { + return distances.get(v); + } + + public List getPath(final T v) { + final List p = new ArrayList<>(); + T parent = v; + if (v != this.source) { + while (parent != this.source) { + p.add(0, parent); + parent = this.paths.get(parent); + } + p.add(0, this.source); + } else { + p.add(this.source); + } + return p; + } + } + + public record AllResults( + T source, Map distances, Map> predecessors) { + + public List> getPaths(final T t) { + if (t.equals(source)) { + return List.of(List.of(source)); + } + final List> paths = new ArrayList<>(); + for (final T predecessor : predecessors.getOrDefault(t, List.of())) { + for (final List path : getPaths(predecessor)) { + paths.add(Utils.concat(path, t)); + } + } + return paths; + } + } +} diff --git a/src/main/java/com/github/pareronia/aoc/intcode/IntCode.java b/src/main/java/com/github/pareronia/aoc/intcode/IntCode.java index 436aa2b6..f53bf4cb 100644 --- a/src/main/java/com/github/pareronia/aoc/intcode/IntCode.java +++ b/src/main/java/com/github/pareronia/aoc/intcode/IntCode.java @@ -11,8 +11,10 @@ import java.util.stream.Stream; import com.github.pareronia.aoc.AssertUtils; +import com.github.pareronia.aoc.solution.Logger; +import com.github.pareronia.aoc.solution.LoggerEnabled; -public class IntCode { +public class IntCode implements LoggerEnabled { private static final int ADD = 1; private static final int MUL = 2; @@ -29,7 +31,7 @@ public class IntCode { private static final int IMMEDIATE = 1; private static final int RELATIVE = 2; - private final boolean debug; + private final Logger logger; private int ip; private int base; @@ -39,8 +41,8 @@ public class IntCode { private boolean halted; public IntCode(final List instructions, final boolean debug) { - this.debug = debug; this.program = new ArrayList<>(instructions); + this.logger = new Logger(debug); } public void run(final List program) { @@ -86,7 +88,7 @@ private void doRun( while (true) { final Long op = program.get(ip); final int opcode = (int) (op % 100); - final int[] modes = new int[] { + final int[] modes = { -1, (int) ((op / 100) % 10), (int) ((op / 1000) % 10), @@ -94,55 +96,51 @@ private void doRun( }; final int[] addr = getAddr(modes); switch (opcode) { - case ADD: + case ADD -> { set(addr[3], get(addr[1]) + get(addr[2])); ip += 4; - break; - case MUL: + } + case MUL -> { set(addr[3], get(addr[1]) * get(addr[2])); ip += 4; - break; - case INPUT: + } + case INPUT -> { if (this.runTillInputRequired && input.isEmpty()) { this.runTillInputRequired = false; return; } set(addr[1], input.pop()); ip += 2; - break; - case OUTPUT: + } + case OUTPUT -> { output.add(get(addr[1])); ip += 2; if (this.runTillHasOutput) { this.runTillHasOutput = false; return; } - break; - case JIT: - ip = get(addr[1]) != 0 ? getInt(addr[2]) : ip + 3; - break; - case JIF: - ip = get(addr[1]) == 0 ? getInt(addr[2]) : ip + 3; - break; - case LT: + } + case JIT -> ip = get(addr[1]) != 0 ? getInt(addr[2]) : ip + 3; + case JIF -> ip = get(addr[1]) == 0 ? getInt(addr[2]) : ip + 3; + case LT -> { set(addr[3], get(addr[1]) < get(addr[2]) ? 1 : 0); ip += 4; - break; - case EQ: + } + case EQ -> { set(addr[3], get(addr[1]) == get(addr[2]) ? 1 : 0); ip += 4; - break; - case BASE: + } + case BASE -> { base += get(addr[1]); ip += 2; - break; - case EXIT: + } + case EXIT -> { log(String.format("%d: EXIT", ip)); this.halted = true; return; - default: - throw new IllegalStateException( - String.format("Invalid opcode: '%d'", opcode)); + } + default -> throw new IllegalStateException( + "Invalid opcode: '%d'".formatted(opcode)); } } } @@ -173,16 +171,15 @@ private int[] getAddr(final int[] modes) { final int[] addr = new int[4]; try { for (int i = 1; i <= 3; i++) { - if (modes[i] == POSITION) { - addr[i] = Math.toIntExact(this.program.get(this.ip + i)); - } else if (modes[i] == IMMEDIATE) { - addr[i] = this.ip + i; - } else if (modes[i] == RELATIVE) { - addr[i] = Math.toIntExact(this.program.get(this.ip + i) + this.base); - } else { - throw new IllegalArgumentException( - String.format("Invalid mode '%d'", modes[i])); - } + addr[i] = switch(modes[i]) { + case POSITION -> Math.toIntExact( + this.program.get(this.ip + i)); + case IMMEDIATE -> this.ip + i; + case RELATIVE -> Math.toIntExact( + this.program.get(this.ip + i) + this.base); + default -> throw new IllegalArgumentException( + "Invalid mode '%d'".formatted(modes[i])); + }; } } catch (final IndexOutOfBoundsException | ArithmeticException e) { } @@ -197,14 +194,12 @@ public List getProgram() { return Collections.unmodifiableList(this.program); } - private void log(final Object obj) { - if (!debug) { - return; - } - System.out.println(obj); - } - - public static List parse(final String input) { + @Override + public Logger getLogger() { + return this.logger; + } + + public static List parse(final String input) { return Stream.of(input.split(",")) .map(Long::valueOf) .collect(toUnmodifiableList()); diff --git a/src/main/java/com/github/pareronia/aoc/knothash/KnotHash.java b/src/main/java/com/github/pareronia/aoc/knothash/KnotHash.java index eb2820a5..75219f1d 100644 --- a/src/main/java/com/github/pareronia/aoc/knothash/KnotHash.java +++ b/src/main/java/com/github/pareronia/aoc/knothash/KnotHash.java @@ -12,9 +12,6 @@ import com.github.pareronia.aoc.StringUtils; import com.github.pareronia.aoc.Utils; -import lombok.Builder; -import lombok.Getter; - public class KnotHash { public static final List SEED = @@ -38,8 +35,7 @@ private static List paddedLengths(final String input) { private static int[] calculate(final List lengths) { final List elements = new ArrayList<>(SEED); - State state = State.builder() - .elements(elements).lengths(lengths).cur(0).skip(0).build(); + State state = new State(elements, lengths, 0, 0); for (int i = 0; i < 64; i++) { state = KnotHash.round(state); } @@ -53,17 +49,16 @@ private static int[] calculate(final List lengths) { } public static State round(final State state) { - final List elements = state.getElements(); - final List lengths = state.getLengths(); - int cur = state.getCur(); - int skip = state.getSkip(); + final List elements = state.elements(); + final List lengths = state.lengths(); + int cur = state.cur(); + int skip = state.skip(); for (final int len : lengths) { reverse(elements, cur, len); cur = (cur + len + skip) % elements.size(); skip++; } - return State.builder() - .elements(elements).lengths(lengths).cur(cur).skip(skip).build(); + return new State(elements, lengths, cur, skip); } private static void reverse( @@ -96,12 +91,5 @@ private static String toBinaryString(final int[] dense) { .collect(joining()); } - @Builder - @Getter - public static final class State { - private final List elements; - private final List lengths; - private final int cur; - private final int skip; - } + public record State(List elements, List lengths, int cur, int skip) {} } diff --git a/src/main/java/com/github/pareronia/aoc/navigation/Heading.java b/src/main/java/com/github/pareronia/aoc/navigation/Heading.java index a379a7c7..f288661a 100644 --- a/src/main/java/com/github/pareronia/aoc/navigation/Heading.java +++ b/src/main/java/com/github/pareronia/aoc/navigation/Heading.java @@ -4,11 +4,7 @@ import com.github.pareronia.aoc.geometry.Turn; import com.github.pareronia.aoc.geometry.Vector; -import lombok.Getter; -import lombok.ToString; - -@ToString -public class Heading { +public record Heading(Vector vector) { public static final Heading NORTH = Heading.fromDirection(Direction.UP); public static final Heading NORTHEAST = Heading.fromDirection(Direction.RIGHT_AND_UP); public static final Heading EAST = Heading.fromDirection(Direction.RIGHT); @@ -18,13 +14,6 @@ public class Heading { public static final Heading WEST = Heading.fromDirection(Direction.LEFT); public static final Heading NORTHWEST = Heading.fromDirection(Direction.LEFT_AND_UP); - @Getter - private final Vector vector; - - private Heading(final Vector direction) { - this.vector = direction; - } - public static final Heading of(final int x, final int y) { return new Heading(Vector.of(x, y)); } @@ -42,6 +31,6 @@ public Heading turn(final Turn turn) { } public Heading add(final Direction direction, final int amplitude) { - return new Heading(this.getVector().add(direction.getVector(), amplitude)); + return new Heading(this.vector.add(direction.getVector(), amplitude)); } } diff --git a/src/main/java/com/github/pareronia/aoc/navigation/Navigation.java b/src/main/java/com/github/pareronia/aoc/navigation/Navigation.java index 2a08e279..292d9bbc 100644 --- a/src/main/java/com/github/pareronia/aoc/navigation/Navigation.java +++ b/src/main/java/com/github/pareronia/aoc/navigation/Navigation.java @@ -7,39 +7,37 @@ import com.github.pareronia.aoc.geometry.Position; import com.github.pareronia.aoc.geometry.Vector; -import lombok.Getter; -import lombok.ToString; - -@ToString(onlyExplicitlyIncluded = true) public class Navigation { - @Getter - @ToString.Include protected Position position; private final List visitedPositions = new ArrayList<>(); private final Predicate inBoundsPredicate; - public Navigation(Position position) { + public Navigation(final Position position) { this.position = position; rememberVisitedPosition(position); inBoundsPredicate = pos -> true; } - public Navigation(Position position, Predicate inBounds) { + public Navigation(final Position position, final Predicate inBounds) { this.position = position; rememberVisitedPosition(position); inBoundsPredicate = inBounds; } - protected boolean inBounds(Position position) { + public Position getPosition() { + return position; + } + + protected boolean inBounds(final Position position) { return inBoundsPredicate.test(position); } - protected final void rememberVisitedPosition(Position position) { + protected final void rememberVisitedPosition(final Position position) { this.visitedPositions.add(position); } - protected void translate(Vector heading, Integer amount) { + protected void translate(final Vector heading, final Integer amount) { Position newPosition = this.position; for (int i = 0; i < amount; i++) { newPosition = newPosition.translate(heading, 1); @@ -54,11 +52,18 @@ public List getVisitedPositions() { return getVisitedPositions(false); } - public List getVisitedPositions(boolean includeStartPosition) { + public List getVisitedPositions(final boolean includeStartPosition) { if (includeStartPosition) { return this.visitedPositions; } else { return this.visitedPositions.subList(1, this.visitedPositions.size()); } } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Navigation [position=").append(position).append("]"); + return builder.toString(); + } } diff --git a/src/main/java/com/github/pareronia/aoc/navigation/NavigationWithHeading.java b/src/main/java/com/github/pareronia/aoc/navigation/NavigationWithHeading.java index e83312c6..98ad9a00 100644 --- a/src/main/java/com/github/pareronia/aoc/navigation/NavigationWithHeading.java +++ b/src/main/java/com/github/pareronia/aoc/navigation/NavigationWithHeading.java @@ -5,16 +5,8 @@ import com.github.pareronia.aoc.geometry.Position; import com.github.pareronia.aoc.geometry.Turn; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -@ToString(callSuper = true, onlyExplicitlyIncluded = true) public class NavigationWithHeading extends Navigation { - @ToString.Include - @Getter - @Setter private Heading heading; public NavigationWithHeading(final Position position, final Heading heading) { @@ -37,18 +29,33 @@ public NavigationWithHeading navigate(final Heading heading, final int amount) { return this; } + public Heading getHeading() { + return heading; + } + + public void setHeading(final Heading heading) { + this.heading = heading; + } + public NavigationWithHeading turn(final Turn turn) { this.heading = this.heading.turn(turn); return this; } public NavigationWithHeading forward(final Integer amount) { - translate(this.heading.getVector(), amount); + translate(this.heading.vector(), amount); return this; } public NavigationWithHeading drift(final Heading heading, final Integer amount) { - translate(heading.getVector(), amount); + translate(heading.vector(), amount); return this; } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("NavigationWithHeading [heading=").append(heading).append("]"); + return builder.toString(); + } } diff --git a/src/main/java/com/github/pareronia/aoc/solution/ANSIColors.java b/src/main/java/com/github/pareronia/aoc/solution/ANSIColors.java new file mode 100644 index 00000000..0d0c6d77 --- /dev/null +++ b/src/main/java/com/github/pareronia/aoc/solution/ANSIColors.java @@ -0,0 +1,61 @@ +package com.github.pareronia.aoc.solution; + +public class ANSIColors { + + public static final String RESET = "\u001B[0m"; + + // Text attributes + public static final String BOLD = "\u001B[1m"; + + // Regular text colors + public static final String BLACK = "\u001B[30m"; + public static final String RED = "\u001B[31m"; + public static final String GREEN = "\u001B[32m"; + public static final String YELLOW = "\u001B[33m"; + public static final String BLUE = "\u001B[34m"; + public static final String MAGENTA = "\u001B[35m"; + public static final String CYAN = "\u001B[36m"; + public static final String WHITE = "\u001B[37m"; + + // Bright text colors + public static final String BRIGHT_BLACK = "\u001B[30;1m"; + public static final String BRIGHT_RED = "\u001B[31;1m"; + public static final String BRIGHT_GREEN = "\u001B[32;1m"; + public static final String BRIGHT_YELLOW = "\u001B[33;1m"; + public static final String BRIGHT_BLUE = "\u001B[34;1m"; + public static final String BRIGHT_MAGENTA = "\u001B[35;1m"; + public static final String BRIGHT_CYAN = "\u001B[36;1m"; + public static final String BRIGHT_WHITE = "\u001B[37;1m"; + + // Background colors + public static final String BACKGROUND_BLACK = "\u001B[40m"; + public static final String BACKGROUND_RED = "\u001B[41m"; + public static final String BACKGROUND_GREEN = "\u001B[42m"; + public static final String BACKGROUND_YELLOW = "\u001B[43m"; + public static final String BACKGROUND_BLUE = "\u001B[44m"; + public static final String BACKGROUND_MAGENTA = "\u001B[45m"; + public static final String BACKGROUND_CYAN = "\u001B[46m"; + public static final String BACKGROUND_WHITE = "\u001B[47m"; + + // Bright background colors + public static final String BRIGHT_BACKGROUND_BLACK = "\u001B[40;1m"; + public static final String BRIGHT_BACKGROUND_RED = "\u001B[41;1m"; + public static final String BRIGHT_BACKGROUND_GREEN = "\u001B[42;1m"; + public static final String BRIGHT_BACKGROUND_YELLOW = "\u001B[43;1m"; + public static final String BRIGHT_BACKGROUND_BLUE = "\u001B[44;1m"; + public static final String BRIGHT_BACKGROUND_MAGENTA = "\u001B[45;1m"; + public static final String BRIGHT_BACKGROUND_CYAN = "\u001B[46;1m"; + public static final String BRIGHT_BACKGROUND_WHITE = "\u001B[47;1m"; + + public static String bold(final String text) { + return BOLD + text + RESET; + } + + public static String yellow(final String text) { + return YELLOW + text + RESET; + } + + public static String red(final String text) { + return RED + text + RESET; + } +} diff --git a/src/main/java/com/github/pareronia/aoc/solution/Logger.java b/src/main/java/com/github/pareronia/aoc/solution/Logger.java index d5edb61e..90e38f0f 100644 --- a/src/main/java/com/github/pareronia/aoc/solution/Logger.java +++ b/src/main/java/com/github/pareronia/aoc/solution/Logger.java @@ -2,13 +2,14 @@ import java.util.function.Supplier; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor public class Logger { private final boolean debug; private boolean trace; + + public Logger(final boolean debug) { + this.debug = debug; + } public void setTrace(final boolean trace) { this.trace = trace; diff --git a/src/main/java/com/github/pareronia/aoc/solution/LoggerEnabled.java b/src/main/java/com/github/pareronia/aoc/solution/LoggerEnabled.java new file mode 100644 index 00000000..447a93b4 --- /dev/null +++ b/src/main/java/com/github/pareronia/aoc/solution/LoggerEnabled.java @@ -0,0 +1,24 @@ +package com.github.pareronia.aoc.solution; + +import java.util.function.Supplier; + +public interface LoggerEnabled { + + default void log(final Object obj) { + getLogger().log(obj); + } + + default void trace(final Object obj) { + getLogger().trace(obj); + } + + default void log(final Supplier supplier) { + getLogger().log(supplier); + } + + default void trace(final Supplier supplier) { + getLogger().trace(supplier); + } + + Logger getLogger(); +} diff --git a/src/main/java/com/github/pareronia/aoc/solution/SolutionBase.java b/src/main/java/com/github/pareronia/aoc/solution/SolutionBase.java index 175b2a3a..ff6cf1a9 100644 --- a/src/main/java/com/github/pareronia/aoc/solution/SolutionBase.java +++ b/src/main/java/com/github/pareronia/aoc/solution/SolutionBase.java @@ -6,20 +6,23 @@ import static com.github.pareronia.aoc.solution.SolutionUtils.runSamples; import java.util.List; -import java.util.function.Supplier; import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aocd.SystemUtils; -public abstract class SolutionBase { +public abstract class SolutionBase implements LoggerEnabled { protected final boolean debug; protected final Puzzle puzzle; protected final Logger logger; + protected final SystemUtils systemUtils; + protected boolean trace; protected SolutionBase(final boolean debug) { this.debug = debug; this.logger = new Logger(debug); this.puzzle = puzzle(this.getClass()); + this.systemUtils = new SystemUtils(); } protected abstract Input parseInput(List inputs); @@ -36,10 +39,11 @@ protected void run() throws Exception { this.samples(); final Timed timed = Timed.timed( - () -> this.parseInput(this.getInputData())); - final Input input = timed.getResult(); + () -> this.parseInput(this.getInputData()), + systemUtils::getSystemNanoTime); + final Input input = timed.result(); System.out.println(String.format("Input took %s", - printDuration(timed.getDuration()))); + printDuration(timed.duration()))); puzzle.check( () -> lap("Part 1", () -> this.solvePart1(input)), () -> lap("Part 2", () -> this.solvePart2(input)) @@ -55,26 +59,16 @@ public Output2 part2(final List inputs) { } protected List getInputData() { - return puzzle.getInputData(); - } - - protected void setTrace(final boolean trace) { - this.logger.setTrace(trace); - } - - protected void log(final Object obj) { - this.logger.log(obj); - } - - protected void trace(final Object obj) { - this.logger.trace(obj); - } - - protected void log(final Supplier supplier) { - this.logger.log(supplier); + return puzzle.inputData(); } - protected void trace(final Supplier supplier) { - this.logger.trace(supplier); + @Override + public Logger getLogger() { + return this.logger; } + + protected void setTrace(final boolean trace) { + this.trace = true; + this.logger.setTrace(trace); + } } diff --git a/src/main/java/com/github/pareronia/aoc/solution/SolutionUtils.java b/src/main/java/com/github/pareronia/aoc/solution/SolutionUtils.java index a6bd81a9..f94b71f2 100644 --- a/src/main/java/com/github/pareronia/aoc/solution/SolutionUtils.java +++ b/src/main/java/com/github/pareronia/aoc/solution/SolutionUtils.java @@ -10,42 +10,45 @@ import java.util.concurrent.Callable; import java.util.stream.Stream; -import com.github.pareronia.aocd.Aocd; import com.github.pareronia.aocd.Puzzle; +import com.github.pareronia.aocd.SystemUtils; +import com.github.pareronia.aocd.User; public class SolutionUtils { public static Puzzle puzzle(final Class klass) { final String[] split = klass.getSimpleName().substring("AoC".length()).split("_"); - return Aocd.puzzle(Integer.parseInt(split[0]), Integer.parseInt(split[1])); + return Puzzle.builder() + .year(Integer.parseInt(split[0])) + .day(Integer.parseInt(split[1])) + .user(User.getDefaultUser()) + .build(); } public static String printDuration(final Duration duration) { - final long timeSpent = duration.toNanos() / 1_000; - double time; - String unit; - if (timeSpent < 1000) { - time = timeSpent; - unit = "µs"; - } else if (timeSpent < 1_000_000) { - time = timeSpent / 1000.0; - unit = "ms"; + final double timeSpent = duration.toNanos() / 1_000_000.0; + String time; + if (timeSpent <= 1000) { + time = String.format("%.3f", timeSpent); + } else if (timeSpent <= 5_000) { + time = ANSIColors.yellow(String.format("%.0f", timeSpent)); } else { - time = timeSpent / 1_000_000.0; - unit = "s"; + time = ANSIColors.red(String.format("%.0f", timeSpent)); } - return String.format("%.3f %s", time, unit); + return String.format("%s ms", time); } public static V lap(final String prefix, final Callable callable) throws Exception { - final Timed timed = Timed.timed(callable); - final V answer = timed.getResult(); - final String duration = printDuration(timed.getDuration()); + final Timed timed = Timed.timed( + callable, + () -> new SystemUtils().getSystemNanoTime()); + final V answer = timed.result(); + final String duration = printDuration(timed.duration()); System.out.println(String.format("%s : %s, took: %s", - prefix, answer, duration)); + prefix, ANSIColors.bold(answer.toString()), duration)); return answer; } diff --git a/src/main/java/com/github/pareronia/aoc/solution/Timed.java b/src/main/java/com/github/pareronia/aoc/solution/Timed.java index dacd4585..39a0c696 100644 --- a/src/main/java/com/github/pareronia/aoc/solution/Timed.java +++ b/src/main/java/com/github/pareronia/aoc/solution/Timed.java @@ -2,22 +2,20 @@ import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.concurrent.Callable; +import java.util.function.Supplier; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -@Getter -public final class Timed { - private final V result; - private final Duration duration; +public record Timed(V result, Duration duration) { - public static Timed timed(final Callable callable) throws Exception { - final long timerStart = System.nanoTime(); + public static Timed timed( + final Callable callable, + final Supplier nanoTimeSupplier + ) throws Exception + { + final long timerStart = nanoTimeSupplier.get(); final V answer = callable.call(); + final long timerEnd = nanoTimeSupplier.get(); return new Timed<>( answer, - Duration.of(System.nanoTime() - timerStart, ChronoUnit.NANOS)); + Duration.of(timerEnd - timerStart, ChronoUnit.NANOS)); } } diff --git a/src/main/java/com/github/pareronia/aoc/vm/Instruction.java b/src/main/java/com/github/pareronia/aoc/vm/Instruction.java index b8f99cd5..c502feba 100644 --- a/src/main/java/com/github/pareronia/aoc/vm/Instruction.java +++ b/src/main/java/com/github/pareronia/aoc/vm/Instruction.java @@ -5,12 +5,7 @@ import java.util.Collections; import java.util.List; -import lombok.Value; - -@Value -public class Instruction { - private final Opcode opcode; - private final List operands; +public record Instruction(Opcode opcode, List operands) { public static Instruction NOP() { return new Instruction(Opcode.NOP, Collections.emptyList()); diff --git a/src/main/java/com/github/pareronia/aoc/vm/Program.java b/src/main/java/com/github/pareronia/aoc/vm/Program.java index 8fff9308..eef4496a 100644 --- a/src/main/java/com/github/pareronia/aoc/vm/Program.java +++ b/src/main/java/com/github/pareronia/aoc/vm/Program.java @@ -7,30 +7,58 @@ import java.util.function.Consumer; import java.util.function.Supplier; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; - -@RequiredArgsConstructor public class Program { private final List instructions; - @Getter private final Map memory = new HashMap<>(); - @Getter private final Map registers = new HashMap<>(); - @Getter(value = AccessLevel.PACKAGE) private final Integer infiniteLoopTreshold; - @Getter(value = AccessLevel.PACKAGE) private final Consumer outputConsumer; - @Setter - @Getter(value = AccessLevel.PACKAGE) private Supplier inputSupplier = null; - @Getter private Integer instructionPointer = 0; - @Getter private long cycles = 0L; + + public Program( + final List instructions, + final Integer infiniteLoopTreshold, + final Consumer outputConsumer + ) { + this.instructions = instructions; + this.infiniteLoopTreshold = infiniteLoopTreshold; + this.outputConsumer = outputConsumer; + } + + public Map getMemory() { + return memory; + } + public Map getRegisters() { + return registers; + } + + protected Integer getInfiniteLoopTreshold() { + return infiniteLoopTreshold; + } + + protected Consumer getOutputConsumer() { + return outputConsumer; + } + + protected Supplier getInputSupplier() { + return inputSupplier; + } + + public void setInputSupplier(final Supplier inputSupplier) { + this.inputSupplier = inputSupplier; + } + + public Integer getInstructionPointer() { + return instructionPointer; + } + + public long getCycles() { + return cycles; + } + public void reset() { this.instructionPointer = 0; this.cycles = 0; diff --git a/src/main/java/com/github/pareronia/aoc/vm/VirtualMachine.java b/src/main/java/com/github/pareronia/aoc/vm/VirtualMachine.java index 5d3951b4..e44b7895 100644 --- a/src/main/java/com/github/pareronia/aoc/vm/VirtualMachine.java +++ b/src/main/java/com/github/pareronia/aoc/vm/VirtualMachine.java @@ -36,16 +36,16 @@ private void nop(final Program program, final Instruction instruction, final Int } private void set(final Program program, final Instruction instruction, final Integer ip) { - final String register = (String) instruction.getOperands().get(0); - final String op2 = (String) instruction.getOperands().get(1); + final String register = (String) instruction.operands().get(0); + final String op2 = (String) instruction.operands().get(1); final Optional value = getValue(program, op2); value.ifPresent(v -> program.setRegisterValue(register, v)); program.moveIntructionPointer(1); } private void add(final Program program, final Instruction instruction, final Integer ip) { - final String register = (String) instruction.getOperands().get(0); - final String op2 = (String) instruction.getOperands().get(1); + final String register = (String) instruction.operands().get(0); + final String op2 = (String) instruction.operands().get(1); final Optional value = getValue(program, op2); value.ifPresent(v -> { final Long newValue = Optional.ofNullable(program.getRegisters().get(register)) @@ -57,8 +57,8 @@ private void add(final Program program, final Instruction instruction, final Int } private void sub(final Program program, final Instruction instruction, final Integer ip) { - final String register = (String) instruction.getOperands().get(0); - final String op2 = (String) instruction.getOperands().get(1); + final String register = (String) instruction.operands().get(0); + final String op2 = (String) instruction.operands().get(1); final Optional value = getValue(program, op2); value.ifPresent(v -> { final Long newValue = Optional.ofNullable(program.getRegisters().get(register)) @@ -70,8 +70,8 @@ private void sub(final Program program, final Instruction instruction, final Int } private void mul(final Program program, final Instruction instruction, final Integer ip) { - final String register = (String) instruction.getOperands().get(0); - final String op2 = (String) instruction.getOperands().get(1); + final String register = (String) instruction.operands().get(0); + final String op2 = (String) instruction.operands().get(1); final Optional value = getValue(program, op2); value.ifPresent(v -> { final Long newValue = Optional.ofNullable(program.getRegisters().get(register)) @@ -83,8 +83,8 @@ private void mul(final Program program, final Instruction instruction, final Int } private void div(final Program program, final Instruction instruction, final Integer ip) { - final String register = (String) instruction.getOperands().get(0); - final String op2 = (String) instruction.getOperands().get(1); + final String register = (String) instruction.operands().get(0); + final String op2 = (String) instruction.operands().get(1); final Optional value = getValue(program, op2); value.ifPresent(v -> { assert v != 0; @@ -97,8 +97,8 @@ private void div(final Program program, final Instruction instruction, final Int } private void mod(final Program program, final Instruction instruction, final Integer ip) { - final String register = (String) instruction.getOperands().get(0); - final String op2 = (String) instruction.getOperands().get(1); + final String register = (String) instruction.operands().get(0); + final String op2 = (String) instruction.operands().get(1); final Optional value = getValue(program, op2); value.ifPresent(v -> { assert v > 0; @@ -111,8 +111,8 @@ private void mod(final Program program, final Instruction instruction, final Int } private void eql(final Program program, final Instruction instruction, final Integer ip) { - final String register = (String) instruction.getOperands().get(0); - final String op2 = (String) instruction.getOperands().get(1); + final String register = (String) instruction.operands().get(0); + final String op2 = (String) instruction.operands().get(1); final Optional value = getValue(program, op2); value.ifPresent(v -> { final Long newValue = Optional.ofNullable(program.getRegisters().get(register)) @@ -124,7 +124,7 @@ private void eql(final Program program, final Instruction instruction, final Int } private void jmp(final Program program, final Instruction instruction, final Integer ip) { - final Integer count = (Integer) instruction.getOperands().get(0); + final Integer count = (Integer) instruction.operands().get(0); program.moveIntructionPointer(count); } @@ -139,9 +139,9 @@ private void jg0(final Program program, final Instruction instruction, final Int private void jump(final Program program, final Instruction instruction, final Integer ip, final Predicate condition ) { - final String op1 = (String) instruction.getOperands().get(0); + final String op1 = (String) instruction.operands().get(0); final Optional test = getValue(program, op1); - final String op2 = (String) instruction.getOperands().get(1); + final String op2 = (String) instruction.operands().get(1); final Long count; if (op2.startsWith("*")) { count = program.getRegisters().get(op2.substring(1)); @@ -155,9 +155,9 @@ private void jump(final Program program, final Instruction instruction, } private void tgl(final Program program, final Instruction instruction, final Integer ip) { - final String register = (String) instruction.getOperands().get(0); + final String register = (String) instruction.operands().get(0); Optional.ofNullable(program.getRegisters().get(register)) - .map(v -> v.intValue()) + .map(Long::intValue) .filter(v -> ip + v < program.getInstructions().size() && ip + v >= 0) .ifPresent(v -> { @@ -170,24 +170,24 @@ private void tgl(final Program program, final Instruction instruction, final Int } private Instruction toggleInstruction(final Instruction instruction) { - if (instruction.getOpcode() == Opcode.ADD - && instruction.getOperands().get(1).equals(1L)) { - final String register = (String) instruction.getOperands().get(0); + if (instruction.opcode() == Opcode.ADD + && instruction.operands().get(1).equals(1L)) { + final String register = (String) instruction.operands().get(0); return Instruction.ADD(register, -1L); - } else if (instruction.getOpcode() == Opcode.ADD - && instruction.getOperands().get(1).equals(-1L) - || instruction.getOpcode() == Opcode.TGL) { - final String register = (String) instruction.getOperands().get(0); + } else if (instruction.opcode() == Opcode.ADD + && instruction.operands().get(1).equals(-1L) + || instruction.opcode() == Opcode.TGL) { + final String register = (String) instruction.operands().get(0); return Instruction.ADD(register, 1L); - } else if (instruction.getOpcode() == Opcode.JN0) { - final String op1 = (String) instruction.getOperands().get(0); - final String op2 = (String) instruction.getOperands().get(1); + } else if (instruction.opcode() == Opcode.JN0) { + final String op1 = (String) instruction.operands().get(0); + final String op2 = (String) instruction.operands().get(1); return Instruction.SET( op2.startsWith("*") ? op2.substring(1) : op2, op1); - } else if (instruction.getOpcode() == Opcode.SET) { - final String op1 = (String) instruction.getOperands().get(0); - final String op2 = (String) instruction.getOperands().get(1); + } else if (instruction.opcode() == Opcode.SET) { + final String op1 = (String) instruction.operands().get(0); + final String op2 = (String) instruction.operands().get(1); return Instruction.JN0( op2, StringUtils.isNumeric(op1) ? op1 : "*" + op1); @@ -196,7 +196,7 @@ private Instruction toggleInstruction(final Instruction instruction) { } private void out(final Program program, final Instruction instruction, final Integer ip) { - final String op1 = (String) instruction.getOperands().get(0); + final String op1 = (String) instruction.operands().get(0); final Optional value = getValue(program, op1); if (program.getOutputConsumer() != null) { value.ifPresent(v -> program.getOutputConsumer().accept(v)); @@ -205,10 +205,10 @@ private void out(final Program program, final Instruction instruction, final Int } private void on0(final Program program, final Instruction instruction, final Integer ip) { - final String op1 = (String) instruction.getOperands().get(0); + final String op1 = (String) instruction.operands().get(0); final Optional test = getValue(program, op1); final Optional value; - final String op2 = (String) instruction.getOperands().get(1); + final String op2 = (String) instruction.operands().get(1); if (op2.startsWith("*")) { value = Optional.ofNullable(program.getRegisters().get(op2.substring(1))); } else { @@ -223,7 +223,7 @@ private void on0(final Program program, final Instruction instruction, final Int } private void inp(final Program program, final Instruction instruction, final Integer ip ) { - final String op1 = (String) instruction.getOperands().get(0); + final String op1 = (String) instruction.operands().get(0); assert program.getInputSupplier() != null; final Long value = program.getInputSupplier().get(); program.getRegisters().put(op1, value); @@ -252,7 +252,7 @@ public void step(final Program program) { final Integer instructionPointer = program.getInstructionPointer(); final Instruction instruction = program.getInstructions().get(instructionPointer); - this.instructionSet.get(instruction.getOpcode()) + this.instructionSet.get(instruction.opcode()) .execute(program, instruction, instructionPointer); program.incrementCycles(); } diff --git a/src/main/java/com/github/pareronia/aocd/AllUsersRunner.java b/src/main/java/com/github/pareronia/aocd/AllUsersRunner.java new file mode 100644 index 00000000..9a2d260b --- /dev/null +++ b/src/main/java/com/github/pareronia/aocd/AllUsersRunner.java @@ -0,0 +1,20 @@ +package com.github.pareronia.aocd; +import java.util.Set; + +import com.github.pareronia.aocd.MultipleDaysRunner.Day; +import com.github.pareronia.aocd.MultipleDaysRunner.Listener; + +public class AllUsersRunner { + + private static final Set ALL_USERS = User.builder().getAllUsers(); + private static final Set DAYS = Set.of( + Day.at( + Integer.getInteger("year"), + Integer.getInteger("day") + ) + ); + + public static void main(final String[] args) throws Exception { + new MultipleDaysRunner().run(DAYS, ALL_USERS, new Listener() {}); + } +} diff --git a/src/main/java/com/github/pareronia/aocd/Aocd.java b/src/main/java/com/github/pareronia/aocd/Aocd.java index 620c9de3..29537db6 100644 --- a/src/main/java/com/github/pareronia/aocd/Aocd.java +++ b/src/main/java/com/github/pareronia/aocd/Aocd.java @@ -30,19 +30,18 @@ public class Aocd { public static final ZoneId AOC_TZ = ZoneId.of("America/New_York"); + @Deprecated public static List getData(final Integer year, final Integer day) { - final List inputData = puzzle(year, day).getInputData(); - if (inputData.isEmpty()) { - System.err.println("!! INPUT DATA MISSING !!"); - } - return inputData; - } - - public static Puzzle puzzle(final Integer year, final Integer day) { - return Puzzle.create(year, day); + return Puzzle.builder() + .year(year).day(day).user(User.getDefaultUser()) + .build() + .inputData(); } - public static Puzzle puzzle(final Integer year, final Integer day, final String name) { - return Puzzle.create(year, day, name); + @Deprecated + public static Puzzle puzzle(final Integer year, final Integer day) { + return Puzzle.builder() + .year(year).day(day).user(User.getDefaultUser()) + .build(); } } diff --git a/src/main/java/com/github/pareronia/aocd/MultipleDaysRunner.java b/src/main/java/com/github/pareronia/aocd/MultipleDaysRunner.java index ff10f4d3..abb21cdb 100644 --- a/src/main/java/com/github/pareronia/aocd/MultipleDaysRunner.java +++ b/src/main/java/com/github/pareronia/aocd/MultipleDaysRunner.java @@ -27,27 +27,51 @@ public class MultipleDaysRunner { private final SystemUtils systemUtils = new SystemUtils(); public void run(final Set days, final Listener listener) throws Exception { + this.run(days, Set.of(User.getDefaultUser()), listener); + } + + public void run(final Set days, final Set users, final Listener listener) { for (final Day day : new TreeSet<>(days)) { - final var puzzle = Aocd.puzzle(day.year, day.day); - final List input = new ArrayList<>(); - input.add(String.valueOf(puzzle.getYear())); - input.add(String.valueOf(puzzle.getDay())); - input.addAll(puzzle.getInputData()); - final var args = input.toArray(new String[input.size()]); - final var request = Request.create(systemUtils.getLocalDate(), args); - final var response = Runner.create(systemUtils).run(request); - final String result1 = Optional.ofNullable(response.getPart1()) - .map(Part::getAnswer).orElse(null); - listener.result(puzzle, 1, puzzle.getAnswer1(), result1); - if (day.day == 25) { - continue; - } - final String result2 = Optional.ofNullable(response.getPart2()) - .map(Part::getAnswer).orElse(null); - listener.result(puzzle, 2, puzzle.getAnswer2(), result2); + for (final User user : users) { + final var puzzle = Puzzle.builder() + .year(day.year).day(day.day).user(user) + .build(); + try { + this.run(puzzle, listener); + } catch (final Exception e) { + System.err.println("%d/%d/%s: FAIL (%s)".formatted( + day.year, day.day, user.name(), + Optional.ofNullable(e.getCause()) + .map(Throwable::getMessage) + .orElse(e.getMessage()))); + } + } } } + private void run(final Puzzle puzzle, final Listener listener) throws Exception { + final List input = new ArrayList<>(); + input.add(String.valueOf(puzzle.year())); + input.add(String.valueOf(puzzle.day())); + final List inputData = puzzle.inputData(); + if (inputData.isEmpty()) { + return; + } + input.addAll(inputData); + final var args = input.toArray(new String[input.size()]); + final var request = Request.create(systemUtils.getLocalDate(), args); + final var response = Runner.create(systemUtils).run(request, false); + final String result1 = Optional.ofNullable(response.getPart1()) + .map(Part::getAnswer).orElse(null); + listener.result(puzzle, 1, puzzle.answer1(), result1); + if (puzzle.day() == 25) { + return; + } + final String result2 = Optional.ofNullable(response.getPart2()) + .map(Part::getAnswer).orElse(null); + listener.result(puzzle, 2, puzzle.answer2(), result2); + } + public static void main(final String[] _args) throws Exception { new MultipleDaysRunner().run(DAYS, new Listener() {}); } @@ -75,16 +99,19 @@ default void result( final var failDecider = new Puzzle.FailDecider(); final String message; final Status status = failDecider.fail(expected, actual); + final int day = puzzle.day(); + final int year = puzzle.year(); + final String name = puzzle.user().name(); if (status == Puzzle.FailDecider.Status.FAIL) { message = String.format( - "%d/%02d/%d: FAIL - expected '%s', got '%s'", - puzzle.getYear(), puzzle.getDay(), part, expected, actual); + "%d/%02d/%d/%-10s: FAIL - expected '%s', got '%s'", + year, day, part, name, expected, actual); } else if (status == Puzzle.FailDecider.Status.UNKNOWN) { message = String.format( - "%d/%02d/%d: UNKNOWN", puzzle.getYear(), puzzle.getDay(), part); + "%d/%02d/%d/%-10s: UNKNOWN", year, day, part, name); } else { message = String.format( - "%d/%02d/%d: OK", puzzle.getYear(), puzzle.getDay(), part); + "%d/%02d/%d/%-10s: OK - %s", year, day, part, name, actual); } System.out.println(message); } diff --git a/src/main/java/com/github/pareronia/aocd/Puzzle.java b/src/main/java/com/github/pareronia/aocd/Puzzle.java index b07351c5..b0171ae1 100644 --- a/src/main/java/com/github/pareronia/aocd/Puzzle.java +++ b/src/main/java/com/github/pareronia/aocd/Puzzle.java @@ -29,71 +29,36 @@ of this software and associated documentation files (the "Software"), to deal import java.time.LocalDate; import java.time.LocalDateTime; import java.time.Month; +import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.Callable; import java.util.stream.Stream; import com.github.pareronia.aoc.StringUtils; -import lombok.Getter; +public record Puzzle( + int year, + int day, + User user, + List inputData, + String answer1, + String answer2 + ) { + + public static PuzzleBuilder builder() { + return new PuzzleBuilder(new SystemUtils()); + } -public class Puzzle { - - private final SystemUtils systemUtils; - @Getter - private final int year; - @Getter - private final int day; - private final Path inputDataFile; - private final Path titleFile; - private final Path answer1File; - private final Path answer2File; - private final FailDecider failDecider; - private final LocalDateTime releaseTime; - private String title; - private String answer1; - private String answer2; - - private Puzzle(final SystemUtils systemUtils, final Integer year, final Integer day, final User user, final Path aocdDir) { - this.systemUtils = systemUtils; - this.year = year; - this.day = day; - this.inputDataFile = user.getMemoDir().resolve(String.format("%d_%02d_input.txt", year, day)); - this.titleFile = aocdDir.resolve("titles").resolve(String.format("%d_%02d.txt", year, day)); - this.answer1File = user.getMemoDir().resolve(String.format("%d_%02da_answer.txt", year, day)); - this.answer2File = user.getMemoDir().resolve(String.format("%d_%02db_answer.txt", year, day)); - this.failDecider = new FailDecider(); - this.releaseTime = LocalDate.of(year, Month.DECEMBER, day).atStartOfDay(Aocd.AOC_TZ).toLocalDateTime(); - } - - public static final Puzzle create(final Integer year, final Integer day, final String name) { - return create(year, day, User.getUser(name)); - } - - public static final Puzzle create(final Integer year, final Integer day) { - return create(year, day, User.getDefaultUser()); - } - - public static final Puzzle create(final Integer year, final Integer day, final User user) { - final SystemUtils systemUtils = new SystemUtils(); - final Path aocdDir = systemUtils.getAocdDir(); - return create(systemUtils, year, day, user, aocdDir); - } - - public static final Puzzle create(final SystemUtils systemUtils, final Integer year, final Integer day, final User user, final Path aocdDir) { - return new Puzzle(systemUtils, year, day, user, aocdDir); - } - - public void check(final Callable part1, final Callable part2) throws Exception { + public void check(final Callable part1, final Callable part2) throws Exception { + final Puzzle.FailDecider failDecider = new Puzzle.FailDecider(); final String[] fails = new String[2]; - final String answer1 = getAnswer1(); final V1 result1 = part1.call(); if (failDecider.fail(answer1, result1) == FailDecider.Status.FAIL) { fails[0] = String.format( "%sPart 1: Expected: '%s', got '%s'", System.lineSeparator(), answer1, result1); } - final String answer2 = getAnswer2(); final V2 result2 = part2.call(); if (failDecider.fail(answer2, result2) == FailDecider.Status.FAIL) { fails[1] = String.format( @@ -104,48 +69,10 @@ public void check(final Callable part1, final Callable part2) t throw new AssertionError(Stream.of(fails).collect(joining())); } } - - public String getTitle() { - if (this.title == null) { - this.title = systemUtils.readFirstLineIfExists(titleFile).orElse(StringUtils.EMPTY); - } - return this.title; - } - - public String getAnswer1() { - if (StringUtils.isEmpty(this.answer1)) { - this.answer1 = systemUtils.readFirstLineIfExists(answer1File) - .orElse(StringUtils.EMPTY); - } - return this.answer1; - } - - public String getAnswer2() { - if (StringUtils.isEmpty(this.answer2)) { - this.answer2 = systemUtils.readFirstLineIfExists(answer2File) - .orElse(StringUtils.EMPTY); - } - return this.answer2; - } + @Deprecated public List getInputData() { - List inputData = systemUtils.readAllLinesIfExists(inputDataFile); - if (inputData.isEmpty()) { - if (!isReleased()) { - System.err.println("!! PUZZLE NOT YET RELEASED !!"); - return inputData; - } - systemUtils.getInput(year, day, inputDataFile); - inputData = systemUtils.readAllLinesIfExists(inputDataFile); - } - if (inputData.isEmpty()) { - System.err.println("!! INPUT DATA MISSING !!"); - } - return inputData; - } - - boolean isReleased() { - return !systemUtils.getLocalDateTime().isBefore(releaseTime); + return this.inputData; } public static final class FailDecider { @@ -158,4 +85,80 @@ public Status fail(final String answer, final V result) { return answer.equals(String.valueOf(result)) ? Status.OK : Status.FAIL; } } + + public static class PuzzleBuilder { + private final SystemUtils systemUtils; + private int year; + private int day; + private User user; + + protected PuzzleBuilder(final SystemUtils systemUtils) { + this.systemUtils = systemUtils; + } + + public Puzzle build() { + Objects.requireNonNull(year); + Objects.requireNonNull(day); + Objects.requireNonNull(user); + final List inputData = readInputData(); + final Path answer1File = user.memoDir() + .resolve(String.format("%d_%02da_answer.txt", year, day)); + final String answer1 = readAnswer(answer1File); + final Path answer2File = user.memoDir() + .resolve(String.format("%d_%02db_answer.txt", year, day)); + final String answer2 = readAnswer(answer2File); + return new Puzzle(year, day, user, inputData, answer1, answer2); + } + + public PuzzleBuilder year(final int year) { + this.year = year; + return this; + } + + public PuzzleBuilder day(final int day) { + this.day = day; + return this; + } + + public PuzzleBuilder user(final User user) { + this.user = user; + return this; + } + + private List readInputData() { + final Path inputDataFile = user.memoDir() + .resolve(String.format("%d_%02d_input.txt", year, day)); + List inputData = systemUtils.readAllLinesIfExists(inputDataFile); + if (!inputData.isEmpty()) { + return inputData; + } + if (!isReleased()) { + System.err.println("!! PUZZLE NOT YET RELEASED !!"); + return Collections.emptyList(); + } + if (user.token().startsWith("offline|")) { + return Collections.emptyList(); + } + inputData = systemUtils.getInput(user.token(), year, day); + if (inputData.isEmpty()) { + System.err.println("!! INPUT DATA MISSING !!"); + return Collections.emptyList(); + } + systemUtils.writeAllLines(inputDataFile, inputData); + return inputData; + } + + private String readAnswer(final Path answerFile) { + return systemUtils.readFirstLineIfExists(answerFile) + .orElse(StringUtils.EMPTY); + } + + private boolean isReleased() { + final LocalDateTime releaseTime + = LocalDate.of(year, Month.DECEMBER, day) + .atStartOfDay(Aocd.AOC_TZ) + .toLocalDateTime(); + return !systemUtils.getLocalDateTime().isBefore(releaseTime); + } + } } diff --git a/src/main/java/com/github/pareronia/aocd/RunServer.java b/src/main/java/com/github/pareronia/aocd/RunServer.java index cefbe541..d79c9641 100644 --- a/src/main/java/com/github/pareronia/aocd/RunServer.java +++ b/src/main/java/com/github/pareronia/aocd/RunServer.java @@ -14,10 +14,8 @@ import java.util.ArrayList; import java.util.List; import java.util.logging.Level; +import java.util.logging.Logger; -import lombok.extern.java.Log; - -@Log public class RunServer { public static final int OK = 0; @@ -27,6 +25,8 @@ public class RunServer { private static final String END = "END"; private static final String STOP = "STOP"; + private static final Logger log = Logger.getLogger(RunServer.class.getName()); + private final int port; private final RequestHandler requestHandler; private ServerSocket server; diff --git a/src/main/java/com/github/pareronia/aocd/Runner.java b/src/main/java/com/github/pareronia/aocd/Runner.java index f1ac987d..fa3743ba 100644 --- a/src/main/java/com/github/pareronia/aocd/Runner.java +++ b/src/main/java/com/github/pareronia/aocd/Runner.java @@ -13,15 +13,17 @@ import com.github.pareronia.aoc.Json; import com.github.pareronia.aoc.solution.SolutionBase; +import com.github.pareronia.aoc.solution.Timed; import com.github.pareronia.aocd.RunServer.RequestHandler; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor class Runner { - public static void main(final String[] args) throws Exception { + protected Runner(final SystemUtils systemUtils, final ClassFactory classFactory) { + this.systemUtils = systemUtils; + this.classFactory = classFactory; + } + + public static void main(final String[] args) throws Exception { final SystemUtils systemUtils = new SystemUtils(); final Request request = Request.create(systemUtils.getLocalDate(), args); final Response result = Runner.create(systemUtils).run(request); @@ -38,7 +40,7 @@ public static RequestHandler createRequestHandler(final SystemUtils systemUtils) static Runner create(final SystemUtils systemUtils) { return new Runner(systemUtils, - className -> Class.forName(className)); + Class::forName); } static Runner create(final SystemUtils systemUtils, @@ -50,6 +52,10 @@ static Runner create(final SystemUtils systemUtils, private final ClassFactory classFactory; Response run(final Request request) throws Exception { + return run(request, true); + } + + Response run(final Request request, final boolean warmup) throws Exception { final Class klass; try { final String className = "AoC" + request.year.toString() @@ -59,15 +65,23 @@ Response run(final Request request) throws Exception { return Response.EMPTY; } if (SolutionBase.class.isAssignableFrom(klass)) { - warmUpSolutionPart(1, klass, List.copyOf(request.inputs)); + if (warmup) { + warmUpSolutionPart(1, klass, List.copyOf(request.inputs)); + } final Result result1 = runSolutionPart(1, klass, List.copyOf(request.inputs)); - warmUpSolutionPart(2, klass, List.copyOf(request.inputs)); + if (warmup) { + warmUpSolutionPart(2, klass, List.copyOf(request.inputs)); + } final Result result2 = runSolutionPart(2, klass, List.copyOf(request.inputs)); return Response.create(result1, result2); } else { - warmUpPart(1, klass, List.copyOf(request.inputs)); + if (warmup) { + warmUpPart(1, klass, List.copyOf(request.inputs)); + } final Result result1 = runPart(1, klass, List.copyOf(request.inputs)); - warmUpPart(2, klass, List.copyOf(request.inputs)); + if (warmup) { + warmUpPart(2, klass, List.copyOf(request.inputs)); + } final Result result2 = runPart(2, klass, List.copyOf(request.inputs)); return Response.create(result1, result2); } @@ -99,10 +113,9 @@ private Result runPart( { final Object puzzle = createPuzzle(klass, input); final Method method = klass.getDeclaredMethod("solvePart" + part); - final long start = systemUtils.getSystemNanoTime(); - final Object answer = method.invoke(puzzle); - final Duration duration = Duration.ofNanos(systemUtils.getSystemNanoTime() - start); - return new Result(answer, duration); + final Timed timed = Timed.timed( + () -> method.invoke(puzzle), systemUtils::getSystemNanoTime); + return new Result(timed.result(), timed.duration()); } private Result runSolutionPart( @@ -113,10 +126,9 @@ private Result runSolutionPart( { final Object puzzle = createPuzzle(klass); final Method method = SolutionBase.class.getDeclaredMethod("part" + part, List.class); - final long start = systemUtils.getSystemNanoTime(); - final Object answer = method.invoke(puzzle, input); - final Duration duration = Duration.ofNanos(systemUtils.getSystemNanoTime() - start); - return new Result(answer, duration); + final Timed timed = Timed.timed( + () -> method.invoke(puzzle, input), systemUtils::getSystemNanoTime); + return new Result(timed.result(), timed.duration()); } private Object createPuzzle(final Class klass, final List input) throws Exception { @@ -131,15 +143,23 @@ private Object createPuzzle(final Class klass) throws Exception { .invoke(null); } - @RequiredArgsConstructor private static final class Result { - private final Object answer; + protected Result(final Object answer, final Duration duration) { + this.answer = answer; + this.duration = duration; + } + private final Object answer; private final Duration duration; } - @RequiredArgsConstructor static final class Request { - private final Integer year; + protected Request(final Integer year, final Integer day, final List inputs) { + this.year = year; + this.day = day; + this.inputs = inputs; + } + + private final Integer year; private final Integer day; private final List inputs; @@ -163,31 +183,53 @@ public static Request create(final LocalDate date, final String args[]) { } } - @RequiredArgsConstructor - @Getter static final class Response { public static final Response EMPTY = new Response(null, null); private final Part part1; private final Part part2; - protected static Response create( + protected Response(final Part part1, final Part part2) { + this.part1 = part1; + this.part2 = part2; + } + + protected static Response create( final Result result1, final Result result2) { return new Response( new Part(result1.answer.toString(), result1.duration.toNanos()), new Part(result2.answer.toString(), result2.duration.toNanos())); } + public Part getPart1() { + return part1; + } + + public Part getPart2() { + return part2; + } + @Override public String toString() { return Json.toJson(this); } - @RequiredArgsConstructor - @Getter public static final class Part { private final String answer; private final Long duration; + + protected Part(final String answer, final Long duration) { + this.answer = answer; + this.duration = duration; + } + + public String getAnswer() { + return answer; + } + + public Long getDuration() { + return duration; + } } } diff --git a/src/main/java/com/github/pareronia/aocd/SystemUtils.java b/src/main/java/com/github/pareronia/aocd/SystemUtils.java index 2b699845..7813cdc4 100644 --- a/src/main/java/com/github/pareronia/aocd/SystemUtils.java +++ b/src/main/java/com/github/pareronia/aocd/SystemUtils.java @@ -35,6 +35,7 @@ of this software and associated documentation files (the "Software"), to deal import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collections; @@ -64,33 +65,18 @@ public Path getAocdDir() { } } - public String getToken() { - final String tokenFromEnv = System.getenv("AOC_SESSION"); - if (StringUtils.isNotBlank(tokenFromEnv)) { - return tokenFromEnv; - } - return readAllLines(getAocdDir().resolve("token")).stream() - .findFirst() - .orElseThrow(() -> new AocdException("Missing session ID")); + public String getTokenFromEnv() { + return System.getenv("AOC_SESSION"); } - @SuppressWarnings("unchecked") - public Map getUserIds() { - try (Reader reader = Files.newBufferedReader(getAocdDir().resolve("token2id.json"))) { + @SuppressWarnings("unchecked") + public Map readMapFromJsonFile(final Path path) { + try (Reader reader = Files.newBufferedReader(path)) { return Json.fromJson(reader, Map.class); } catch (final IOException e) { throw new AocdException(e); } - } - - @SuppressWarnings("unchecked") - public Map getTokens() { - try (Reader reader = Files.newBufferedReader(getAocdDir().resolve("tokens.json"))) { - return Json.fromJson(reader, Map.class); - } catch (final IOException e) { - throw new AocdException(e); - } - } + } public List readAllLinesIfExists(final Path path) { if (Files.notExists(Objects.requireNonNull(path))) { @@ -103,6 +89,18 @@ public Optional readFirstLineIfExists(final Path path) { return readAllLinesIfExists(path).stream().findFirst(); } + public Optional readFirstLine(final Path path) { + return readAllLines(path).stream().findFirst(); + } + + public void writeAllLines(final Path path, final List lines) { + try { + Files.write(path, lines, StandardOpenOption.CREATE, StandardOpenOption.WRITE); + } catch (final IOException e) { + throw new AocdException(e); + } + } + public LocalDate getLocalDate() { return LocalDate.now(Aocd.AOC_TZ); } @@ -115,14 +113,14 @@ public long getSystemNanoTime() { return System.nanoTime(); } - public void getInput(final int year, final int day, final Path path) { + public List getInput(final String cookie, final int year, final int day) { final HttpClient http = HttpClient.newHttpClient(); final HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(String.format("https://adventofcode.com/%d/day/%d/input", year, day))) - .header("Cookie", "session=" + getToken()) + .uri(URI.create("https://adventofcode.com/%d/day/%d/input".formatted(year, day))) + .header("Cookie", "session=" + cookie) .build(); try { - http.send(request, BodyHandlers.ofFile(path)); + return http.send(request, BodyHandlers.ofLines()).body().toList(); } catch (IOException | InterruptedException e) { throw new AocdException(e); } @@ -170,4 +168,9 @@ private String getOsName() { private String getSystemProperty(final String property) { return System.getProperty(property); } + + public void clrscr() { + // TODO Auto-generated method stub + + } } diff --git a/src/main/java/com/github/pareronia/aocd/User.java b/src/main/java/com/github/pareronia/aocd/User.java index f22ba2d2..f8331909 100644 --- a/src/main/java/com/github/pareronia/aocd/User.java +++ b/src/main/java/com/github/pareronia/aocd/User.java @@ -23,47 +23,77 @@ of this software and associated documentation files (the "Software"), to deal */ package com.github.pareronia.aocd; +import static java.util.stream.Collectors.toSet; + import java.nio.file.Path; import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import com.github.pareronia.aoc.StringUtils; + +public record User(String name, String token, String id, Path memoDir) { + + public static User getDefaultUser() { + return User.builder().build(); + } + + public static UserBuilder builder() { + return new UserBuilder(new SystemUtils()); + } + + public static class UserBuilder { + private final SystemUtils systemUtils; + private String name; + + protected UserBuilder(final SystemUtils systemUtils) { + this.systemUtils = systemUtils; + } + + public UserBuilder name(final String name) { + this.name = name; + return this; + } + + public User build() { + return build(this.name); + } + + public Set getAllUsers() { + return getTokens().keySet().stream().map(this::build).collect(toSet()); + } + + private User build(String name) { + final String token; + if (name == null) { + name = "default"; + token = getToken(); + } else { + token = getTokens().get(name); + } + Objects.requireNonNull(token); + final String id = getUserIds().get(token); + final Path memoDir = systemUtils.getAocdDir().resolve(id); + return new User(name, token, id, memoDir); + } + + public String getToken() { + final String tokenFromEnv = systemUtils.getTokenFromEnv(); + if (StringUtils.isNotBlank(tokenFromEnv)) { + return tokenFromEnv; + } + return systemUtils.readFirstLine(systemUtils.getAocdDir().resolve("token")) + .orElseThrow(() -> new AocdException("Missing session ID")); + } + + private Map getUserIds() { + final Path path = systemUtils.getAocdDir().resolve("token2id.json"); + return systemUtils.readMapFromJsonFile(path); + } -public class User { - - private final String token; - private final Path memoDir; - private final String id; - - private User(final SystemUtils systemUtils, final String token) { - this.token = token; - this.id = systemUtils.getUserIds().get(token); - this.memoDir = systemUtils.getAocdDir().resolve(this.id); - } - - public String getToken() { - return token; - } - - public Path getMemoDir() { - return memoDir; - } - - public String getId() { - return id; - } - - public static User create(final SystemUtils systemUtils, final String token) { - return new User(systemUtils, token); - } - - public static User getDefaultUser() { - final SystemUtils systemUtils = new SystemUtils(); - final String token = systemUtils.getToken(); - return new User(systemUtils, token); - } - - public static User getUser(final String name) { - final SystemUtils systemUtils = new SystemUtils(); - final Map tokens = systemUtils.getTokens(); - final String token = tokens.get(name); - return new User(systemUtils, token); - } + private Map getTokens() { + final Path path = systemUtils.getAocdDir().resolve("tokens.json"); + return systemUtils.readMapFromJsonFile(path); + } + } } diff --git a/src/main/python/AoC2015_01.py b/src/main/python/AoC2015_01.py index 56d62bdf..69912812 100644 --- a/src/main/python/AoC2015_01.py +++ b/src/main/python/AoC2015_01.py @@ -3,63 +3,69 @@ # Advent of Code 2015 Day 1 # -from aoc import my_aocd +import sys +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples -def part_1(inputs: tuple[str]) -> int: - assert len(inputs) == 1 - return len(inputs[0]) - 2 * inputs[0].count(")") +Input = str +Output1 = int +Output2 = int +TEST1 = "(())" +TEST2 = "()()" +TEST3 = "(((" +TEST4 = "(()(()(" +TEST5 = "))(((((" +TEST6 = "())" +TEST7 = "))(" +TEST8 = ")))" +TEST9 = ")())())" +TEST10 = ")" +TEST11 = "()())" -def part_2(inputs: tuple[str]) -> int: - assert len(inputs) == 1 - sum_ = int() - for i, c in enumerate(inputs[0]): - if c == "(": - sum_ += 1 - elif c == ")": - sum_ -= 1 - else: - raise ValueError("Invalid input") - if sum_ == -1: - return i + 1 - raise RuntimeError("Unreachable") +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data)[0] -TEST1 = "(())".splitlines() -TEST2 = "()()".splitlines() -TEST3 = "(((".splitlines() -TEST4 = "(()(()(".splitlines() -TEST5 = "))(((((".splitlines() -TEST6 = "())".splitlines() -TEST7 = "))(".splitlines() -TEST8 = ")))".splitlines() -TEST9 = ")())())".splitlines() -TEST10 = ")".splitlines() -TEST11 = "()())".splitlines() + def part_1(self, input: Input) -> Output1: + return len(input) - 2 * input.count(")") + def part_2(self, input: Input) -> Output2: + sum_ = 0 + for i, c in enumerate(input): + sum_ += 1 if c == "(" else -1 + if sum_ == -1: + return i + 1 + raise RuntimeError("Unreachable") + + @aoc_samples( + ( + ("part_1", TEST1, 0), + ("part_1", TEST2, 0), + ("part_1", TEST3, 3), + ("part_1", TEST4, 3), + ("part_1", TEST5, 3), + ("part_1", TEST6, -1), + ("part_1", TEST7, -1), + ("part_1", TEST8, -3), + ("part_1", TEST9, -3), + ("part_2", TEST10, 1), + ("part_2", TEST11, 5), + ) + ) + def samples(self) -> None: + pass -def main() -> None: - my_aocd.print_header(2015, 1) - assert part_1(TEST1) == 0 - assert part_1(TEST2) == 0 - assert part_1(TEST3) == 3 - assert part_1(TEST4) == 3 - assert part_1(TEST5) == 3 - assert part_1(TEST6) == -1 - assert part_1(TEST7) == -1 - assert part_1(TEST8) == -3 - assert part_1(TEST9) == -3 - assert part_2(TEST10) == 1 - assert part_2(TEST11) == 5 +solution = Solution(2015, 1) - inputs = my_aocd.get_input(2015, 1, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2015_02.py b/src/main/python/AoC2015_02.py index 7a058ff8..d5d55bd3 100644 --- a/src/main/python/AoC2015_02.py +++ b/src/main/python/AoC2015_02.py @@ -3,49 +3,64 @@ # Advent of Code 2015 Day 2 # -from aoc import my_aocd +import sys +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples -def _calculate_required_area(present: tuple[int, int, int]) -> int: - l, w, h = present - sides = (2 * l * w, 2 * w * h, 2 * h * l) - return sum(sides) + min(sides) // 2 +Present = tuple[int, int, int] +Input = list[Present] +Output1 = int +Output2 = int -def _calculate_required_length(present: tuple[int, int, int]) -> int: - l, w, h = present - circumferences = (2 * (l + w), 2 * (w + h), 2 * (h + l)) - return min(circumferences) + l * w * h +TEST1 = "2x3x4" +TEST2 = "1x1x10" -def part_1(inputs: tuple[str]) -> int: - return sum(_calculate_required_area((map(int, line.split('x')))) - for line in inputs) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + def parse_present(line: str) -> Present: + lg, w, h = map(int, line.split("x")) + return (lg, w, h) + return [parse_present(line) for line in input_data] -def part_2(inputs: tuple[str]) -> int: - return sum(_calculate_required_length((map(int, line.split('x')))) - for line in inputs) + def part_1(self, presents: Input) -> Output1: + def calculate_required_area(present: Present) -> int: + lg, w, h = present + sides = (2 * lg * w, 2 * w * h, 2 * h * lg) + return sum(sides) + min(sides) // 2 + return sum(calculate_required_area(present) for present in presents) -TEST1 = "2x3x4".splitlines() -TEST2 = "1x1x10".splitlines() + def part_2(self, presents: Input) -> Output2: + def calculate_required_length(present: Present) -> int: + lg, w, h = present + circumferences = (2 * (lg + w), 2 * (w + h), 2 * (h + lg)) + return min(circumferences) + lg * w * h + return sum(calculate_required_length(present) for present in presents) + + @aoc_samples( + ( + ("part_1", TEST1, 58), + ("part_1", TEST2, 43), + ("part_2", TEST1, 34), + ("part_2", TEST2, 14), + ) + ) + def samples(self) -> None: + pass -def main() -> None: - my_aocd.print_header(2015, 2) - assert part_1(TEST1) == 58 - assert part_1(TEST2) == 43 - assert part_2(TEST1) == 34 - assert part_2(TEST2) == 14 +solution = Solution(2015, 2) - inputs = my_aocd.get_input(2015, 2, 1000) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2015_03.py b/src/main/python/AoC2015_03.py index edbbb573..d9536e9b 100644 --- a/src/main/python/AoC2015_03.py +++ b/src/main/python/AoC2015_03.py @@ -3,67 +3,78 @@ # Advent of Code 2015 Day 3 # -import aocd -from aoc import my_aocd -from aoc.navigation import NavigationWithHeading, Heading -from aoc.geometry import Position - - -def _count_unique_positions(positions: list[Position]) -> int: - return len({p for p in positions}) - - -def _add_navigation_instruction( - navigation: NavigationWithHeading, c: str -) -> None: - navigation.drift(Heading.from_str(c), 1) - +import sys -def part_1(inputs: tuple[str, ...]) -> int: - assert len(inputs) == 1 - navigation = NavigationWithHeading(Position(0, 0), Heading.NORTH) - for c in inputs[0]: - _add_navigation_instruction(navigation, c) - return _count_unique_positions(navigation.get_visited_positions(True)) - - -def part_2(inputs: tuple[str, ...]) -> int: - assert len(inputs) == 1 - santa_navigation = NavigationWithHeading(Position(0, 0), Heading.NORTH) - robot_navigation = NavigationWithHeading(Position(0, 0), Heading.NORTH) - for i, c in enumerate(inputs[0]): - if i % 2 != 0: - _add_navigation_instruction(robot_navigation, c) - else: - _add_navigation_instruction(santa_navigation, c) - visited = santa_navigation.get_visited_positions(True) - visited.extend(robot_navigation.get_visited_positions(True)) - return _count_unique_positions(visited) - - -TEST1 = ">".splitlines() -TEST2 = "^>v<".splitlines() -TEST3 = "^v^v^v^v^v".splitlines() -TEST4 = "^v".splitlines() +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry import Position +from aoc.navigation import Heading +from aoc.navigation import NavigationWithHeading + +START = Position(0, 0) +Navigation = NavigationWithHeading +Input = list[Heading] +Output1 = int +Output2 = int + +TEST1 = ">" +TEST2 = "^>v<" +TEST3 = "^v^v^v^v^v" +TEST4 = "^v" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [Heading.from_str(ch) for ch in list(input_data)[0]] + + def count_unique_positions(self, positions: list[Position]) -> int: + return len({p for p in positions}) + + def add_navigation_instruction( + self, navigation: Navigation, c: Heading + ) -> None: + navigation.drift(c, 1) + + def part_1(self, inputs: Input) -> Output1: + navigation = Navigation(START, Heading.NORTH) + for c in inputs: + self.add_navigation_instruction(navigation, c) + return self.count_unique_positions( + navigation.get_visited_positions(True) + ) + + def part_2(self, inputs: Input) -> Output2: + santa_navigation = Navigation(START, Heading.NORTH) + robot_navigation = Navigation(START, Heading.NORTH) + for i, c in enumerate(inputs): + self.add_navigation_instruction( + robot_navigation if i % 2 else santa_navigation, c + ) + return self.count_unique_positions( + santa_navigation.get_visited_positions(True) + + robot_navigation.get_visited_positions(True) + ) + + @aoc_samples( + ( + ("part_1", TEST1, 2), + ("part_1", TEST2, 4), + ("part_1", TEST3, 2), + ("part_2", TEST4, 3), + ("part_2", TEST2, 3), + ("part_2", TEST3, 11), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2015, 3) def main() -> None: - puzzle = aocd.models.Puzzle(2015, 3) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST1) == 2 # type:ignore[arg-type] - assert part_1(TEST2) == 4 # type:ignore[arg-type] - assert part_1(TEST3) == 2 # type:ignore[arg-type] - assert part_2(TEST4) == 3 # type:ignore[arg-type] - assert part_2(TEST2) == 3 # type:ignore[arg-type] - assert part_2(TEST3) == 11 # type:ignore[arg-type] - - inputs = my_aocd.get_input_data(puzzle, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) if __name__ == "__main__": diff --git a/src/main/python/AoC2015_04.py b/src/main/python/AoC2015_04.py index b9a3d18e..f0922ed6 100644 --- a/src/main/python/AoC2015_04.py +++ b/src/main/python/AoC2015_04.py @@ -3,49 +3,56 @@ # Advent of Code 2015 Day 4 # +import sys from hashlib import md5 -from aoc import my_aocd -from aoc.common import spinner +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.common import spinner -def _find_md5_starting_with_zeroes(input_: str, zeroes: int) -> int: - i = 0 - val = input_ - target = "0" * zeroes - while val[:zeroes] != target: - i += 1 - spinner(i) - str2hash = input_ + str(i) - val = md5(str2hash.encode()).hexdigest() # nosec - return i +Input = str +Output1 = int +Output2 = int -def part_1(inputs: tuple[str]) -> int: - assert len(inputs) == 1 - return _find_md5_starting_with_zeroes(inputs[0], 5) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data)[0] + def find_md5_starting_with_zeroes(self, seed: str, zeroes: int) -> int: + i = 0 + val = seed + target = "0" * zeroes + while val[:zeroes] != target: + i += 1 + spinner(i) + str2hash = seed + str(i) + val = md5(str2hash.encode()).hexdigest() # nosec + return i -def part_2(inputs: tuple[str]) -> int: - assert len(inputs) == 1 - return _find_md5_starting_with_zeroes(inputs[0], 6) + def part_1(self, seed: Input) -> Output1: + return self.find_md5_starting_with_zeroes(seed, 5) + def part_2(self, seed: Input) -> Output2: + return self.find_md5_starting_with_zeroes(seed, 6) -TEST1 = "abcdef".splitlines() -TEST2 = "pqrstuv".splitlines() + @aoc_samples( + ( + ("part_1", "abcdef", 609043), + ("part_1", "pqrstuv", 1048970), + ) + ) + def samples(self) -> None: + pass -def main() -> None: - my_aocd.print_header(2015, 4) +solution = Solution(2015, 4) - assert part_1(TEST1) == 609043 - assert part_1(TEST2) == 1048970 - inputs = my_aocd.get_input(2015, 4, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2015_06.py b/src/main/python/AoC2015_06.py index 091b5ca0..2825e8fe 100644 --- a/src/main/python/AoC2015_06.py +++ b/src/main/python/AoC2015_06.py @@ -5,158 +5,137 @@ from __future__ import annotations +import sys from enum import Enum -from typing import Callable, NamedTuple +from enum import auto +from enum import unique +from typing import NamedTuple -import aocd -import numpy as np -import numpy.typing as npt +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.grid import Cell -from aoc import my_aocd -from aoc.geometry import Position +TEST1 = "turn on 0,0 through 999,999" +TEST2 = "toggle 0,0 through 999,0" +TEST3 = "turn off 499,499 through 500,500" +TEST4 = "turn on 0,0 through 0,0" +TEST5 = "toggle 0,0 through 999,999" +@unique +class Mode(Enum): + MODE_1 = auto() + MODE_2 = auto() + + +@unique class Action(Enum): - TURN_ON = 1 - TOGGLE = 2 - TURN_OFF = 3 + TURN_ON = auto() + TOGGLE = auto() + TURN_OFF = auto() + + @classmethod + def from_string(cls, action: str) -> Action: + match action: + case "turn_on": + return Action.TURN_ON + case "turn_off": + return Action.TURN_OFF + case _: + return Action.TOGGLE + + def apply( + self, lights: list[list[int]], start: Cell, end: Cell, mode: Mode + ) -> None: + match self: + case Action.TURN_ON: + match mode: + case Mode.MODE_1: + for r in range(start.row, end.row + 1): + for c in range(start.col, end.col + 1): + lights[r][c] = 1 + case Mode.MODE_2: + for r in range(start.row, end.row + 1): + for c in range(start.col, end.col + 1): + lights[r][c] += 1 + case Action.TURN_OFF: + match mode: + case Mode.MODE_1: + for r in range(start.row, end.row + 1): + for c in range(start.col, end.col + 1): + lights[r][c] = 0 + case Mode.MODE_2: + for r in range(start.row, end.row + 1): + for c in range(start.col, end.col + 1): + lights[r][c] = max(lights[r][c] - 1, 0) + case Action.TOGGLE: + match mode: + case Mode.MODE_1: + for r in range(start.row, end.row + 1): + for c in range(start.col, end.col + 1): + lights[r][c] = 0 if lights[r][c] == 1 else 1 + case Mode.MODE_2: + for r in range(start.row, end.row + 1): + for c in range(start.col, end.col + 1): + lights[r][c] += 2 class Instruction(NamedTuple): action: Action - start: Position - end: Position + start: Cell + end: Cell @classmethod def from_input(cls, input_: str) -> Instruction: - input_ = input_.replace("turn ", "turn_") - splits = input_.split(" through ") - action_and_start_splits = splits[0].split(" ") - action_s = action_and_start_splits[0] - if action_s == "turn_on": - action = Action.TURN_ON - elif action_s == "turn_off": - action = Action.TURN_OFF - elif action_s == "toggle": - action = Action.TOGGLE - else: - raise ValueError("Invalid input") - start_splits = action_and_start_splits[1].split(",") - start = Position.of(int(start_splits[0]), int(start_splits[1])) - end_splits = splits[1].split(",") - end = Position.of(int(end_splits[0]), int(end_splits[1])) - return Instruction(action, start, end) - - -class Grid: - def __init__( - self, - turn_on: Callable[[npt.NDArray[np.int_], Position, Position], None], - turn_off: Callable[[npt.NDArray[np.int_], Position, Position], None], - toggle: Callable[[npt.NDArray[np.int_], Position, Position], None], - ): - self.lights = np.zeros((1000, 1000), np.byte) - self.turn_on = turn_on - self.turn_off = turn_off - self.toggle = toggle - - def process_instructions(self, instructions: list[Instruction]) -> None: - for instruction in instructions: - action = ( - self.turn_on - if instruction.action == Action.TURN_ON - else self.turn_off - if instruction.action == Action.TURN_OFF - else self.toggle - ) - action(self.lights, instruction.start, instruction.end) - - def get_total_light_value(self) -> int: - return int(np.sum(self.lights)) - + splits = input_.replace("turn ", "turn_").split(" through ") + action_s, start_s = splits[0].split(" ") + start = Cell(*list(map(int, start_s.split(",")))[:2]) + end = Cell(*list(map(int, splits[1].split(",")))[:2]) + return Instruction(Action.from_string(action_s), start, end) -def _parse(inputs: tuple[str, ...]) -> list[Instruction]: - return [Instruction.from_input(line) for line in inputs] +Input = list[Instruction] +Output1 = int +Output2 = int -def part_1(inputs: tuple[str, ...]) -> int: - def turn_on( - lights: npt.NDArray[np.int_], start: Position, end: Position - ) -> None: - lights[start.x : end.x + 1, start.y : end.y + 1] = 1 # noqa E203 - def turn_off( - lights: npt.NDArray[np.int_], start: Position, end: Position - ) -> None: - lights[start.x : end.x + 1, start.y : end.y + 1] = 0 # noqa E203 +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [Instruction.from_input(line) for line in input_data] - def toggle( - lights: npt.NDArray[np.int_], start: Position, end: Position - ) -> None: - lights[ - start.x : end.x + 1, start.y : end.y + 1 # noqa E203 - ] = np.logical_not( - lights[start.x : end.x + 1, start.y : end.y + 1] # noqa E203 - ) - - lights = Grid( - lambda lights, start, end: turn_on(lights, start, end), - lambda lights, start, end: turn_off(lights, start, end), - lambda lights, start, end: toggle(lights, start, end), - ) - lights.process_instructions(_parse(inputs)) - return lights.get_total_light_value() - - -def part_2(inputs: tuple[str, ...]) -> int: - def turn_on( - lights: npt.NDArray[np.int_], start: Position, end: Position - ) -> None: - lights[start.x : end.x + 1, start.y : end.y + 1] += 1 # noqa E203 + def solve(self, instructions: Input, mode: Mode) -> int: + lights = [[0 for _ in range(1000)] for _ in range(1000)] + for instruction in instructions: + instruction.action.apply( + lights, instruction.start, instruction.end, mode + ) + return sum(lights[r][c] for r in range(1000) for c in range(1000)) - def turn_off( - lights: npt.NDArray[np.int_], start: Position, end: Position - ) -> None: - lights[start.x : end.x + 1, start.y : end.y + 1] -= 1 # noqa E203 - lights[lights < 0] = 0 + def part_1(self, instructions: Input) -> Output1: + return self.solve(instructions, Mode.MODE_1) - def toggle( - lights: npt.NDArray[np.int_], start: Position, end: Position - ) -> None: - lights[start.x : end.x + 1, start.y : end.y + 1] += 2 # noqa E203 + def part_2(self, instructions: Input) -> Output2: + return self.solve(instructions, Mode.MODE_2) - lights = Grid( - lambda lights, start, end: turn_on(lights, start, end), - lambda lights, start, end: turn_off(lights, start, end), - lambda lights, start, end: toggle(lights, start, end), + @aoc_samples( + ( + ("part_1", TEST1, 1_000_000), + ("part_1", TEST2, 1000), + ("part_1", TEST3, 0), + ("part_2", TEST4, 1), + ("part_2", TEST5, 2_000_000), + ) ) - lights.process_instructions(_parse(inputs)) - return lights.get_total_light_value() + def samples(self) -> None: + pass -TEST1 = "turn on 0,0 through 999,999".splitlines() -TEST2 = "toggle 0,0 through 999,0".splitlines() -TEST3 = "turn off 499,499 through 500,500".splitlines() -TEST4 = "turn on 0,0 through 0,0".splitlines() -TEST5 = "toggle 0,0 through 999,999".splitlines() +solution = Solution(2015, 6) def main() -> None: - puzzle = aocd.models.Puzzle(2015, 6) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST1) == 1_000_000 # type:ignore[arg-type] - assert part_1(TEST2) == 1000 # type:ignore[arg-type] - assert part_1(TEST3) == 0 # type:ignore[arg-type] - assert part_2(TEST4) == 1 # type:ignore[arg-type] - assert part_2(TEST5) == 2_000_000 # type:ignore[arg-type] - - inputs = my_aocd.get_input(puzzle.year, puzzle.day, 300) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) if __name__ == "__main__": diff --git a/src/main/python/AoC2015_07.py b/src/main/python/AoC2015_07.py new file mode 100644 index 00000000..f0d6c9cf --- /dev/null +++ b/src/main/python/AoC2015_07.py @@ -0,0 +1,118 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2015 Day 7 +# + +import sys +from collections import deque + +from aoc.common import InputData +from aoc.common import SolutionBase + +Gate = tuple[str, str, str | None, str] +Wires = dict[str, int] +Input = tuple[dict[str, int], list[Gate]] +Output1 = int +Output2 = int + + +TEST = """\ +123 -> x +456 -> y +x AND y -> d +x OR y -> e +x LSHIFT 2 -> f +y RSHIFT 2 -> g +NOT x -> h +NOT y -> i +i -> j +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + wires, gates = Wires(), list[Gate]() + for line in input_data: + first, second = line.split(" -> ") + splits = first.split() + if len(splits) == 1: + if first.isnumeric(): + wires[second] = int(first) + continue + else: + in1, op, in2 = splits[0], "SET", None + elif len(splits) == 2: + in1, op, in2 = splits[1], "NOT", None + else: + in1, op, in2 = splits[0], splits[1], splits[2] + gates.append((in1, op, in2, second)) + return wires, gates + + def solve(self, wires: Wires, gates: list[Gate], wire: str) -> int: + def exec_op(in1: str, op: str, in2: str | None, out: str) -> None: + match op: + case "SET": + wires[out] = wires[in1] + case "AND": + assert in2 is not None + if in1.isnumeric(): + wires[out] = int(in1) & wires[in2] + else: + wires[out] = wires[in1] & wires[in2] + case "LSHIFT": + assert in2 is not None + wires[out] = wires[in1] << int(in2) + case "NOT": + wires[out] = (1 << 16) + ~wires[in1] + case "OR": + assert in2 is not None + wires[out] = wires[in1] | wires[in2] + case "RSHIFT": + assert in2 is not None + wires[out] = wires[in1] >> int(in2) + case _: + raise ValueError + + q = deque(gates) + while q: + in1, op, in2, out = q.popleft() + if (in1.isnumeric() or in1 in wires) and ( + in2 is None or in2.isnumeric() or in2 in wires + ): + exec_op(in1, op, in2, out) + else: + q.append((in1, op, in2, out)) + return wires[wire] + + def part_1(self, input: Input) -> Output1: + wires, gates = input + return self.solve(wires.copy(), gates, "a") + + def part_2(self, input: Input) -> Output2: + wires, gates = input + wires_2 = wires.copy() + wires_2["b"] = self.solve(wires.copy(), gates, "a") + return self.solve(wires_2, gates, "a") + + def samples(self) -> None: + wires, gates = self.parse_input(TEST.splitlines()) + assert self.solve(wires, gates, "x") == 123 + assert self.solve(wires, gates, "y") == 456 + assert self.solve(wires, gates, "d") == 72 + assert self.solve(wires, gates, "e") == 507 + assert self.solve(wires, gates, "f") == 492 + assert self.solve(wires, gates, "g") == 114 + assert self.solve(wires, gates, "h") == 65412 + assert self.solve(wires, gates, "i") == 65079 + assert self.solve(wires, gates, "j") == 65079 + + +solution = Solution(2015, 7) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2015_08.py b/src/main/python/AoC2015_08.py index 24d17e40..c355b583 100644 --- a/src/main/python/AoC2015_08.py +++ b/src/main/python/AoC2015_08.py @@ -4,50 +4,74 @@ # import re -from aoc import my_aocd +import sys +from enum import Enum +from enum import auto +from enum import unique +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples -def _count_decoding_overhead(string: str) -> int: - assert string[0] == '"' and string[-1] == '"' - cnt = 2 - while string.find(r'\\') != -1: - string = string.replace(r'\\', '', 1) - cnt += 1 - return cnt + string.count(r'\"') \ - + 3 * len(re.findall(r'\\x[0-9a-f]{2}', string)) +TEST = r""""" +"abc" +"aaa\"aaa" +"\x27" +""" +RE = re.compile(r"\\x[0-9a-f]{2}") +Input = InputData +Output1 = int +Output2 = int -def part_1(inputs: tuple[str]) -> int: - return sum([_count_decoding_overhead(s) for s in inputs]) +@unique +class Mode(Enum): + DECODE = auto() + ENCODE = auto() -def _count_encoding_overhead(string: str) -> int: - return 2 + string.count('\\') + string.count('"') + def overhead(self, string: str) -> int: + match self: + case Mode.DECODE: + assert string[0] == '"' and string[-1] == '"' + cnt = 2 + while string.find(r"\\") != -1: + string = string.replace(r"\\", "", 1) + cnt += 1 + return cnt + string.count(r"\"") + 3 * len(RE.findall(string)) + case Mode.ENCODE: + return 2 + string.count("\\") + string.count('"') -def part_2(inputs: tuple[str]) -> int: - return sum([_count_encoding_overhead(s) for s in inputs]) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return input_data + def solve(self, input: Input, mode: Mode) -> int: + return sum(mode.overhead(s) for s in input) -TEST = r'''"" -"abc" -"aaa\"aaa" -"\x27" -'''.splitlines() + def part_1(self, input: Input) -> Output1: + return self.solve(input, Mode.DECODE) + def part_2(self, input: Input) -> Output2: + return self.solve(input, Mode.ENCODE) + + @aoc_samples( + ( + ("part_1", TEST, 12), + ("part_2", TEST, 19), + ) + ) + def samples(self) -> None: + pass -def main() -> None: - my_aocd.print_header(2015, 8) - assert part_1(TEST) == 12 - assert part_2(TEST) == 19 +solution = Solution(2015, 8) - inputs = my_aocd.get_input(2015, 8, 300) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2015_09.py b/src/main/python/AoC2015_09.py index 1361d5db..0658604b 100644 --- a/src/main/python/AoC2015_09.py +++ b/src/main/python/AoC2015_09.py @@ -6,22 +6,30 @@ from __future__ import annotations import itertools +import sys from collections import defaultdict -from typing import Generator, NamedTuple +from typing import Iterator +from typing import NamedTuple -import aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples -from aoc import my_aocd +TEST = """\ +London to Dublin = 464 +London to Belfast = 518 +Dublin to Belfast = 141 +""" class Distances(NamedTuple): matrix: list[list[int]] @classmethod - def from_input(cls, inputs: tuple[str]) -> Distances: + def from_input(cls, inputs: InputData) -> Distances: cnt = 0 - def get_and_increment(): + def get_and_increment() -> int: nonlocal cnt tmp = cnt cnt += 1 @@ -38,40 +46,42 @@ def get_and_increment(): matrix[k[1]][k[0]] = v return Distances(matrix) - def get_distances_of_complete_routes(self) -> Generator[int]: + def get_distances_of_complete_routes(self) -> Iterator[int]: size = len(self.matrix) for p in itertools.permutations(range(size), size): yield sum(self.matrix[p[i - 1]][p[i]] for i in range(1, size)) -def part_1(inputs: tuple[str]) -> int: - return min(Distances.from_input(inputs).get_distances_of_complete_routes()) +Input = Distances +Output1 = int +Output2 = int -def part_2(inputs: tuple[str]) -> int: - return max(Distances.from_input(inputs).get_distances_of_complete_routes()) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return Distances.from_input(input_data) + def part_1(self, distances: Input) -> Output1: + return min(distances.get_distances_of_complete_routes()) -TEST = """\ -London to Dublin = 464 -London to Belfast = 518 -Dublin to Belfast = 141 -""".splitlines() + def part_2(self, distances: Input) -> Output2: + return max(distances.get_distances_of_complete_routes()) + + @aoc_samples( + ( + ("part_1", TEST, 605), + ("part_2", TEST, 982), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2015, 9) def main() -> None: - puzzle = aocd.models.Puzzle(2015, 9) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST) == 605 - assert part_2(TEST) == 982 - - inputs = my_aocd.get_input(puzzle.year, puzzle.day, 28) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) if __name__ == "__main__": diff --git a/src/main/python/AoC2015_10.py b/src/main/python/AoC2015_10.py index b27de509..aba2cbaf 100644 --- a/src/main/python/AoC2015_10.py +++ b/src/main/python/AoC2015_10.py @@ -3,54 +3,170 @@ # Advent of Code 2015 Day 10 # -from aoc import my_aocd -from aoc.common import spinner - - -def _look_and_say(string: str) -> str: - result = "" - i = 0 - while i < len(string): - digit = string[i] - j = 0 - while i+j < len(string) and string[i+j] == digit: - j += 1 - result += str(j) + digit - i += j - return result - - -def _look_and_say_iterations(string: str, iterations: int) -> str: - for i in range(iterations): - string = _look_and_say(string) - spinner(i, iterations // 4) - return string - - -def part_1(inputs: tuple[str]) -> int: - assert len(inputs) == 1 - return len(_look_and_say_iterations(inputs[0], 40)) - - -def part_2(inputs: tuple[str]) -> int: - assert len(inputs) == 1 - return len(_look_and_say_iterations(inputs[0], 50)) - - -TEST = "1".splitlines() +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase + +ELEMENTS = """\ +22 -> H -> H +13112221133211322112211213322112 -> He -> Hf Pa H Ca Li +312211322212221121123222112 -> Li -> He +111312211312113221133211322112211213322112 -> Be -> Ge Ca Li +1321132122211322212221121123222112 -> B -> Be +3113112211322112211213322112 -> C -> B +111312212221121123222112 -> N -> C +132112211213322112 -> O -> N +31121123222112 -> F -> O +111213322112 -> Ne -> F +123222112 -> Na -> Ne +3113322112 -> Mg -> Pm Na +1113222112 -> Al -> Mg +1322112 -> Si -> Al +311311222112 -> P -> Ho Si +1113122112 -> S -> P +132112 -> Cl -> S +3112 -> Ar -> Cl +1112 -> K -> Ar +12 -> Ca -> K +3113112221133112 -> Sc -> Ho Pa H Ca Co +11131221131112 -> Ti -> Sc +13211312 -> V -> Ti +31132 -> Cr -> V +111311222112 -> Mn -> Cr Si +13122112 -> Fe -> Mn +32112 -> Co -> Fe +11133112 -> Ni -> Zn Co +131112 -> Cu -> Ni +312 -> Zn -> Cu +13221133122211332 -> Ga -> Eu Ca Ac H Ca Zn +31131122211311122113222 -> Ge -> Ho Ga +11131221131211322113322112 -> As -> Ge Na +13211321222113222112 -> Se -> As +3113112211322112 -> Br -> Se +11131221222112 -> Kr -> Br +1321122112 -> Rb -> Kr +3112112 -> Sr -> Rb +1112133 -> Y -> Sr U +12322211331222113112211 -> Zr -> Y H Ca Tc +1113122113322113111221131221 -> Nb -> Er Zr +13211322211312113211 -> Mo -> Nb +311322113212221 -> Tc -> Mo +132211331222113112211 -> Ru -> Eu Ca Tc +311311222113111221131221 -> Rh -> Ho Ru +111312211312113211 -> Pd -> Rh +132113212221 -> Ag -> Pd +3113112211 -> Cd -> Ag +11131221 -> In -> Cd +13211 -> Sn -> In +3112221 -> Sb -> Pm Sn +1322113312211 -> Te -> Eu Ca Sb +311311222113111221 -> I -> Ho Te +11131221131211 -> Xe -> I +13211321 -> Cs -> Xe +311311 -> Ba -> Cs +11131 -> La -> Ba +1321133112 -> Ce -> La H Ca Co +31131112 -> Pr -> Ce +111312 -> Nd -> Pr +132 -> Pm -> Nd +311332 -> Sm -> Pm Ca Zn +1113222 -> Eu -> Sm +13221133112 -> Gd -> Eu Ca Co +3113112221131112 -> Tb -> Ho Gd +111312211312 -> Dy -> Tb +1321132 -> Ho -> Dy +311311222 -> Er -> Ho Pm +11131221133112 -> Tm -> Er Ca Co +1321131112 -> Yb -> Tm +311312 -> Lu -> Yb +11132 -> Hf -> Lu +13112221133211322112211213322113 -> Ta -> Hf Pa H Ca W +312211322212221121123222113 -> W -> Ta +111312211312113221133211322112211213322113 -> Re -> Ge Ca W +1321132122211322212221121123222113 -> Os -> Re +3113112211322112211213322113 -> Ir -> Os +111312212221121123222113 -> Pt -> Ir +132112211213322113 -> Au -> Pt +31121123222113 -> Hg -> Au +111213322113 -> Tl -> Hg +123222113 -> Pb -> Tl +3113322113 -> Bi -> Pm Pb +1113222113 -> Po -> Bi +1322113 -> At -> Po +311311222113 -> Rn -> Ho At +1113122113 -> Fr -> Rn +132113 -> Ra -> Fr +3113 -> Ac -> Ra +1113 -> Th -> Ac +13 -> Pa -> Th +3 -> U -> Pa +""" +N = 92 + + +Input = tuple[list[str], list[list[int]], str] +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + """https://github.com/maneatingape/advent-of-code-rust/blob/3a95adb336f4e9af3ca509b17aa60d6360510290/src/year2015/day10.rs""" # noqa E501 + + def parse_input(self, input_data: InputData) -> Input: + elements = [line.split() for line in ELEMENTS.splitlines()] + indices = {sp[2]: i for i, sp in enumerate(elements)} + sequence = ["" for _ in range(N)] + decays = [list[int]() for _ in range(N)] + for i, sp in enumerate(elements): + sequence[i] = sp[0] + decays[i] = [indices[x] for x in sp[4:]] + return sequence, decays, list(input_data)[0] + + def _solve(self, string: str, iterations: int) -> str: + current = string + for _ in range(iterations): + nxt = "" + i = 0 + while i < len(current): + digit = current[i] + j = 0 + while i + j < len(current) and current[i + j] == digit: + j += 1 + nxt += str(j) + digit + i += j + current = nxt + return current + + def solve(self, inputs: Input, iterations: int) -> int: + sequence, decays, input = inputs + current = [0 for _ in range(N)] + current[sequence.index(input)] = 1 + for _ in range(iterations): + nxt = [0 for _ in range(N)] + for i, c in enumerate(current): + if c > 0: + for d in decays[i]: + nxt[d] += c + current = nxt + return sum(c * len(s) for c, s in zip(current, sequence)) + + def part_1(self, inputs: Input) -> Output1: + return self.solve(inputs, iterations=40) + + def part_2(self, inputs: Input) -> Output2: + return self.solve(inputs, iterations=50) + + def samples(self) -> None: + assert self._solve("1", iterations=5) == "312211" + + +solution = Solution(2015, 10) def main() -> None: - my_aocd.print_header(2015, 10) - - assert _look_and_say_iterations(TEST, 5) == "312211" - - inputs = my_aocd.get_input(2015, 10, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2015_18.py b/src/main/python/AoC2015_18.py index c124dcb9..c49dc642 100644 --- a/src/main/python/AoC2015_18.py +++ b/src/main/python/AoC2015_18.py @@ -3,199 +3,55 @@ # Advent of Code 2015 Day 18 # -from functools import lru_cache -from aoc import my_aocd -from aoc.common import log - -ON = "#" -OFF = "." - - -def _parse_dict(inputs: tuple[str]) -> dict: - grid = dict() - for r, row in enumerate(inputs): - for c, cell in enumerate(row): - if cell == ON: - grid[(r, c)] = () - return grid, (len(inputs), len(inputs[0])) - - -def _parse_array(inputs: tuple[str]) -> list[str]: - return list(inputs) - - -def _log_grid_array(grid: list[str]): - if len(grid) > 6: - return - [log(line) for line in grid] - log("") - - -def _log_grid_dict(grid: dict, size: tuple[int, int]): - array = [] - for r in range(size[0]): - row = "" - for c in range(size[1]): - if (r, c) in grid: - row += ON - else: - row += OFF - array.append(row) - return _log_grid_array(array) - - -@lru_cache(maxsize=10000) -def _find_neighbours(r: int, c: int, num_rows: int, num_cols: int) -> tuple: - return tuple((rr, cc) - for rr in range(r-1, r+2) - for cc in range(c-1, c+2) - if not (r == rr and c == cc) - and 0 <= rr < num_rows and 0 <= cc < num_cols) - - -def _next_generation_dict(grid: dict, - size: tuple[int, int], - stuck_positions: list[tuple[int, int]], - stuck_value: str) -> dict: - to_on = set() - to_off = set() - for cell in grid.keys(): - neighbours = _find_neighbours(*cell, *size) - neighbours_on = sum(1 for _ in neighbours if _ in grid) - if neighbours_on in {2, 3} \ - or stuck_value == ON and cell in stuck_positions: - to_on.add(cell) - else: - to_off.add(cell) - for n in neighbours: - if n in grid: - continue - n_neighbours = _find_neighbours(*n, *size) - n_neighbours_on = sum(1 for _ in n_neighbours if _ in grid) - if n_neighbours_on == 3: - to_on.add(n) - for cell in to_on: - grid[cell] = () - for cell in to_off: - del grid[cell] - return grid - - -def _next_generation_array(grid: list[str], - stuck_positions: list[tuple[int, int]], - stuck_value: str) -> list[str]: - size = (len(grid), len(grid[0])) - new_grid = list[str]() - for r, row in enumerate(grid): - new_row = "" - for c, cell in enumerate(row): - neighbours = _find_neighbours(r, c, *size) - neighbours_on = sum(1 for n in neighbours - if grid[n[0]][n[1]] == ON) - if cell == ON and neighbours_on in {2, 3} \ - or cell == OFF and neighbours_on == 3 \ - or stuck_value == ON and (r, c) in stuck_positions: - new_row += ON - else: - new_row += OFF - new_grid.append(new_row) - return new_grid - - -def _run_generations_array(grid: list[str], generations: int, - stuck_positions: list[tuple[int, int]] = [], - stuck_value: str = ON) -> list[str]: - for i in range(generations): - grid = _next_generation_array(grid, stuck_positions, stuck_value) - _log_grid_array(grid) - return grid - - -def _run_generations_dict(grid: list[str], - size: tuple[int, int], - generations: int, - stuck_positions: list[tuple[int, int]] = [], - stuck_value: str = ON) -> list[str]: - for i in range(generations): - grid = _next_generation_dict(grid, size, stuck_positions, stuck_value) - _log_grid_dict(grid, size) - return grid - - -def _do_part_1_dict(inputs: tuple[str], generations: int) -> int: - grid, size = _parse_dict(inputs) - _log_grid_dict(grid, size) - grid = _run_generations_dict(grid, size, generations, [], ON) - log(_find_neighbours.cache_info()) - return len(grid) - - -def _do_part_1_array(inputs: tuple[str], generations: int) -> int: - grid = _parse_array(inputs) - _log_grid_array(grid) - grid = _run_generations_array(grid, generations) - log(_find_neighbours.cache_info()) - return sum(line.count(ON) for line in grid) - - -def part_1_array(inputs: tuple[str]) -> int: - return _do_part_1_array(inputs, 100) - - -def part_1_dict(inputs: tuple[str]) -> int: - return _do_part_1_dict(inputs, 100) - - -def _do_part_2_dict(inputs: tuple[str], generations: int) -> int: - grid, size = _parse_dict(inputs) - max_r = size[0] - 1 - max_c = size[1] - 1 - stuck_positions = [(0, 0), (0, max_c), (max_r, 0), (max_r, max_c)] - for cell in stuck_positions: - grid[cell] = () - _log_grid_dict(grid, size) - grid = _run_generations_dict(grid, size, generations, - stuck_positions, stuck_value=ON) - log(_find_neighbours.cache_info()) - return len(grid) - - -def _do_part_2_array(inputs: tuple[str], generations: int) -> int: - grid = _parse_array(inputs) - max_r = len(grid) - 1 - max_c = len(grid[0]) - 1 - stuck_positions = [(0, 0), (0, max_c), (max_r, 0), (max_r, max_c)] - new_grid = list[str]() - for r, row in enumerate(grid): - new_row = "" - for c, cell in enumerate(row): - if (r, c) in stuck_positions: - new_row += ON - else: - new_row += cell - new_grid.append(new_row) - grid = new_grid - _log_grid_array(grid) - grid = _run_generations_array(grid, generations, - stuck_positions, stuck_value=ON) - log(_find_neighbours.cache_info()) - return sum(line.count(ON) for line in grid) - - -def part_2_dict(inputs: tuple[str]) -> int: - return _do_part_2_dict(inputs, 100) - - -def part_2_array(inputs: tuple[str]) -> int: - return _do_part_2_array(inputs, 100) - - -def part_1(inputs: tuple[str]) -> int: - return part_1_dict(inputs) - - -def part_2(inputs: tuple[str]) -> int: - return part_2_dict(inputs) +import functools +import sys +from collections import Counter +from typing import Iterable +from typing import cast + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.game_of_life import ClassicRules +from aoc.game_of_life import GameOfLife +from aoc.grid import Cell + + +class FixedGrid(GameOfLife.Universe[Cell]): + def __init__(self, input: InputData): + lines = list(input) + self.height = len(lines) + self.width = len(lines[0]) + self.initial_alive = { + Cell(r, c) + for r in range(self.height) + for c in range(self.width) + if lines[r][c] == "#" + } + self.stuck_positions = { + Cell(0, 0), + Cell(0, self.width - 1), + Cell(self.height - 1, 0), + Cell(self.height - 1, self.width - 1), + } + + def neighbour_count(self, alive: Iterable[Cell]) -> dict[Cell, int]: + return Counter(n for cell in alive for n in self._neighbours(cell)) + + @functools.cache + def _neighbours(self, cell: Cell) -> set[Cell]: + return { + n + for n in cell.get_all_neighbours() + if n.row >= 0 + and n.row < self.height + and n.col >= 0 + and n.col < self.width + } + + +Input = FixedGrid +Output1 = int +Output2 = int TEST = """\ @@ -205,23 +61,51 @@ def part_2(inputs: tuple[str]) -> int: ..#... #.#..# ####.. -""".splitlines() +""" -def main() -> None: - my_aocd.print_header(2015, 18) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return FixedGrid(input_data) + + def solve_1(self, grid: Input, generations: int) -> Output1: + game_of_life = GameOfLife( + grid.initial_alive, grid, ClassicRules() + ) # type:ignore[misc] + for _ in range(generations): + game_of_life.next_generation() + return len(set(game_of_life.alive)) + + def solve_2(self, grid: Input, generations: int) -> Output2: + game_of_life = GameOfLife( + grid.initial_alive | grid.stuck_positions, grid, ClassicRules() + ) # type:ignore[misc] + for _ in range(generations): + game_of_life.next_generation() + alive = set(cast(Iterable[Cell], game_of_life.alive)) + alive |= grid.stuck_positions + game_of_life = GameOfLife( + alive, grid, ClassicRules() + ) # type:ignore[misc] + return len(set(game_of_life.alive)) + + def part_1(self, input: Input) -> Output1: + return self.solve_1(input, 100) - assert _do_part_1_array(TEST, 4) == 4 - assert _do_part_1_dict(TEST, 4) == 4 - assert _do_part_2_array(TEST, 5) == 17 - assert _do_part_2_dict(TEST, 5) == 17 + def part_2(self, input: Input) -> Output2: + return self.solve_2(input, 100) - inputs = my_aocd.get_input(2015, 18, 100) - result1 = part_1_dict(inputs) - print(f"Part 1: {result1}") - result2 = part_2_dict(inputs) - print(f"Part 2: {result2}") + def samples(self) -> None: + assert self.solve_1(self.parse_input(TEST.splitlines()), 4) == 4 + assert self.solve_2(self.parse_input(TEST.splitlines()), 5) == 17 + + +solution = Solution(2015, 18) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2015_19.py b/src/main/python/AoC2015_19.py index 78ea240b..aa8f0756 100644 --- a/src/main/python/AoC2015_19.py +++ b/src/main/python/AoC2015_19.py @@ -3,14 +3,16 @@ # Advent of Code 2015 Day 19 # +import aocd from collections import defaultdict + from aoc import my_aocd from aoc.common import log -def _parse(inputs: tuple[str]) -> tuple[dict, str]: +def _parse(inputs: tuple[str]) -> tuple[dict[str, list[str]], str]: blocks = my_aocd.to_blocks(inputs) - replacements = defaultdict(list[str]) + replacements = defaultdict[str, list[str]](list[str]) for line in blocks[0]: split = line.split(" => ") replacements[split[0]].append(split[1]) @@ -18,7 +20,9 @@ def _parse(inputs: tuple[str]) -> tuple[dict, str]: return replacements, blocks[1][0] -def _run_replacement(replacements: dict, molecule: str) -> set[str]: +def _run_replacement( + replacements: dict[str, list[str]], molecule: str +) -> set[str]: molecules = set[str]() key = "" for i, c in enumerate(molecule): @@ -27,12 +31,12 @@ def _run_replacement(replacements: dict, molecule: str) -> set[str]: continue if c in replacements: key = c - elif molecule[i:i+2] in replacements: - key = molecule[i:i+2] + elif molecule[i : i + 2] in replacements: # noqa E203 + key = molecule[i : i + 2] # noqa E203 else: continue for r in replacements[key]: - molecules.add(molecule[:i] + r + molecule[i+len(key):]) + molecules.add(molecule[:i] + r + molecule[i + len(key) :]) # noqa E203 return molecules @@ -41,8 +45,9 @@ def part_1(inputs: tuple[str]) -> int: return len(_run_replacement(replacements, molecule)) -def _fabricate(target: str, molecule: str, replacements: dict, - cnt: int) -> int: +def _fabricate( + target: str, molecule: str, replacements: dict[str, list[str]], cnt: int +) -> int: new_molecules = _run_replacement(replacements, molecule) if target in new_molecules: return cnt + 1 @@ -72,6 +77,7 @@ def part_2(inputs: tuple[str]) -> int: new_molecule = new_molecule.replace(old, new, 1) cnt += 1 log(cnt) + log(f"{old}->{new}") log(new_molecule) return cnt @@ -118,20 +124,22 @@ def part_2(inputs: tuple[str]) -> int: def main() -> None: - my_aocd.print_header(2015, 19) + puzzle = aocd.models.Puzzle(2015, 19) + my_aocd.print_header(puzzle.year, puzzle.day) - assert part_1(TEST1) == 4 - assert part_1(TEST2) == 7 - assert part_1(TEST3) == 6 - assert part_2_bis(TEST4) == 3 - assert part_2_bis(TEST5) == 6 + assert part_1(TEST1) == 4 # type:ignore[arg-type] + assert part_1(TEST2) == 7 # type:ignore[arg-type] + assert part_1(TEST3) == 6 # type:ignore[arg-type] + assert part_2_bis(TEST4) == 3 # type:ignore[arg-type] + assert part_2_bis(TEST5) == 6 # type:ignore[arg-type] - inputs = my_aocd.get_input(2015, 19, 45) - result1 = part_1(inputs) + inputs = my_aocd.get_input_data(puzzle, 45) + result1 = part_1(inputs) # type:ignore[arg-type] print(f"Part 1: {result1}") - result2 = part_2(inputs) + result2 = part_2(inputs) # type:ignore[arg-type] print(f"Part 2: {result2}") + my_aocd.check_results(puzzle, result1, result2) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2015_23.py b/src/main/python/AoC2015_23.py index 92f8bb08..fde2fdd5 100644 --- a/src/main/python/AoC2015_23.py +++ b/src/main/python/AoC2015_23.py @@ -21,7 +21,7 @@ def _parse(inputs: tuple[str]) -> list[Instruction_]: def _build_program(inss: list[Instruction_]) -> Program: def translate_instruction(ins: Instruction_) -> Instruction: if ins.operation == "hlf": - return Instruction.DIV(ins.operands[0], 2) + return Instruction.DIV(ins.operands[0], "2") elif ins.operation == "tpl": return Instruction.MUL(ins.operands[0], "3") elif ins.operation == "inc": diff --git a/src/main/python/AoC2016_03.py b/src/main/python/AoC2016_03.py index 6c9f70fd..f105111a 100644 --- a/src/main/python/AoC2016_03.py +++ b/src/main/python/AoC2016_03.py @@ -3,58 +3,61 @@ # Advent of Code 2016 Day 3 # -from collections.abc import Generator -import aocd -from aoc import my_aocd +import sys +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples -def _parse(inputs: tuple[str]) -> Generator[tuple[int, int, int]]: - return (tuple(map(int, line.split())) for line in inputs) +TEST1 = "5 10 25" +TEST2 = "3 4 5" +TEST3 = """\ +5 3 6 +10 4 8 +25 5 10 +""" +Input = list[list[int]] +Output1 = int +Output2 = int -def _valid(t: tuple[int]) -> bool: - return t[0] + t[1] > t[2] \ - and t[0] + t[2] > t[1] \ - and t[1] + t[2] > t[0] +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [list(map(int, line.split())) for line in input_data] -def part_1(inputs: tuple[str]) -> int: - return sum(_valid(t) for t in _parse(inputs)) + def valid(self, t: tuple[int, int, int]) -> bool: + return t[0] + t[1] > t[2] and t[0] + t[2] > t[1] and t[1] + t[2] > t[0] + def part_1(self, ts: Input) -> int: + return sum(self.valid(tuple(_ for _ in t[:3])) for t in ts) -def part_2(inputs: tuple[str]) -> int: - ts = list(_parse(inputs)) - return sum(int(_valid((ts[i][0], ts[i+1][0], ts[i+2][0]))) - + int(_valid((ts[i][1], ts[i+1][1], ts[i+2][1]))) - + int(_valid((ts[i][2], ts[i+1][2], ts[i+2][2]))) - for i in range(0, len(ts), 3) - ) + def part_2(self, ts: Input) -> int: + return sum( + sum( + self.valid((ts[i][j], ts[i + 1][j], ts[i + 2][j])) + for j in range(3) + ) + for i in range(0, len(ts), 3) + ) - -TEST1 = "5 10 25".splitlines() -TEST2 = "3 4 5".splitlines() -TEST3 = '''\ -5 3 6 -10 4 8 -25 5 10 -'''.splitlines() + @aoc_samples( + ( + ("part_1", TEST1, 0), + ("part_1", TEST2, 1), + ("part_2", TEST3, 2), + ) + ) + def samples(self) -> None: + pass -def main() -> None: - puzzle = aocd.models.Puzzle(2016, 3) - my_aocd.print_header(puzzle.year, puzzle.day) +solution = Solution(2016, 3) - assert part_1(TEST1) == 0 - assert part_1(TEST2) == 1 - assert part_2(TEST3) == 2 - inputs = my_aocd.get_input(2016, 3, 1911) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2016_04.py b/src/main/python/AoC2016_04.py index 557086a9..db42041e 100644 --- a/src/main/python/AoC2016_04.py +++ b/src/main/python/AoC2016_04.py @@ -4,13 +4,25 @@ # from __future__ import annotations -from typing import NamedTuple + import re -from aoc import my_aocd +import sys +from collections import Counter +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +REGEXP = r"([-a-z]+)-([0-9]+)\[([a-z]{5})\]$" +MATCH = r"[a-z]{9}-[a-z]{6}-[a-z]{7}$" -REGEXP = r'([-a-z]+)-([0-9]+)\[([a-z]{5})\]$' -MATCH = r'[a-z]{9}-[a-z]{6}-[a-z]{7}$' +TEST = """\ +aaaaa-bbb-z-y-x-123[abxyz] +a-b-c-d-e-f-g-h-987[abcde] +not-a-real-room-404[oarel] +totally-real-room-200[decoy] +""" class Room(NamedTuple): @@ -19,61 +31,57 @@ class Room(NamedTuple): checksum: str @classmethod - def of(cls, name: str, sector_id: str, checksum: str) -> Room: - return Room(name, int(sector_id), checksum) + def from_input(cls, line: str) -> Room: + m = re.search(REGEXP, line) + assert m is not None + return Room(m.groups()[0], int(m.groups()[1]), m.groups()[2]) def is_real(self) -> bool: - hist = {c: self.name.count(c) - for c in set(self.name.replace('-', ''))} - items = list(hist.items()) - items.sort(key=lambda x: x[1]*-100 + ord(x[0])) - return "".join([x for x, y in items][:5]) == self.checksum + most_common = sorted( + Counter(self.name.replace("-", "")).items(), + key=lambda item: item[1] * -100 + ord(item[0]), + ) + return "".join(x for x, _ in most_common[:5]) == self.checksum def decrypt(self) -> str: shift = self.sector_id % 26 - return "".join([chr((ord(c) + shift - 97) % 26 + 97) - if c != '-' else ' ' - for c in self.name]) + return "".join( + chr((ord(c) + shift - 97) % 26 + 97) if c != "-" else " " + for c in self.name + ) -def parse(inputs: tuple[str]) -> tuple[Room]: - return (Room.of(*re.search(REGEXP, input_).groups()) - for input_ in inputs) +Input = list[Room] +Output1 = int +Output2 = int -def part_1(inputs: tuple[str]) -> int: - return sum(room.sector_id - for room in - parse(inputs) if room.is_real()) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [Room.from_input(line) for line in input_data] + def part_1(self, rooms: Input) -> int: + return sum(room.sector_id for room in rooms if room.is_real()) -def part_2(inputs: tuple[str]) -> int: - matches = [room.sector_id for room in parse(inputs) - if re.match(MATCH, room.name) is not None - and room.decrypt() == 'northpole object storage'] - assert len(matches) == 1 - return matches[0] + def part_2(self, rooms: Input) -> int: + return next( + room.sector_id + for room in rooms + if re.match(MATCH, room.name) is not None + and room.decrypt() == "northpole object storage" + ) + @aoc_samples((("part_1", TEST, 1514),)) + def samples(self) -> None: + pass -TEST = '''\ -aaaaa-bbb-z-y-x-123[abxyz] -a-b-c-d-e-f-g-h-987[abcde] -not-a-real-room-404[oarel] -totally-real-room-200[decoy] -'''.splitlines() +solution = Solution(2016, 4) -def main() -> None: - my_aocd.print_header(2016, 4) - - assert part_1(TEST) == 1514 - inputs = my_aocd.get_input(2016, 4, 935) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2016_06.py b/src/main/python/AoC2016_06.py index d58f364f..f2e2b1a0 100644 --- a/src/main/python/AoC2016_06.py +++ b/src/main/python/AoC2016_06.py @@ -3,30 +3,19 @@ # Advent of Code 2015 Day 6 # +import sys from collections import Counter -from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples -def _get_counters(inputs: tuple[str]) -> list[Counter]: - def count(i: int) -> Counter: - cnt = Counter() - for input_ in inputs: - cnt[input_[i]] += 1 - return cnt - return [count(i) for i in range(len(inputs[0]))] +Input = list[Counter[str]] +Output1 = str +Output2 = str -def part_1(inputs: tuple[str]) -> str: - return "".join([counter.most_common()[0][0] - for counter in _get_counters(inputs)]) - - -def part_2(inputs: tuple[str]) -> str: - return "".join([counter.most_common()[-1][0] - for counter in _get_counters(inputs)]) - - -TEST = '''\ +TEST = """\ eedadn drvtee eandsr @@ -43,21 +32,38 @@ def part_2(inputs: tuple[str]) -> str: vrdear dvrsen enarar -'''.splitlines() +""" -def main() -> None: - my_aocd.print_header(2016, 6) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + inputs = list(input_data) + return [ + Counter(line[i] for line in inputs) for i in range(len(inputs[0])) + ] + + def part_1(self, counters: Input) -> str: + return "".join(counter.most_common()[0][0] for counter in counters) - assert part_1(TEST) == "easter" - assert part_2(TEST) == "advent" + def part_2(self, counters: Input) -> str: + return "".join(counter.most_common()[-1][0] for counter in counters) - inputs = my_aocd.get_input(2016, 6, 624) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + @aoc_samples( + ( + ("part_1", TEST, "easter"), + ("part_2", TEST, "advent"), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2016, 6) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2016_07.py b/src/main/python/AoC2016_07.py index 77824fe1..b91e7bab 100644 --- a/src/main/python/AoC2016_07.py +++ b/src/main/python/AoC2016_07.py @@ -4,75 +4,81 @@ # import re -from aoc import my_aocd +import sys -ABBA = r'([a-z])(?!\1)([a-z])\2\1' -HYPERNET = r'\[([a-z]*)\]' -ABA = r'([a-z])(?!\1)[a-z]\1' +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +ABBA = re.compile(r"([a-z])(?!\1)([a-z])\2\1") +HYPERNET = re.compile(r"\[([a-z]*)\]") +ABA = re.compile(r"([a-z])(?!\1)[a-z]\1") -def _is_tls(ip: str) -> bool: - for b in re.findall(HYPERNET, ip): - if re.search(ABBA, b) is not None: - return False - ip = ip.replace('[' + b + ']', '/') - return re.search(ABBA, ip) is not None - - -def _aba2bab(aba: str) -> str: - return aba[1] + aba[0] + aba[1] - - -def _is_sls(ip: str) -> bool: - bs = re.findall(HYPERNET, ip) - for b in bs: - ip = ip.replace('[' + b + ']', '/') - abas = [m.group() - for i in range(len(ip) - 3) - for m in re.finditer(ABA, ip[i:])] - for aba in abas: - bab = _aba2bab(aba) - for b in bs: - if bab in b: - return True - return False - - -def part_1(inputs: tuple[str]) -> int: - return sum(1 for input_ in inputs if _is_tls(input_)) - - -def part_2(inputs: tuple[str]) -> int: - return sum(1 for input_ in inputs if _is_sls(input_)) - - -TEST1 = '''\ +TEST1 = """\ abba[mnop]qrst abcd[bddb]xyyx aaaa[qwer]tyui ioxxoj[asdfgh]zxcvbn abcox[ooooo]xocba -'''.splitlines() -TEST2 = '''\ +""" +TEST2 = """\ aba[bab]xyz xyx[xyx]xyx aaa[kek]eke zazbz[bzb]cdb -'''.splitlines() +""" +Input = InputData +Output1 = int +Output2 = int -def main() -> None: - my_aocd.print_header(2016, 7) - assert part_1(TEST1) == 2 - assert part_2(TEST2) == 3 +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return input_data + + def part_1(self, inputs: InputData) -> int: + def is_tls(ip: str) -> bool: + for b in HYPERNET.findall(ip): + if ABBA.search(b) is not None: + return False + ip = ip.replace("[" + b + "]", "/") + return ABBA.search(ip) is not None + + return sum(is_tls(line) for line in inputs) + + def part_2(self, inputs: InputData) -> int: + def is_sls(ip: str) -> bool: + bs = HYPERNET.findall(ip) + for b in bs: + ip = ip.replace("[" + b + "]", "/") + return any( + any(bab in b for b in bs) + for bab in ( + m.group()[1] + m.group()[0] + m.group()[1] + for i in range(len(ip) - 3) + for m in ABA.finditer(ip[i:]) + ) + ) + + return sum(is_sls(line) for line in inputs) - inputs = my_aocd.get_input(2016, 7, 2000) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + @aoc_samples( + ( + ("part_1", TEST1, 2), + ("part_2", TEST2, 3), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2016, 7) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2016_08.py b/src/main/python/AoC2016_08.py new file mode 100644 index 00000000..21c435e8 --- /dev/null +++ b/src/main/python/AoC2016_08.py @@ -0,0 +1,82 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2016 Day 8 +# + +import itertools +import sys +from typing import cast + +from advent_of_code_ocr import convert_6 +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.grid import Cell +from aoc.grid import CharGrid + +Input = InputData +Output1 = int +Output2 = str + +ON = "#" +OFF = "." + +TEST = """\ +rect 3x2 +rotate column x=1 by 1 +rotate row y=0 by 4 +rotate column x=1 by 1 +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return input_data + + def solve(self, input: Input, rows: int, columns: int) -> CharGrid: + grid = CharGrid.from_strings([OFF * columns for _ in range(rows)]) + for line in input: + if line.startswith("rect "): + y, x = map(int, line[len("rect ") :].split("x")) # noqa E203 + for r, c in itertools.product(range(x), range(y)): + grid.set_value(Cell(r, c), ON) + elif line.startswith("rotate row "): + offset = len("rotate row ") + row, amount = line[offset:].split(" by ") + grid.roll_row(int(row[2:]), int(amount)) + else: + offset = len("rotate column ") + column, amount = line[offset:].split(" by ") + grid.roll_column(int(column[2:]), int(amount)) + return grid + + def solve_1(self, input: Input, rows: int, columns: int) -> Output1: + grid = self.solve(input, rows, columns) + return len(list(grid.get_all_equal_to(ON))) + + def part_1(self, input: Input) -> Output1: + return self.solve_1(input, 6, 50) + + def part_2(self, input: Input) -> Output2: + grid = self.solve(input, 6, 50) + return cast( + str, + convert_6( + "\n".join(grid.get_rows_as_strings()), + fill_pixel=ON, + empty_pixel=OFF, + ), + ) + + def samples(self) -> None: + assert self.solve_1(self.parse_input(TEST.splitlines()), 3, 7) == 6 + + +solution = Solution(2016, 8) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2016_09.py b/src/main/python/AoC2016_09.py index 97252ba6..fc6723db 100644 --- a/src/main/python/AoC2016_09.py +++ b/src/main/python/AoC2016_09.py @@ -3,85 +3,93 @@ # Advent of Code 2016 Day 9 # -from aoc import my_aocd - - -def _parse(inputs: tuple[str]) -> str: - assert len(inputs) == 1 - return inputs[0] - - -def _decompressed_length(input_: str, recursive: bool) -> int: - if '(' not in input_: - return len(input_) - - cnt = 0 - in_marker = False - marker = "" - i = 0 - while i < len(input_): - c = input_[i] - if c == '(': - in_marker = True - elif c == ')': - splits = marker.split('x') - skip = int(splits[0]) - repeat = int(splits[1]) - if recursive: - skipped = input_[i+1:i+1+skip] - cnt += repeat * _decompressed_length(skipped, True) +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +TEST1 = "ADVENT" +TEST2 = "A(1x5)BC" +TEST3 = "(3x3)XYZ" +TEST4 = "A(2x2)BCD(2x2)EFG" +TEST5 = "(6x1)(1x3)A" +TEST6 = "X(8x2)(3x3)ABCY" +TEST7 = "(27x12)(20x12)(13x14)(7x10)(1x12)A" +TEST8 = "(25x3)(3x3)ABC(2x3)XY(5x2)PQRSTX(18x9)(3x2)TWO(5x7)SEVEN" + + +Input = str +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data)[0] + + def decompressed_length(self, input: str, recursive: bool) -> int: + if "(" not in input: + return len(input) + + cnt = 0 + in_marker = False + marker = "" + i = 0 + while i < len(input): + c = input[i] + if c == "(": + in_marker = True + elif c == ")": + splits = marker.split("x") + skip = int(splits[0]) + repeat = int(splits[1]) + if recursive: + skipped = input[i + 1 : i + 1 + skip] # noqa E203 + cnt += repeat * self.decompressed_length(skipped, True) + else: + cnt += repeat * skip + i += skip + marker = "" + in_marker = False else: - cnt += repeat * skip - i += skip - marker = "" - in_marker = False - else: - if in_marker: - marker += c - else: - cnt += 1 - i += 1 - return cnt - - -def part_1(inputs: tuple[str]) -> int: - return _decompressed_length(_parse(inputs), False) - + if in_marker: + marker += c + else: + cnt += 1 + i += 1 + return cnt + + def part_1(self, input: str) -> int: + return self.decompressed_length(input, False) + + def part_2(self, input: str) -> int: + return self.decompressed_length(input, True) + + @aoc_samples( + ( + ("part_1", TEST1, 6), + ("part_1", TEST2, 7), + ("part_1", TEST3, 9), + ("part_1", TEST4, 11), + ("part_1", TEST5, 6), + ("part_1", TEST6, 18), + ("part_2", TEST3, 9), + ("part_2", TEST6, 20), + ("part_2", TEST7, 241920), + ("part_2", TEST8, 445), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2016, 9) -def part_2(inputs: tuple[str]) -> int: - return _decompressed_length(_parse(inputs), True) - -TEST1 = "ADVENT".splitlines() -TEST2 = "A(1x5)BC".splitlines() -TEST3 = "(3x3)XYZ".splitlines() -TEST4 = "A(2x2)BCD(2x2)EFG".splitlines() -TEST5 = "(6x1)(1x3)A".splitlines() -TEST6 = "X(8x2)(3x3)ABCY".splitlines() -TEST7 = "(27x12)(20x12)(13x14)(7x10)(1x12)A".splitlines() -TEST8 = "(25x3)(3x3)ABC(2x3)XY(5x2)PQRSTX(18x9)(3x2)TWO(5x7)SEVEN".splitlines() +def main() -> None: + solution.run(sys.argv) -def main() -> None: - my_aocd.print_header(2016, 9) - - assert part_1(TEST1) == 6 - assert part_1(TEST2) == 7 - assert part_1(TEST3) == 9 - assert part_1(TEST4) == 11 - assert part_1(TEST5) == 6 - assert part_1(TEST6) == 18 - assert part_2(TEST3) == 9 - assert part_2(TEST6) == 20 - assert part_2(TEST7) == 241920 - assert part_2(TEST8) == 445 - - inputs = my_aocd.get_input(2016, 9, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - - -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2016_12.py b/src/main/python/AoC2016_12.py index c03a0b20..6b05eee2 100644 --- a/src/main/python/AoC2016_12.py +++ b/src/main/python/AoC2016_12.py @@ -3,32 +3,70 @@ # Advent of Code 2016 Day 12 # -import aocd -from aoc import my_aocd -from aoc.vm import Program, VirtualMachine +import sys + from aoc.assembunny import Assembunny -from aoc.math import Fibonacci +from aoc.assembunny import AssembunnyInstruction +from aoc.common import InputData +from aoc.common import SolutionBase from aoc.common import log +from aoc.math import Fibonacci +from aoc.vm import Program +from aoc.vm import VirtualMachine + +TEST1 = """\ +cpy 41 a +inc a +inc a +dec a +jnz a 2 +dec a +""" +TEST2 = """\ +cpy 1 a +cpy 1 b +cpy 26 d +jnz c 2 +jnz 1 5 +cpy 7 c +inc d +dec c +jnz c -2 +cpy a c +inc a +dec b +jnz b -2 +cpy c b +dec d +jnz d -6 +cpy 16 c +cpy 12 d +inc a +dec d +jnz d -2 +dec c +jnz c -5 +""" # from u/blockingthesky @reddit -def _solve_reddit(inp: tuple[str], init_c: int) -> int: - reg = {'a': 0, 'b': 0, 'c': init_c, 'd': 0} +def _solve_reddit(inp: list[str], init_c: int) -> int: + reg = {"a": 0, "b": 0, "c": init_c, "d": 0} ind = 0 while ind < len(inp): - ins = inp[ind].split(' ') - if ins[0] == 'cpy': - if ins[1][0] in 'abcd': + ins = inp[ind].split(" ") + if ins[0] == "cpy": + if ins[1][0] in "abcd": reg[ins[2]] = reg[ins[1]] else: j = int(ins[1]) reg[ins[2]] = j - elif ins[0] == 'inc': + elif ins[0] == "inc": reg[ins[1]] += 1 - elif ins[0] == 'dec': + elif ins[0] == "dec": reg[ins[1]] -= 1 - if ins[0] == 'jnz': - if ins[1] in 'abcd': + if ins[0] == "jnz": + if ins[1] in "abcd": if reg[ins[1]] != 0: ind += int(ins[2]) else: @@ -40,89 +78,60 @@ def _solve_reddit(inp: tuple[str], init_c: int) -> int: ind += 1 else: ind += 1 - return reg['a'] + return reg["a"] -def _solve_vm(inputs: tuple[str], init_c: int) -> int: - inss = Assembunny.parse(inputs) - program = Program(Assembunny.translate(inss)) +def _solve_vm(instructions: list[AssembunnyInstruction], init_c: int) -> int: + program = Program(Assembunny.translate(instructions)) program.set_register_value("c", init_c) VirtualMachine().run_program(program) log(program.registers["a"]) return int(program.registers["a"]) -def _solve(inputs: tuple[str, ...], init_c: int) -> int: - values = list(map(lambda i: int(i), - filter(lambda i: i.isnumeric(), - map(lambda i: i.operands[0], - filter(lambda i: i.operation == "cpy", - Assembunny.parse(inputs)))))) - n = values[0] + values[1] + values[2] - n += 0 if init_c == 0 else 7 - return Fibonacci.binet(n) + values[4] * values[5] +Input = list[AssembunnyInstruction] +Output1 = int +Output2 = int -def part_1(inputs: tuple[str, ...]) -> int: - return _solve(inputs, 0) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return Assembunny.parse(tuple(input_data)) + def solve( + self, instructions: list[AssembunnyInstruction], init_c: int + ) -> int: + values = [ + int(i) + for i in [ + i.operands[0] for i in instructions if i.operation == "cpy" + ] + if i.isnumeric() + ] + n = values[0] + values[1] + values[2] + n += 0 if init_c == 0 else 7 + return Fibonacci.binet(n) + values[4] * values[5] -def part_2(inputs: tuple[str, ...]) -> int: - return _solve(inputs, 1) + def part_1(self, instructions: list[AssembunnyInstruction]) -> int: + return self.solve(instructions, 0) + def part_2(self, instructions: list[AssembunnyInstruction]) -> int: + return self.solve(instructions, 1) -TEST1 = '''\ -cpy 41 a -inc a -inc a -dec a -jnz a 2 -dec a -'''.splitlines() -TEST2 = '''\ -cpy 1 a -cpy 1 b -cpy 26 d -jnz c 2 -jnz 1 5 -cpy 7 c -inc d -dec c -jnz c -2 -cpy a c -inc a -dec b -jnz b -2 -cpy c b -dec d -jnz d -6 -cpy 16 c -cpy 12 d -inc a -dec d -jnz d -2 -dec c -jnz c -5 -'''.splitlines() + def samples(self) -> None: + assert self.solve(self.parse_input(TEST2.splitlines()), 0) == 318_003 + assert self.solve(self.parse_input(TEST2.splitlines()), 1) == 9_227_657 + assert _solve_reddit(TEST1.splitlines(), 0) == 42 + assert _solve_reddit(TEST2.splitlines(), 0) == 318_003 + assert _solve_reddit(TEST2.splitlines(), 1) == 9_227_657 -def main() -> None: - puzzle = aocd.models.Puzzle(2016, 12) - my_aocd.print_header(puzzle.year, puzzle.day) +solution = Solution(2016, 12) - assert _solve(TEST2, 0) == 318_003 # type:ignore[arg-type] - assert _solve(TEST2, 1) == 9_227_657 # type:ignore[arg-type] - assert _solve_reddit(TEST1, 0) == 42 # type:ignore[arg-type] - assert _solve_reddit(TEST2, 0) == 318_003 # type:ignore[arg-type] - assert _solve_reddit(TEST2, 1) == 9_227_657 # type:ignore[arg-type] - inputs = my_aocd.get_input_data(puzzle, 23) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2016_16.py b/src/main/python/AoC2016_16.py index f6381276..0b6c3ab2 100644 --- a/src/main/python/AoC2016_16.py +++ b/src/main/python/AoC2016_16.py @@ -3,52 +3,62 @@ # Advent of Code 2016 Day 16 # -from aoc import my_aocd +import sys +from typing import NamedTuple +from aoc.common import InputData +from aoc.common import SolutionBase -TABLE = str.maketrans('01', '10') +class DragonCurve(NamedTuple): + initial_state: str + table: dict[int, int] = str.maketrans("01", "10") -def _parse(inputs: tuple[str]) -> str: - assert len(inputs) == 1 - return inputs[0] + def dragon_curve(self, input_: str) -> str: + return input_ + "0" + input_[::-1].translate(self.table) + def checksum(self, data: list[str]) -> str: + pairs = [ + "1" if data[i] == data[i + 1] else "0" + for i in range(0, len(data) - 1, 2) + ] + return "".join(pairs) if len(pairs) % 2 != 0 else self.checksum(pairs) -def _dragon_curve(input_: str) -> str: - return input_ + "0" + input_[::-1].translate(TABLE) + def checksum_for_size(self, size: int) -> str: + data = self.initial_state + while len(data) < size: + data = self.dragon_curve(data) + return self.checksum(list(data[:size])) -def _checksum(data: list[str]) -> str: - pairs = ['1' if data[i] == data[i+1] else '0' - for i in range(0, len(data) - 1, 2)] - return pairs if len(pairs) % 2 != 0 else _checksum(pairs) +Input = DragonCurve +Output1 = str +Output2 = str -def _solve(data: str, size: int) -> str: - while len(data) < size: - data = _dragon_curve(data) - return "".join(_checksum(list(data[:size]))) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return DragonCurve(list(input_data)[0]) + def solve(self, dragon_curve: DragonCurve, size: int) -> str: + return dragon_curve.checksum_for_size(size) -def part_1(inputs: tuple[str]) -> str: - return _solve(_parse(inputs), 272) + def part_1(self, dragon_curve: DragonCurve) -> str: + return self.solve(dragon_curve, 272) + def part_2(self, dragon_curve: DragonCurve) -> str: + return self.solve(dragon_curve, 35651584) -def part_2(inputs: tuple[str]) -> str: - return _solve(_parse(inputs), 35651584) + def samples(self) -> None: + assert self.solve(self.parse_input(["10000"]), 20) == "01100" -def main() -> None: - my_aocd.print_header(2016, 16) +solution = Solution(2016, 16) - assert _solve("10000", 20) == "01100" - inputs = my_aocd.get_input(2016, 16, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2016_18.py b/src/main/python/AoC2016_18.py index c57b90d7..d16d6b68 100644 --- a/src/main/python/AoC2016_18.py +++ b/src/main/python/AoC2016_18.py @@ -2,64 +2,62 @@ # # Advent of Code 2016 Day 18 # -# TODO : try w/ numpy -from aoc import my_aocd +import sys -SAFE = '.' -TRAP = '^' -TRAPS = ('^^.', '.^^', '^..', '..^') +from aoc.common import InputData +from aoc.common import SolutionBase +SAFE = "." +TRAP = "^" +TRAPS = {"^^.", ".^^", "^..", "..^"} -def _parse(inputs: tuple[str]) -> str: - assert len(inputs) == 1 - return inputs[0] +Input = str +Output1 = int +Output2 = int -def _get_previous(s: str, i: int) -> str: - first = SAFE if i - 1 < 0 else s[i-1] - second = s[i] - third = SAFE if i + 1 == len(s) else s[i+1] - return first + second + third +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data)[0] -def _solve(first_row: str, rows: int) -> int: - width = len(first_row) - safe_count = first_row.count(SAFE) - prev_row = first_row - for _ in range(1, rows): - new_row = str() - for j in range(width): - prev = _get_previous(prev_row, j) - if prev in TRAPS: - new_row += TRAP - else: - new_row += SAFE - safe_count += 1 - prev_row = new_row - return safe_count + def solve(self, first_row: str, rows: int) -> int: + width = len(first_row) + safe_count = first_row.count(SAFE) + prev_row = [_ for _ in first_row] + for _ in range(1, rows): + new_row = [" "] * width + for j in range(width): + first = SAFE if j - 1 < 0 else prev_row[j - 1] + second = prev_row[j] + third = SAFE if j + 1 == width else prev_row[j + 1] + prev = first + second + third + if prev in TRAPS: + new_row[j] = TRAP + else: + new_row[j] = SAFE + safe_count += 1 + prev_row = new_row + return safe_count + def part_1(self, input: str) -> int: + return self.solve(input, 40) -def part_1(inputs: tuple[str]) -> int: - return _solve(_parse(inputs), 40) + def part_2(self, input: str) -> int: + return self.solve(input, 400_000) + def samples(self) -> None: + assert self.solve("..^^.", 3) == 6 + assert self.solve(".^^.^.^^^^", 10) == 38 -def part_2(inputs: tuple[str]) -> int: - return _solve(_parse(inputs), 400_000) +solution = Solution(2016, 18) -def main() -> None: - my_aocd.print_header(2016, 18) - - assert _solve("..^^.", 3) == 6 - assert _solve(".^^.^.^^^^", 10) == 38 - inputs = my_aocd.get_input(2016, 18, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2016_19.py b/src/main/python/AoC2016_19.py index 76aa662a..ce87a348 100644 --- a/src/main/python/AoC2016_19.py +++ b/src/main/python/AoC2016_19.py @@ -4,8 +4,13 @@ # from __future__ import annotations -import aocd -from aoc import my_aocd + +import sys +from dataclasses import dataclass + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples class Node: @@ -15,94 +20,89 @@ class Node: def __init__(self, value: int): self.value = value - self.prev = None - self.next = None - - -class DoublyLinkedList: - size: int - head: Node - tail: Node - - def __init__(self): - self.size = 0 - - def add_tail(self, value: int): - node = Node(value) - if self.size == 0: - self.tail = node - self.head = node - else: - self.tail.next = node - node.prev = self.tail - self.tail = node - self.size += 1 - - def close(self): - self.head.prev = self.tail - self.tail.next = self.head - - def remove(self, node: Node): + self.prev = None # type:ignore[assignment] + self.next = None # type:ignore[assignment] + + +@dataclass +class Elves: + head: Node = None # type:ignore[assignment] + size: int = 0 + + @classmethod + def from_input(cls, input: str) -> Elves: + elves = Elves() + prev: Node = None # type:ignore[assignment] + node: Node = None # type:ignore[assignment] + for i in range(int(input)): + node = Node(i + 1) + if elves.size == 0: + elves.head = node + else: + node.prev = prev + prev.next = node + prev = node + elves.size += 1 + elves.head.prev = node + node.next = elves.head + return elves + + def remove(self, node: Node) -> None: node.prev.next = node.next node.next.prev = node.prev self.size -= 1 -def _parse(inputs: tuple[str]) -> str: - assert len(inputs) == 1 - elves = DoublyLinkedList() - for i in range(int(inputs[0])): - elves.add_tail(i + 1) - elves.close() - return elves - - -def part_1(inputs: tuple[str]) -> int: - elves = _parse(inputs) - curr = elves.head - while elves.size > 1: - loser = curr.next - elves.remove(loser) - curr = curr.next - return curr.value - - -def part_2(inputs: tuple[str]) -> int: - elves = _parse(inputs) - curr = elves.head - opposite = elves.head - for i in range(elves.size // 2): - opposite = opposite.next - while elves.size > 1: - loser = opposite - elves.remove(loser) - if elves.size % 2: - opposite = opposite.next - else: - opposite = opposite.next.next - curr = curr.next - return curr.value +Input = str +Output1 = int +Output2 = int -TEST = '''\ -5 -'''.splitlines() +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data)[0] + def part_1(self, input: str) -> int: + elves = Elves.from_input(input) + curr = elves.head + while elves.size > 1: + loser = curr.next + elves.remove(loser) + curr = curr.next + return curr.value -def main() -> None: - puzzle = aocd.models.Puzzle(2016, 19) - my_aocd.print_header(puzzle.year, puzzle.day) + def part_2(self, input: str) -> int: + elves = Elves.from_input(input) + curr = elves.head + opposite = elves.head + for i in range(elves.size // 2): + opposite = opposite.next + while elves.size > 1: + loser = opposite + elves.remove(loser) + if elves.size % 2: + opposite = opposite.next + else: + opposite = opposite.next.next + curr = curr.next + return curr.value - assert part_1(TEST) == 3 - assert part_2(TEST) == 2 + @aoc_samples( + ( + ("part_1", "5", 3), + ("part_2", "5", 2), + ) + ) + def samples(self) -> None: + pass - inputs = my_aocd.get_input(puzzle.year, puzzle.day, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + +solution = Solution(2016, 19) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2016_22.py b/src/main/python/AoC2016_22.py index e2198d6f..41f8eb3f 100644 --- a/src/main/python/AoC2016_22.py +++ b/src/main/python/AoC2016_22.py @@ -4,14 +4,28 @@ # from __future__ import annotations + +import sys from typing import NamedTuple -import aocd -import re -from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry import Position -REGEX = r'^\/dev\/grid\/node-x([0-9]+)-y([0-9]+)' \ - + r'\s+[0-9]+T\s+([0-9]+)T\s+([0-9]+)T\s+[0-9]+%$' +TEST = """\ +root@ebhq-gridcenter# df -h +Filesystem Size Used Avail Use% +/dev/grid/node-x0-y0 10T 8T 2T 80% +/dev/grid/node-x0-y1 11T 6T 5T 54% +/dev/grid/node-x0-y2 32T 28T 4T 87% +/dev/grid/node-x1-y0 9T 7T 2T 77% +/dev/grid/node-x1-y1 8T 0T 8T 0% +/dev/grid/node-x1-y2 11T 7T 4T 63% +/dev/grid/node-x2-y0 10T 6T 4T 60% +/dev/grid/node-x2-y1 9T 8T 1T 88% +/dev/grid/node-x2-y2 9T 6T 3T 66% +""" class Node(NamedTuple): @@ -21,11 +35,13 @@ class Node(NamedTuple): free: int @classmethod - def of(cls, x: str, y: str, used: str, free: str) -> Node: - return Node(int(x), int(y), int(used), int(free)) + def from_input(cls, line: str) -> Node: + fs, size, used, avail, pct = line.split() + x, y = fs.split("-")[-2:] + return Node(int(x[1:]), int(y[1:]), int(used[:-1]), int(avail[:-1])) - def __eq__(self, other) -> bool: - if other is None: + def __eq__(self, other: object) -> bool: + if other is None or not type(other) is Node: return False return self.x == other.x and self.y == other.y @@ -33,51 +49,78 @@ def is_not_empty(self) -> bool: return self.used > 0 -def _parse(inputs: tuple[str]) -> list[Node]: - return [Node.of(*re.search(REGEX, i).groups()) for i in inputs[2:]] - - -def part_1(inputs: tuple[str]) -> str: - nodes = _parse(inputs) - return sum(1 - for b in nodes - for a in nodes if a.is_not_empty() - if a != b and a.used <= b.free) - - -def part_2(inputs: tuple[str]) -> str: - return 0 +class Cluster(NamedTuple): + nodes: list[Node] - -TEST = '''\ -root@ebhq-gridcenter# df -h -Filesystem Size Used Avail Use% -/dev/grid/node-x0-y0 10T 8T 2T 80% -/dev/grid/node-x0-y1 11T 6T 5T 54% -/dev/grid/node-x0-y2 32T 28T 4T 87% -/dev/grid/node-x1-y0 9T 7T 2T 77% -/dev/grid/node-x1-y1 8T 0T 8T 0% -/dev/grid/node-x1-y2 11T 7T 4T 63% -/dev/grid/node-x2-y0 10T 6T 4T 60% -/dev/grid/node-x2-y1 9T 8T 1T 88% -/dev/grid/node-x2-y2 9T 6T 3T 66% -'''.splitlines() + @classmethod + def from_input(cls, input: InputData) -> Cluster: + return Cluster([Node.from_input(line) for line in list(input)[2:]]) + + def get_unusable_nodes(self) -> set[Node]: + max_free = max(self.nodes, key=lambda n: n.free).free + return {n for n in self.nodes if n.used > max_free} + + def get_max_x(self) -> int: + return max(self.nodes, key=lambda n: n.x).x + + def get_empty_node(self) -> Node: + empty_nodes = [n for n in self.nodes if n.used == 0] + assert len(empty_nodes) == 1, "Expected 1 empty node" + return empty_nodes[0] + + def get_goal_node(self) -> Node: + return [n for n in self.nodes if n.x == self.get_max_x() and n.y == 0][ + 0 + ] + + +Input = Cluster +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, inputs: InputData) -> Input: + return Cluster.from_input(inputs) + + def part_1(self, cluster: Input) -> int: + return sum( + 1 + for b in cluster.nodes + for a in filter(lambda a: a.is_not_empty(), cluster.nodes) + if a != b and a.used <= b.free + ) + + def part_2(self, cluster: Input) -> int: + unusable = cluster.get_unusable_nodes() + hole_ys = {n.y for n in unusable} + assert len(hole_ys) == 1, "Expected all unusable nodes in 1 row" + hole_y = next(_ for _ in hole_ys) + if hole_y <= 1: + raise RuntimeError("Unsolvable") + assert not ( + max(unusable, key=lambda n: n.x).x != cluster.get_max_x() + ), "Expected unusable row to touch side" + hole_x = min(unusable, key=lambda n: n.x).x + hole = Position(hole_x - 1, hole_y) + empty_node = cluster.get_empty_node() + d1 = Position(empty_node.x, empty_node.y).manhattan_distance(hole) + goal_node = cluster.get_goal_node() + d2 = Position(goal_node.x - 1, goal_node.y).manhattan_distance(hole) + d3 = 5 * (goal_node.x - 1) + return d1 + d2 + d3 + 1 + + @aoc_samples((("part_1", TEST, 7),)) + def samples(self) -> None: + pass + + +solution = Solution(2016, 22) def main() -> None: - puzzle = aocd.models.Puzzle(2016, 22) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST) == 7 - assert part_2(TEST) == 0 - - inputs = my_aocd.get_input_data(puzzle, 1052) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2016_23.py b/src/main/python/AoC2016_23.py index 68696ba4..e61549c3 100644 --- a/src/main/python/AoC2016_23.py +++ b/src/main/python/AoC2016_23.py @@ -1,45 +1,20 @@ #! /usr/bin/env python3 # -# Advent of Code 2015 Day 23 +# Advent of Code 2016 Day 23 # import math -import aocd -from aoc import my_aocd -from aoc.vm import Program, VirtualMachine +import sys + from aoc.assembunny import Assembunny +from aoc.assembunny import AssembunnyInstruction +from aoc.common import InputData +from aoc.common import SolutionBase from aoc.common import log +from aoc.vm import Program +from aoc.vm import VirtualMachine - -def _solve_vm(inputs: tuple[str, ...], init_a: int) -> int: - inss = Assembunny.parse(inputs) - program = Program(Assembunny.translate(inss)) - log(program.instructions) - program.set_register_value("a", init_a) - VirtualMachine().run_program(program) - log(program.registers["a"]) - return int(program.registers["a"]) - - -def _solve(inputs: tuple[str, ...], init_a: int) -> int: - ops = {"cpy", "jnz"} - values = list(map(lambda i: int(i), - filter(lambda i: i.isnumeric(), - map(lambda i: i.operands[0], - filter(lambda i: i.operation in ops, - Assembunny.parse(inputs)))))) - return values[2] * values[3] + math.factorial(init_a) - - -def part_1(inputs: tuple[str, ...]) -> int: - return _solve(inputs, 7) - - -def part_2(inputs: tuple[str, ...]) -> int: - return _solve(inputs, 12) - - -TEST1 = '''\ +TEST1 = """\ cpy 2 a tgl a tgl a @@ -47,8 +22,8 @@ def part_2(inputs: tuple[str, ...]) -> int: cpy 1 a dec a dec a -'''.splitlines() -TEST2 = '''\ +""" +TEST2 = """\ cpy a b dec b cpy a d @@ -75,24 +50,61 @@ def part_2(inputs: tuple[str, ...]) -> int: jnz d -2 inc c jnz c -5 -'''.splitlines() +""" -def main() -> None: - puzzle = aocd.models.Puzzle(2016, 23) - my_aocd.print_header(puzzle.year, puzzle.day) +def _solve_vm(instructions: list[AssembunnyInstruction], init_a: int) -> int: + program = Program(Assembunny.translate(instructions)) + log(program.instructions) + program.set_register_value("a", init_a) + VirtualMachine().run_program(program) + log(program.registers["a"]) + return int(program.registers["a"]) + + +Input = list[AssembunnyInstruction] +Output1 = int +Output2 = int + - assert _solve(TEST2, 7) == 11_610 # type:ignore[arg-type] - assert _solve(TEST2, 12) == 479_008_170 # type:ignore[arg-type] - assert _solve_vm(TEST1, 7) == 3 # type:ignore[arg-type] +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return Assembunny.parse(tuple(input_data)) - inputs = my_aocd.get_input_data(puzzle, 26) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + def solve( + self, instructions: list[AssembunnyInstruction], init_a: int + ) -> int: + values = [ + int(i) + for i in [ + i.operands[0] + for i in instructions + if i.operation in {"cpy", "jnz"} + ] + if i.isnumeric() + ] + return values[2] * values[3] + math.factorial(init_a) + + def part_1(self, instructions: list[AssembunnyInstruction]) -> int: + return self.solve(instructions, 7) + + def part_2(self, instructions: list[AssembunnyInstruction]) -> int: + return self.solve(instructions, 12) + + def samples(self) -> None: + assert self.solve(self.parse_input(TEST2.splitlines()), 7) == 11_610 + assert ( + self.solve(self.parse_input(TEST2.splitlines()), 12) == 479_008_170 + ) + assert _solve_vm(self.parse_input(TEST1.splitlines()), 7) == 3 + + +solution = Solution(2016, 23) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2016_25.py b/src/main/python/AoC2016_25.py index 07e1968b..ac43792a 100644 --- a/src/main/python/AoC2016_25.py +++ b/src/main/python/AoC2016_25.py @@ -3,103 +3,63 @@ # Advent of Code 2015 Day 25 # -import aocd -from aoc import my_aocd -from aoc.vm import Program, VirtualMachine +import sys + from aoc.assembunny import Assembunny -from aoc.common import log - - -def _run_program(inputs: tuple[str], init_a: int) -> list[str]: - inss = Assembunny.parse(inputs) - output = list() - program = Program(Assembunny.translate(inss), - inf_loop_treshold=6_000, - output_consumer=lambda s: output.append(s)) - log(program.instructions) - program.set_register_value("a", init_a) - try: - VirtualMachine().run_program(program) - finally: - return output - - -def _solve_vm(inputs: tuple[str]) -> int: - n = 0 - while True: - output = _run_program(inputs, n) - ok = True - for i, v in enumerate(output): - ok = ok and (i % 2 == int(v)) - if ok: - return n - n += 1 - - -def _solve(inputs: tuple[str, ...]) -> int: - values = list(map(lambda i: int(i), - filter(lambda i: i.isnumeric(), - map(lambda i: i.operands[0], - filter(lambda i: i.operation == "cpy", - Assembunny.parse(inputs)))))) - return 2730 - values[0] * 182 - - -def part_1(inputs: tuple[str, ...]) -> int: - return _solve(inputs) - - -def part_2(inputs: tuple[str, ...]) -> None: - return None - - -TEST = '''\ -cpy a d -cpy 14 c -cpy 182 b -inc d -dec b -jnz b -2 -dec c -jnz c -5 -cpy d a -jnz 0 0 -cpy a b -cpy 0 a -cpy 2 c -jnz b 2 -jnz 1 6 -dec b -dec c -jnz c -4 -inc a -jnz 1 -7 -cpy 2 b -jnz c 2 -jnz 1 4 -dec b -dec c -jnz 1 -4 -jnz 0 0 -out b -jnz a -19 -jnz 1 -21 -'''.splitlines() +from aoc.assembunny import AssembunnyInstruction +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.vm import Program +from aoc.vm import VirtualMachine +Input = list[AssembunnyInstruction] +Output1 = int +Output2 = str -def main() -> None: - puzzle = aocd.models.Puzzle(2016, 25) - my_aocd.print_header(puzzle.year, puzzle.day) - assert _solve_vm(TEST) == 182 # type:ignore[arg-type] +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, inputs: InputData) -> Input: + return Assembunny.parse(tuple(inputs)) + + def solve_vm(self, instructions: Input) -> int: + vm_instructions = Assembunny.translate(instructions) + + def run_program(init_a: int) -> list[str]: + output = list() + program = Program( + vm_instructions, + inf_loop_treshold=6_000, + output_consumer=lambda s: output.append(s), + ) + program.set_register_value("a", init_a) + try: + VirtualMachine().run_program(program) + finally: + return output + + n = 150 + while True: + output = run_program(n) + if all(i % 2 == int(v) for i, v in enumerate(output)): + return n + n += 1 + + def part_1(self, instructions: Input) -> int: + return self.solve_vm(instructions) - inputs = my_aocd.get_input_data(puzzle, 30) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = None - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + def part_2(self, instructions: Input) -> str: + return "🎄" + + def samples(self) -> None: + pass + + +solution = Solution(2016, 25) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2017_01.py b/src/main/python/AoC2017_01.py index 1f5be810..b727ffc5 100644 --- a/src/main/python/AoC2017_01.py +++ b/src/main/python/AoC2017_01.py @@ -2,57 +2,59 @@ # # Advent of Code 2017 Day 1 # -from aoc import my_aocd -import aocd +import sys -def _sum_same_chars_at(string: str, distance: int) -> int: - test = string + string[:distance] - return sum([int(test[i]) - for i in range(len(string)) - if test[i] == test[i + distance]]) +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +Input = str +Output1 = int +Output2 = int -def part_1(inputs: tuple[str]) -> int: - return _sum_same_chars_at(inputs[0], 1) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data)[0] -def part_2(inputs: tuple[str]) -> int: - return _sum_same_chars_at(inputs[0], len(inputs[0]) // 2) + def sum_same_chars_at(self, string: str, distance: int) -> int: + test = string + string[:distance] + return sum( + int(test[i]) + for i in range(len(string)) + if test[i] == test[i + distance] + ) + def part_1(self, input: str) -> int: + return self.sum_same_chars_at(input, 1) -TEST1 = "1122".splitlines() -TEST2 = "1111".splitlines() -TEST3 = "1234".splitlines() -TEST4 = "91212129".splitlines() -TEST5 = "1212".splitlines() -TEST6 = "1221".splitlines() -TEST7 = "123425".splitlines() -TEST8 = "123123".splitlines() -TEST9 = "12131415".splitlines() + def part_2(self, input: str) -> int: + return self.sum_same_chars_at(input, len(input) // 2) + + @aoc_samples( + ( + ("part_1", "1122", 3), + ("part_1", "1111", 4), + ("part_1", "1234", 0), + ("part_1", "91212129", 9), + ("part_2", "1212", 6), + ("part_2", "1221", 0), + ("part_2", "123425", 4), + ("part_2", "123123", 12), + ("part_2", "12131415", 4), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2017, 1) def main() -> None: - puzzle = aocd.models.Puzzle(2017, 1) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST1) == 3 - assert part_1(TEST2) == 4 - assert part_1(TEST3) == 0 - assert part_1(TEST4) == 9 - assert part_2(TEST5) == 6 - assert part_2(TEST6) == 0 - assert part_2(TEST7) == 4 - assert part_2(TEST8) == 12 - assert part_2(TEST9) == 4 - - inputs = my_aocd.get_input(2017, 1, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) - - -if __name__ == '__main__': + solution.run(sys.argv) + + +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2017_04.py b/src/main/python/AoC2017_04.py index 3f0f7450..a6112fd8 100644 --- a/src/main/python/AoC2017_04.py +++ b/src/main/python/AoC2017_04.py @@ -2,60 +2,65 @@ # # Advent of Code 2017 Day 4 # + +import sys from collections import Counter from typing import Callable -from aoc import my_aocd -import aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples -def _parse(inputs: tuple[str]) -> int: - assert len(inputs) == 1 - return int(inputs[0]) +Input = list[str] +Output1 = int +Output2 = int -def _has_no_duplicate_words(string: str) -> bool: - return Counter(string.split()).most_common(1)[0][1] == 1 +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data) + def solve( + self, inputs: list[str], strategy: Callable[[list[str]], bool] + ) -> int: + return sum(strategy(line.split()) for line in inputs) -def _has_no_anagrams(string: str) -> bool: - words = string.split() - letter_counts = {tuple(sorted(dict(Counter(word)).items())) - for word in words} - return len(words) == len(letter_counts) + def part_1(self, inputs: list[str]) -> int: + def has_no_duplicate_words(words: list[str]) -> bool: + return Counter(words).most_common(1)[0][1] == 1 + return self.solve(inputs, has_no_duplicate_words) -def _solve(inputs: tuple[str], strategy: Callable) -> int: - return sum(1 for _ in inputs if strategy(_)) + def part_2(self, inputs: list[str]) -> int: + def has_no_anagrams(words: list[str]) -> bool: + return len(words) == len( + {tuple(sorted(Counter(word).items())) for word in words} + ) + return self.solve(inputs, has_no_anagrams) -def part_1(inputs: tuple[str]) -> int: - return _solve(inputs, _has_no_duplicate_words) + @aoc_samples( + ( + ("part_1", "aa bb cc dd ee", 1), + ("part_1", "aa bb cc dd aa", 0), + ("part_1", "aa bb cc dd aaa", 1), + ("part_2", "abcde fghij", 1), + ("part_2", "abcde xyz ecdab", 0), + ("part_2", "a ab abc abd abf abj", 1), + ("part_2", "iiii oiii ooii oooi oooo", 1), + ("part_2", "oiii ioii iioi iiio", 0), + ) + ) + def samples(self) -> None: + pass -def part_2(inputs: tuple[str]) -> int: - return _solve(inputs, _has_no_anagrams) +solution = Solution(2017, 4) def main() -> None: - puzzle = aocd.models.Puzzle(2017, 4) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(("aa bb cc dd ee",)) == 1 - assert part_1(("aa bb cc dd aa",)) == 0 - assert part_1(("aa bb cc dd aaa",)) == 1 - assert part_2(("abcde fghij",)) == 1 - assert part_2(("abcde xyz ecdab",)) == 0 - assert part_2(("a ab abc abd abf abj",)) == 1 - assert part_2(("iiii oiii ooii oooi oooo",)) == 1 - assert part_2(("oiii ioii iioi iiio",)) == 0 - - inputs = my_aocd.get_input(2017, 4, 512) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) - - -if __name__ == '__main__': + solution.run(sys.argv) + + +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2017_05.py b/src/main/python/AoC2017_05.py index 72b8fdec..90b832c1 100644 --- a/src/main/python/AoC2017_05.py +++ b/src/main/python/AoC2017_05.py @@ -2,33 +2,17 @@ # # Advent of Code 2017 Day 5 # -from typing import Callable -from aoc import my_aocd -import aocd - - -def _parse(inputs: tuple[str]) -> list[int]: - return [int(_) for _ in inputs] - -def _count_jumps(inputs: tuple[str], strategy: Callable) -> int: - offsets = _parse(inputs) - i = 0 - cnt = 0 - while i < len(offsets): - jump = offsets[i] - offsets[i] = strategy(jump) - i += jump - cnt += 1 - return cnt - - -def part_1(inputs: tuple[str]) -> int: - return _count_jumps(inputs, lambda j: j + 1) +import sys +from typing import Callable +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples -def part_2(inputs: tuple[str]) -> int: - return _count_jumps(inputs, lambda j: j - 1 if j >= 3 else j + 1) +Input = list[int] +Output1 = int +Output2 = int TEST = """\ @@ -37,23 +21,48 @@ def part_2(inputs: tuple[str]) -> int: 0 1 -3 -""".splitlines() +""" -def main() -> None: - puzzle = aocd.models.Puzzle(2017, 5) - my_aocd.print_header(puzzle.year, puzzle.day) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [int(_) for _ in input_data] - assert part_1(TEST) == 5 - assert part_2(TEST) == 10 + def count_jumps( + self, input: list[int], jump_calculator: Callable[[int], int] + ) -> int: + offsets = [_ for _ in input] + i = 0 + cnt = 0 + while i < len(offsets): + jump = offsets[i] + offsets[i] = jump_calculator(jump) + i += jump + cnt += 1 + return cnt - inputs = my_aocd.get_input(2017, 5, 1033) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + def part_1(self, input: Input) -> int: + return self.count_jumps(input, lambda j: j + 1) + + def part_2(self, input: Input) -> int: + return self.count_jumps(input, lambda j: j - 1 if j >= 3 else j + 1) + + @aoc_samples( + ( + ("part_1", TEST, 5), + ("part_2", TEST, 10), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2017, 5) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2017_15.py b/src/main/python/AoC2017_15.py index f29f47ac..9786d2aa 100644 --- a/src/main/python/AoC2017_15.py +++ b/src/main/python/AoC2017_15.py @@ -2,76 +2,111 @@ # # Advent of Code 2017 Day 15 # -from aoc import my_aocd -import aocd -from aoc.common import log +from __future__ import annotations + +import sys +from typing import Callable +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples FACTOR_A = 16807 FACTOR_B = 48271 MOD = 2147483647 +TEST = """\ +Generator A starts with 65 +Generator B starts with 8921 +""" -def _parse(inputs: tuple[str]) -> tuple[int]: - assert len(inputs) == 2 - return (int(inputs[i].split()[-1]) for i in (0, 1)) +class Generator: + def __init__( + self, seed: int, factor: int, condition: Callable[[int], bool] + ) -> None: + self.prev = seed + self.factor = factor + self.condition = condition -def _next(prev: int, factor: int, condition) -> int: - val = prev - while True: - val = (val * factor) % MOD - if condition(val): - return val + def __iter__(self) -> Generator: + return self + def __next__(self) -> int: + val = self.prev + while True: + val = (val * self.factor) % MOD + if self.condition(val): + self.prev = val + return val -def _solve(reps: int, start_a: int, start_b: int, - condition_a, condition_b) -> int: - cnt = 0 - prev_a = start_a - prev_b = start_b - for i in range(reps): - prev_a = _next(prev_a, FACTOR_A, condition_a) - prev_b = _next(prev_b, FACTOR_B, condition_b) - if i < 5: - log(f"{i}: {prev_a} | {prev_b}") - if (prev_a & 0xffff) == (prev_b & 0xffff): - cnt += 1 - return cnt +class Generators(NamedTuple): + a: int + b: int -def part_1(inputs: tuple[str]) -> int: - start_a, start_b = _parse(inputs) - return _solve(40_000_000, start_a, start_b, - lambda x: True, lambda x: True) + @classmethod + def from_input(cls, input: list[str]) -> Generators: + return Generators(*(int(input[i].split()[-1]) for i in (0, 1))) + def generator_a(self, condition: Callable[[int], bool]) -> Generator: + return Generator(self.a, FACTOR_A, condition) -def part_2(inputs: tuple[str]) -> int: - start_a, start_b = _parse(inputs) - return _solve(5_000_000, start_a, start_b, - lambda x: x % 4 == 0, lambda x: x % 8 == 0) + def generator_b(self, condition: Callable[[int], bool]) -> Generator: + return Generator(self.b, FACTOR_B, condition) -TEST = """\ -Generator A starts with 65 -Generator B starts with 8921 -""".splitlines() +Input = Generators +Output1 = int +Output2 = int -def main() -> None: - puzzle = aocd.models.Puzzle(2017, 15) - my_aocd.print_header(puzzle.year, puzzle.day) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return Generators.from_input(list(input_data)) + + def solve( + self, + generators: Generators, + reps: int, + condition_a: Callable[[int], bool], + condition_b: Callable[[int], bool], + ) -> int: + generator_a = generators.generator_a(condition_a) + generator_b = generators.generator_b(condition_b) + return sum( + next(generator_a) & 0xFFFF == next(generator_b) & 0xFFFF + for _ in range(reps) + ) + + def part_1(self, generators: Generators) -> int: + return self.solve( + generators, 40_000_000, lambda x: True, lambda x: True + ) + + def part_2(self, generators: Generators) -> int: + return self.solve( + generators, 5_000_000, lambda x: x % 4 == 0, lambda x: x % 8 == 0 + ) - assert part_1(TEST) == 588 - assert part_2(TEST) == 309 + @aoc_samples( + ( + ("part_1", TEST, 588), + ("part_2", TEST, 309), + ) + ) + def samples(self) -> None: + pass - inputs = my_aocd.get_input(2017, 15, 2) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + +solution = Solution(2017, 15) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2019_01.py b/src/main/python/AoC2019_01.py index 41bc2c03..a75e2a49 100644 --- a/src/main/python/AoC2019_01.py +++ b/src/main/python/AoC2019_01.py @@ -3,56 +3,63 @@ # Advent of Code 2019 Day 1 # -from aoc import my_aocd +import sys +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples -def _parse(inputs: tuple[str]) -> tuple[int]: - return (int(_) for _ in inputs) +TEST1 = "12" +TEST2 = "14" +TEST3 = "1969" +TEST4 = "100756" +Input = list[int] +Output1 = int +Output2 = int -def _fuel_for_mass(m: int) -> int: - return m // 3 - 2 +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [int(_) for _ in input_data] -def part_1(inputs: tuple[str]) -> int: - return sum(_fuel_for_mass(m) for m in _parse(inputs)) + def fuel_for_mass(self, m: int) -> int: + return m // 3 - 2 + def part_1(self, inputs: Input) -> int: + return sum(self.fuel_for_mass(m) for m in inputs) -def _rocket_equation(m: int) -> int: - total_fuel = 0 - while (fuel := _fuel_for_mass(m)) > 0: - total_fuel += fuel - m = fuel - return total_fuel + def rocket_equation(self, m: int) -> int: + total_fuel = 0 + while (fuel := self.fuel_for_mass(m)) > 0: + total_fuel += fuel + m = fuel + return total_fuel + def part_2(self, inputs: Input) -> int: + return sum(self.rocket_equation(m) for m in inputs) -def part_2(inputs: tuple[str]) -> int: - return sum(_rocket_equation(m) for m in _parse(inputs)) + @aoc_samples( + ( + ("part_1", TEST1, 2), + ("part_1", TEST2, 2), + ("part_1", TEST3, 654), + ("part_1", TEST4, 33583), + ("part_2", TEST1, 2), + ("part_2", TEST3, 966), + ("part_2", TEST4, 50346), + ) + ) + def samples(self) -> None: + pass -TEST1 = "12".splitlines() -TEST2 = "14".splitlines() -TEST3 = "1969".splitlines() -TEST4 = "100756".splitlines() +solution = Solution(2019, 1) def main() -> None: - my_aocd.print_header(2019, 1) + solution.run(sys.argv) - assert part_1(TEST1) == 2 - assert part_1(TEST2) == 2 - assert part_1(TEST3) == 654 - assert part_1(TEST4) == 33583 - assert part_2(TEST1) == 2 - assert part_2(TEST3) == 966 - assert part_2(TEST4) == 50346 - inputs = my_aocd.get_input(2019, 1, 100) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - - -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2019_02.py b/src/main/python/AoC2019_02.py index ebcf45c6..c9a81a61 100644 --- a/src/main/python/AoC2019_02.py +++ b/src/main/python/AoC2019_02.py @@ -4,49 +4,47 @@ # import itertools +import sys from copy import deepcopy -import aocd - -from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase from aoc.intcode import IntCode +Input = list[int] +Output1 = int +Output2 = int + -def _parse(inputs: tuple[str]) -> tuple[int]: - assert len(inputs) == 1 - return [int(_) for _ in inputs[0].split(",")] +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return IntCode.parse(list(input_data)) + def run_program(self, prog: list[int], noun: int, verb: int) -> int: + prog[1] = noun + prog[2] = verb + int_code = IntCode(prog) + int_code.run() + return int_code.get_program()[0] -def _run_prog(prog: list[int], noun: int, verb: int) -> int: - prog[1] = noun - prog[2] = verb - int_code = IntCode(prog) - int_code.run() - return int_code.get_program()[0] + def part_1(self, program: list[int]) -> int: + return self.run_program(program, 12, 2) + def part_2(self, program: list[int]) -> int: + for noun, verb in itertools.product(range(100), repeat=2): + if self.run_program(deepcopy(program), noun, verb) == 19_690_720: + return 100 * noun + verb + raise RuntimeError("Unsolved") -def part_1(inputs: tuple[str]) -> int: - return _run_prog(_parse(inputs), 12, 2) + def samples(self) -> None: + pass -def part_2(inputs: tuple[str]) -> int: - prog = _parse(inputs) - for noun, verb in itertools.product(range(100), repeat=2): - if _run_prog(deepcopy(prog), noun, verb) == 19_690_720: - return 100 * noun + verb - raise RuntimeError("Unsolved") +solution = Solution(2019, 2) def main() -> None: - puzzle = aocd.models.Puzzle(2019, 2) - my_aocd.print_header(puzzle.year, puzzle.day) - - inputs = my_aocd.get_input_data(puzzle, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) if __name__ == "__main__": diff --git a/src/main/python/AoC2019_04.py b/src/main/python/AoC2019_04.py index cc2dae8c..189bcd09 100644 --- a/src/main/python/AoC2019_04.py +++ b/src/main/python/AoC2019_04.py @@ -3,60 +3,59 @@ # Advent of Code 2019 Day 3 # -from typing import Callable +import sys from collections import Counter -from aoc import my_aocd - +from typing import Callable -def _parse(inputs: tuple[str]) -> (int, int): - return tuple(int(_) for _ in inputs[0].split("-")) +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.range import RangeInclusive +Input = RangeInclusive +Output1 = int +Output2 = int -def _does_not_decrease(passw: str) -> bool: - return list(passw) == sorted(passw) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + a, b = list(input_data)[0].split("-") + return RangeInclusive.between(int(a), int(b)) -def _is_valid_1(passw: int) -> bool: - passw = str(passw) - return _does_not_decrease(passw) and len(set(passw)) != len(passw) + def does_not_decrease(self, passw: str) -> bool: + return list(passw) == sorted(passw) + def is_valid_1(self, passw: str) -> bool: + return self.does_not_decrease(passw) and len(set(passw)) != len(passw) -def _is_valid_2(passw: int) -> bool: - passw = str(passw) - return _does_not_decrease(passw) and 2 in Counter(passw).values() + def is_valid_2(self, passw: str) -> bool: + return self.does_not_decrease(passw) and 2 in Counter(passw).values() + def count_valid(self, range: Input, check: Callable[[str], bool]) -> int: + return sum(check(str(i)) for i in range.iterator()) -def _count_valid(inputs: tuple[str], check: Callable) -> int: - min_, max_ = _parse(inputs) - return len([_ for _ in range(min_, max_ + 1) if check(_)]) + def part_1(self, range: Input) -> int: + return self.count_valid(range, self.is_valid_1) + def part_2(self, range: Input) -> int: + return self.count_valid(range, self.is_valid_2) -def part_1(inputs: tuple[str]) -> int: - return _count_valid(inputs, _is_valid_1) + def samples(self) -> None: + assert self.is_valid_1("122345") + assert self.is_valid_1("111123") + assert self.is_valid_1("111111") + assert not self.is_valid_1("223450") + assert not self.is_valid_1("123789") + assert self.is_valid_2("112233") + assert not self.is_valid_2("123444") + assert self.is_valid_2("111122") -def part_2(inputs: tuple[str]) -> int: - return _count_valid(inputs, _is_valid_2) +solution = Solution(2019, 4) def main() -> None: - my_aocd.print_header(2019, 4) - - assert _is_valid_1(122345) is True - assert _is_valid_1(111123) is True - assert _is_valid_1(111111) is True - assert _is_valid_1(223450) is False - assert _is_valid_1(123789) is False - assert _is_valid_2(112233) is True - assert _is_valid_2(123444) is False - assert _is_valid_2(111122) is True - - inputs = my_aocd.get_input(2019, 4, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2019_08.py b/src/main/python/AoC2019_08.py index c0e94cca..07d26c80 100644 --- a/src/main/python/AoC2019_08.py +++ b/src/main/python/AoC2019_08.py @@ -3,73 +3,61 @@ # Advent of Code 2019 Day 8 # -import aocd -from typing import Iterator +import sys +from typing import cast + from advent_of_code_ocr import convert_6 -from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase WIDTH = 25 HEIGHT = 6 +Input = str +Output1 = int +Output2 = str -def _parse(inputs: tuple[str, ...], width: int, height: int) -> Iterator[str]: - assert len(inputs) == 1 - layer_size = width * height - assert len(inputs[0]) % layer_size == 0 - return ( - inputs[0][i : i + layer_size] # noqa E203 - for i in range(0, len(inputs[0]), layer_size) - ) - - -def part_1(inputs: tuple[str, ...]) -> int: - layers = _parse(inputs, WIDTH, HEIGHT) - c = [(lyr.count("0"), lyr.count("1"), lyr.count("2")) for lyr in layers] - min0 = min(zeroes for zeroes, _, _ in c) - return [ones * twos for zeroes, ones, twos in c if zeroes == min0][0] - - -def _get_image(inputs: tuple[str, ...], width: int, height: int) -> str: - layers = list(_parse(inputs, width, height)) - image = "" - for i in range(0, len(layers[0])): - for lyr in layers: - if lyr[i] == "2": - continue - else: - break - image += lyr[i] - return image - - -def part_2(inputs: tuple[str, ...]) -> str: - image = _get_image(inputs, WIDTH, HEIGHT) - to_ocr = "\n".join( - [ - image[i : i + WIDTH] # noqa E203 - for i in range(0, WIDTH * HEIGHT, WIDTH) + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data)[0] + + def get_layers(self, input: str, width: int, height: int) -> list[str]: + layer_size = width * height + return [ + input[i : i + layer_size] # noqa E203 + for i in range(0, len(input), layer_size) ] - ) - return convert_6( # type:ignore[no-any-return] - to_ocr, fill_pixel="1", empty_pixel="0" - ) + def part_1(self, input: str) -> int: + layers = self.get_layers(input, WIDTH, HEIGHT) + least_zeroes = min(layers, key=lambda lyr: lyr.count("0")) + return least_zeroes.count("1") * least_zeroes.count("2") + + def get_image(self, input: str, width: int, height: int) -> str: + layers = self.get_layers(input, width, height) + return "".join( + next(lyr[i] for lyr in layers if lyr[i] != "2") + for i in range(0, len(layers[0])) + ) + + def part_2(self, input: str) -> str: + image = self.get_image(input, WIDTH, HEIGHT) + to_ocr = "\n".join( + image[i : i + WIDTH] # noqa E203 + for i in range(0, WIDTH * HEIGHT, WIDTH) + ) + return cast(str, convert_6(to_ocr, fill_pixel="1", empty_pixel="0")) -TEST = """0222112222120000""".splitlines() + def samples(self) -> None: + assert self.get_image("0222112222120000", 2, 2) == "0110" -def main() -> None: - puzzle = aocd.models.Puzzle(2019, 8) - my_aocd.print_header(puzzle.year, puzzle.day) +solution = Solution(2019, 8) - assert _get_image(TEST, 2, 2) == "0110" # type:ignore[arg-type] - inputs = my_aocd.get_input_data(puzzle, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) +def main() -> None: + solution.run(sys.argv) if __name__ == "__main__": diff --git a/src/main/python/AoC2019_10.py b/src/main/python/AoC2019_10.py new file mode 100644 index 00000000..631aeb71 --- /dev/null +++ b/src/main/python/AoC2019_10.py @@ -0,0 +1,188 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2019 Day 10 +# + +from __future__ import annotations + +import math +import sys +from functools import reduce +from typing import Iterator +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.grid import CharGrid + +Position = tuple[int, int] +ASTEROID = "#" + +TEST1 = """\ +.#..# +..... +##### +....# +...## +""" +TEST2 = """\ +......#.#. +#..#.#.... +..#######. +.#.#.###.. +.#..#..... +..#....#.# +#..#....#. +.##.#..### +##...#..#. +.#....#### +""" +TEST3 = """\ +#.#...#.#. +.###....#. +.#....#... +##.#.#.#.# +....#.#.#. +.##..###.# +..#...##.. +..##....## +......#... +.####.###. +""" +TEST4 = """\ +.#..#..### +####.###.# +....###.#. +..###.##.# +##.##.#.#. +....###..# +..#.#..#.# +#..#.#.### +.##...##.# +.....#.#.. +""" +TEST5 = """\ +.#..##.###...####### +##.############..##. +.#.######.########.# +.###.#######.####.#. +#####.##.#.##.###.## +..#####..#.######### +#################### +#.####....###.#.#.## +##.################# +#####.##.###..####.. +..######..##.####### +####.##.####...##..# +.#####..#.######.### +##...#.##########... +#.##########.####### +.####.#.###.###.#.## +....##.##.###..##### +.#.#.###########.### +#.#.#.#####.####.### +###.##.####.##.#..## +""" + + +def asteroids(grid: CharGrid) -> Iterator[Position]: + return ( + (c, r) + for r in range(grid.get_height()) + for c in range(grid.get_width()) + if grid.get_value_at(r, c) == ASTEROID + ) + + +class Asteroid(NamedTuple): + position: Position + others: dict[float, Position] + + @classmethod + def from_(cls, position: Position, grid: CharGrid) -> Asteroid: + return Asteroid(position, Asteroid.angles(grid, position)) + + @classmethod + def angles( + cls, grid: CharGrid, asteroid: Position + ) -> dict[float, Position]: + def merge( + d: dict[float, Position], other: Position + ) -> dict[float, Position]: + + def angle(asteroid: Position, other: Position) -> float: + angle = ( + math.atan2(other[1] - asteroid[1], other[0] - asteroid[0]) + + math.pi / 2 + ) + return angle + 2 * math.pi if angle < 0 else angle + + def distance_squared(a: Position, b: Position) -> int: + dx = a[0] - b[0] + dy = a[1] - b[1] + return dx**2 + dy**2 + + a = angle(asteroid, other) + d[a] = ( + min( + other, + d[a], + key=lambda x: distance_squared(x, asteroid), + ) + if a in d + else other + ) + return d + + return reduce( + merge, + filter(lambda x: x != asteroid, asteroids(grid)), + dict[float, Position](), + ) + + +Input = list[Asteroid] +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + grid = CharGrid.from_strings(list(input_data)) + return [Asteroid.from_(p, grid) for p in asteroids(grid)] + + def best(self, asteroids: list[Asteroid]) -> Asteroid: + return max(asteroids, key=lambda a: len(a.others)) + + def part_1(self, asteroids: Input) -> Output1: + return len(self.best(asteroids).others) + + def part_2(self, asteroids: Input) -> Output2: + d = self.best(asteroids).others + x, y = d[sorted(d.keys())[199]] + return x * 100 + y + + @aoc_samples( + ( + ("part_1", TEST1, 8), + ("part_1", TEST2, 33), + ("part_1", TEST3, 35), + ("part_1", TEST4, 41), + ("part_1", TEST5, 210), + ("part_2", TEST5, 802), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2019, 10) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2019_14.py b/src/main/python/AoC2019_14.py new file mode 100644 index 00000000..b19f633a --- /dev/null +++ b/src/main/python/AoC2019_14.py @@ -0,0 +1,185 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 1 +# + +from __future__ import annotations + +import math +import sys +from collections import defaultdict +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +TEST1 = """\ +10 ORE => 10 A +1 ORE => 1 B +7 A, 1 B => 1 C +7 A, 1 C => 1 D +7 A, 1 D => 1 E +7 A, 1 E => 1 FUEL +""" +TEST2 = """\ +9 ORE => 2 A +8 ORE => 3 B +7 ORE => 5 C +3 A, 4 B => 1 AB +5 B, 7 C => 1 BC +4 C, 1 A => 1 CA +2 AB, 3 BC, 4 CA => 1 FUEL +""" +TEST3 = """\ +157 ORE => 5 NZVS +165 ORE => 6 DCFZ +44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL +12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ +179 ORE => 7 PSHF +177 ORE => 5 HKGWZ +7 DCFZ, 7 PSHF => 2 XJWVT +165 ORE => 2 GPVTF +3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT +""" +TEST4 = """\ +2 VPVL, 7 FWMGM, 2 CXFTF, 11 MNCFX => 1 STKFG +17 NVRVD, 3 JNWZP => 8 VPVL +53 STKFG, 6 MNCFX, 46 VJHF, 81 HVMC, 68 CXFTF, 25 GNMV => 1 FUEL +22 VJHF, 37 MNCFX => 5 FWMGM +139 ORE => 4 NVRVD +144 ORE => 7 JNWZP +5 MNCFX, 7 RFSQX, 2 FWMGM, 2 VPVL, 19 CXFTF => 3 HVMC +5 VJHF, 7 MNCFX, 9 VPVL, 37 CXFTF => 6 GNMV +145 ORE => 6 MNCFX +1 NVRVD => 8 CXFTF +1 VJHF, 6 MNCFX => 4 RFSQX +176 ORE => 6 VJHF +""" +TEST5 = """\ +171 ORE => 8 CNZTR +7 ZLQW, 3 BMBT, 9 XCVML, 26 XMNCP, 1 WPTQ, 2 MZWV, 1 RJRHP => 4 PLWSL +114 ORE => 4 BHXH +14 VRPVC => 6 BMBT +6 BHXH, 18 KTJDG, 12 WPTQ, 7 PLWSL, 31 FHTLT, 37 ZDVW => 1 FUEL +6 WPTQ, 2 BMBT, 8 ZLQW, 18 KTJDG, 1 XMNCP, 6 MZWV, 1 RJRHP => 6 FHTLT +15 XDBXC, 2 LTCX, 1 VRPVC => 6 ZLQW +13 WPTQ, 10 LTCX, 3 RJRHP, 14 XMNCP, 2 MZWV, 1 ZLQW => 1 ZDVW +5 BMBT => 4 WPTQ +189 ORE => 9 KTJDG +1 MZWV, 17 XDBXC, 3 XCVML => 2 XMNCP +12 VRPVC, 27 CNZTR => 2 XDBXC +15 KTJDG, 12 BHXH => 5 XCVML +3 BHXH, 2 VRPVC => 7 MZWV +121 ORE => 7 VRPVC +7 XCVML => 6 RJRHP +5 BHXH, 4 VRPVC => 5 LTCX +""" + +FUEL = "FUEL" +ORE = "ORE" +ONE_TRILLION = 1_000_000_000_000 + + +class Material(NamedTuple): + name: str + amount: int + + @classmethod + def from_string(cls, string: str) -> Material: + amount, name = string.split() + return Material(name, int(amount)) + + +class Reaction(NamedTuple): + material: Material + reactants: set[Material] + + @classmethod + def from_string(cls, string: str) -> Reaction: + left, right = string.split(" => ") + reactants = {Material.from_string(s) for s in left.split(", ")} + return Reaction(Material.from_string(right), reactants) + + +Input = dict[str, Reaction] +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return { + r.material.name: r for r in map(Reaction.from_string, input_data) + } + + def ore_needed_for( + self, + material: str, + amount: int, + reactions: Input, + inventory: dict[str, int] = defaultdict[str, int](int), + ) -> int: + if material == ORE: + return amount + available = inventory[material] + if amount <= available: + inventory[material] = available - amount + return 0 + else: + inventory[material] = 0 + + reaction = reactions[material] + produced = reaction.material.amount + needed = amount - available + runs = math.ceil(float(needed) / produced) + if needed < produced * runs: + inventory[material] += produced * runs - needed + return sum( + self.ore_needed_for(r.name, r.amount * runs, reactions, inventory) + for r in reaction.reactants + ) + + def part_1(self, input: Input) -> Output1: + return self.ore_needed_for(FUEL, 1, input, defaultdict[str, int](int)) + + def part_2(self, input: Input) -> Output2: + part_1 = self.ore_needed_for(FUEL, 1, input) + lo = ONE_TRILLION // part_1 // 10 + hi = ONE_TRILLION // part_1 * 10 + while lo <= hi: + mid = (lo + hi) // 2 + ans = self.ore_needed_for(FUEL, mid, input) + if ans == ONE_TRILLION: + return mid + elif ans < ONE_TRILLION: + lo = mid + 1 + else: + hi = mid - 1 + return lo - 1 + + @aoc_samples( + ( + ("part_1", TEST1, 31), + ("part_1", TEST2, 165), + ("part_1", TEST3, 13312), + ("part_1", TEST4, 180697), + ("part_1", TEST5, 2210736), + ("part_2", TEST3, 82892753), + ("part_2", TEST4, 5586022), + ("part_2", TEST5, 460664), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2019, 14) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2019_17.py b/src/main/python/AoC2019_17.py new file mode 100644 index 00000000..29403bbe --- /dev/null +++ b/src/main/python/AoC2019_17.py @@ -0,0 +1,288 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2019 Day 17 +# + +from __future__ import annotations + +import sys +from typing import Callable +from typing import NamedTuple + +from aoc.collections import index_of_sublist +from aoc.collections import indexes_of_sublist +from aoc.collections import subtract_all +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import log +from aoc.geometry import Direction +from aoc.geometry import Turn +from aoc.grid import Cell +from aoc.intcode import IntCode + +NEWLINE = "\n" +SCAFFOLD = "#" +FUNCTIONS = ["A", "B", "C"] +Input = list[int] +Output1 = int +Output2 = int + +TEST = """\ +#######...##### +#.....#...#...# +#.....#...#...# +......#...#...# +......#...###.# +......#.....#.# +^########...#.# +......#.#...#.# +......######### +........#...#.. +....#########.. +....#...#...... +....#...#...... +....#...#...... +....#####...... +""" + + +class IntCodeComputer(NamedTuple): + program: list[int] + + def run_camera(self) -> list[int]: + intcode = IntCode(self.program) + out = list[int]() + intcode.run([], out) + return out + + def run_robot(self, commands: list[str]) -> list[int]: + intcode = IntCode([2] + self.program[1:]) + input = [ord(ch) for command in commands for ch in command + NEWLINE] + output = list[int]() + intcode.run(input, output) + return output + + +class CameraFeed(NamedTuple): + scaffolds: set[Cell] + robot_cell: Cell + robot_dir: Direction + + @classmethod + def ascii_to_strings(cls, ascii: list[int]) -> list[str]: + strings: list[str] = [] + sb: list[str] = [] + while not len(ascii) == 0: + o = chr(ascii.pop(0)) + if o == NEWLINE and len(sb) > 0: + strings.append("".join(sb)) + sb = [] + else: + sb.append(o) + return strings + + @classmethod + def from_ascii(cls, ascii: list[int]) -> CameraFeed: + strings = CameraFeed.ascii_to_strings(ascii) + return CameraFeed.from_strings(strings) + + @classmethod + def from_strings(cls, strings: list[str]) -> CameraFeed: + arrows = Direction.capital_arrows() + scaffolds = set[Cell]() + log((len(strings), len(strings[0]))) + for r in range(len(strings)): + for c in range(len(strings[0])): + val = strings[r][c] + if val in arrows: + robot_cell = Cell(r, c) + robot_dir = Direction.from_str(val) + elif val == SCAFFOLD: + scaffolds.add(Cell(r, c)) + return CameraFeed(scaffolds, robot_cell, robot_dir) + + +class Command(NamedTuple): + letter: str + cnt: int + + def __str__(self) -> str: + return self.letter if self.cnt == 1 else f"{self.letter}({self.cnt})" + + +class PathFinder: + def __init__(self, feed: CameraFeed) -> None: + self.feed = feed + + class State(NamedTuple): + prev: Cell + curr: Cell + + class DFS: + def __init__( + self, + scaffolds: set[Cell], + path: list[Cell], + start: Cell, + end: Cell, + ) -> None: + self.scaffolds = scaffolds + self.path = path + self.seen = set[PathFinder.State]() + self.is_end: Callable[[PathFinder.State], bool] = ( + lambda state: state.curr == end + ) + self.state = PathFinder.State(start, start) + self.path.append(start) + self.seen.add(self.state) + + def dfs(self) -> bool: + if self.is_end(self.state): + return True + sc = { + n + for n in self.state.curr.get_capital_neighbours() + if n in self.scaffolds + } + if len(sc) == 4: + adjacent = { + s + for s in sc + if s.row == self.state.prev.row + or s.col == self.state.prev.col + } + else: + adjacent = {_ for _ in sc} + for cell in adjacent: + new_state = PathFinder.State(self.state.curr, cell) + if new_state in self.seen: + continue + old_state = self.state + self.state = new_state + self.path.append(cell) + self.seen.add(self.state) + if self.dfs(): + return True + else: + self.path.pop() + self.seen.remove(self.state) + self.state = old_state + return False + + def find_path(self) -> list[Cell]: + start = self.feed.robot_cell + end = next( + cell + for cell in self.feed.scaffolds + if sum( + n in self.feed.scaffolds or n == self.feed.robot_cell + for n in cell.get_capital_neighbours() + ) + == 1 + ) + path = list[Cell]() + dfs = PathFinder.DFS(self.feed.scaffolds, path, start, end) + dfs.dfs() + return path + + def to_commands(self, moves: list[Direction]) -> list[Command]: + commands = list[list[Command]]() + curr = list[Command]() + for i in range(len(moves) - 1): + if moves[i] == moves[i + 1]: + curr.append(Command(curr[-1].letter, 1)) + else: + turn = Turn.from_directions(moves[i], moves[i + 1]) + if len(curr) > 0: + commands.append(curr[:]) + curr = [] + curr.append(Command(turn.letter, 1)) + commands.append(curr[:]) + return [Command(lst[0].letter, len(lst)) for lst in commands] + + def create_ascii_input(self, max_size: int, min_repeats: int) -> list[str]: + path = self.find_path() + # log(path) + moves = [self.feed.robot_dir] + [ + path[i].to(path[i + 1]) for i in range(len(path) - 1) + ] + # log(moves) + commands = self.to_commands(moves) + log(", ".join(str(c) for c in commands)) + lst = commands[:] + d = dict[str, list[Command]]() + for x in FUNCTIONS: + for i in range(max_size, 1, -1): + if i > len(lst): + continue + idxs = indexes_of_sublist(lst, lst[:i]) + if len(idxs) < min_repeats: + continue + else: + d[x] = lst[0:i][:] + lst = subtract_all(lst, lst[:i]) + break + # log(d) + main = list[str]() + lst = commands[:] + cnt = 0 + while len(lst) > 0: + if cnt >= 100: + raise RuntimeError("infinite loop") + for k, v in d.items(): + if index_of_sublist(lst, v) == 0: + main.append(k) + lst = lst[len(v) :] # noqa E203 + break + cnt += 1 + log(main) + ascii_input = ( + [",".join(main)] + + [ + ",".join(f"{c.letter},{c.cnt}" for c in d[x]) + for x in FUNCTIONS + ] + + ["n"] + ) + log(ascii_input) + return ascii_input + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [int(_) for _ in list(input_data)[0].split(",")] + + def part_1(self, program: Input) -> Output1: + feed = CameraFeed.from_ascii(IntCodeComputer(program).run_camera()) + return sum( + cell.row * cell.col + for cell in feed.scaffolds + if all(n in feed.scaffolds for n in cell.get_capital_neighbours()) + ) + + def part_2(self, program: Input) -> Output2: + computer = IntCodeComputer(program) + path_finder = PathFinder(CameraFeed.from_ascii(computer.run_camera())) + ascii_input = path_finder.create_ascii_input(max_size=5, min_repeats=3) + return computer.run_robot(ascii_input)[-1] + + def samples(self) -> None: + path_finder = PathFinder(CameraFeed.from_strings(TEST.splitlines())) + assert path_finder.create_ascii_input(max_size=3, min_repeats=2,) == [ + "A,B,C,B,A,C", + "R,8,R,8", + "R,4,R,4,R,8", + "L,6,L,2", + "n", + ] + + +solution = Solution(2019, 17) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2019_19.py b/src/main/python/AoC2019_19.py new file mode 100644 index 00000000..acbb7eb6 --- /dev/null +++ b/src/main/python/AoC2019_19.py @@ -0,0 +1,141 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2019 Day 19 +# + +from __future__ import annotations + +import sys +from typing import NamedTuple + +import aocd +from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import log +from aoc.intcode import IntCode + +Cell = tuple[int, int] + + +class Beam(NamedTuple): + program: list[int] + + @classmethod + def from_string(cls, string: str) -> Beam: + return Beam([int(_) for _ in string.split(",")]) + + def contains(self, cell: Cell) -> bool: + row, col = cell + out = list[int]() + IntCode(self.program).run([row, col], out) + return out[0] == 1 + + +Input = Beam +Output1 = int +Output2 = int + +TEST1 = """\ +3,20,3,21,2,21,22,24,1,24,20,24,9,24,204,25,99,0,0,0,0,0,10,10,\ +0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,\ +0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,\ +0,0,0,1,1,1,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,\ +0,0,0,1,1 +""" +TEST2 = """\ +3,20,3,21,2,21,22,24,1,24,20,24,9,24,204,25,99,0,0,0,0,0,40,35,\ +0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,\ +1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,\ +1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,\ +1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,\ +1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,\ +1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,\ +1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,\ +1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,\ +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,\ +1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,\ +1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,\ +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,\ +1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,\ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,\ +1,1,1,1,1,1 +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return Beam.from_string(list(input_data)[0]) + + def part_1(self, beam: Input) -> Output1: + return self.solve_1(beam, 50) + + def solve_1(self, beam: Input, size: int) -> int: + return sum( + beam.contains((r, c)) for c in range(size) for r in range(size) + ) + + def part_2(self, beam: Input) -> Output2: + return self.solve_2(beam, 100) + + def solve_2(self, beam: Input, size: int) -> int: + r, c = size - 1, 0 + while True: + while not beam.contains((r, c)): + c += 1 + if beam.contains((r - (size - 1), c + (size - 1))): + return (r - (size - 1)) * 10_000 + c + r += 1 + + def samples(self) -> None: + puzzle = aocd.models.Puzzle(self.year, self.day) + beam = self.parse_input(my_aocd.get_input_data(puzzle)) + for line in [ + "".join("#" if beam.contains((r, c)) else "." for c in range(50)) + for r in range(50) + ]: + log(line) + assert self.solve_1(self.parse_input(TEST1.splitlines()), 10) == 27 + assert self.solve_2(self.parse_input(TEST2.splitlines()), 10) == 250020 + + +solution = Solution(2019, 19) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2020_01.py b/src/main/python/AoC2020_01.py index d223f27f..b46a1a3a 100755 --- a/src/main/python/AoC2020_01.py +++ b/src/main/python/AoC2020_01.py @@ -2,103 +2,12 @@ # # Advent of Code 2020 Day 1 # -import time -from aoc import my_aocd -from aoc.common import log +import sys -def _parse(inputs: tuple[str]) -> tuple[int]: - return tuple(int(_) for _ in inputs) - - -def _print_elapsed(prefix: str, start: float, stop: float) -> None: - log(f"{prefix} Elapsed time: {stop-start:0.5f} seconds") - - -def _print_part_1(prefix: str, first: int, second: int, result: int) -> None: - log(f"{prefix} Twosome: {first} * {second} = {result}") - - -def _print_part_2(prefix: str, first: int, second: int, third: int, - result: int) -> None: - log(f"{prefix} Threesome: {first} * {second} * {third} = {result}") - - -def _squared(numbers: tuple[int], target: int) -> tuple[int]: - for i, n1 in enumerate(numbers): - for n2 in numbers[i:]: - if n1 + n2 == target: - return n1, n2 - - -def _seen_2(numbers: tuple[int], target: int) -> tuple[int]: - seen = set[int]() - for i, n1 in enumerate(numbers): - seen.add(n1) - n2 = target - n1 - if n2 in seen: - return n1, n2 - - -def _do_part_1(numbers: tuple[int], f, prefix: str) -> int: - start = time.perf_counter() - n1, n2 = f(numbers, 2020) - result = n1 * n2 - stop = time.perf_counter() - _print_part_1(prefix, n1, n2, result) - _print_elapsed(prefix, start, stop) - return result - - -def part_1_squared(inputs: tuple[str]) -> int: - return _do_part_1(_parse(inputs), _squared, "[part_1_squared]") - - -def part_1_seen(inputs: tuple[str]) -> int: - return _do_part_1(_parse(inputs), _seen_2, "[part_1_seen]") - - -def _cubed(numbers: tuple[int], target: int) -> tuple[int]: - for i, n1 in enumerate(numbers): - if (twosome := _squared(numbers[i:], target - n1)) is not None: - return n1, twosome[0], twosome[1] - - -def _seen_3(numbers: tuple[int], target: int) -> tuple[int]: - seen = set[int]() - for i, n1 in enumerate(numbers): - seen.add(n1) - for n2 in numbers[i:]: - n3 = 2020 - n1 - n2 - if n3 in seen: - return n1, n2, n3 - - -def _do_part_2(numbers: tuple[int], f, prefix: str) -> int: - start = time.perf_counter() - n1, n2, n3 = f(numbers, 2020) - result = n1 * n2 * n3 - stop = time.perf_counter() - _print_part_2("[part_2_cubed]", n1, n2, n3, result) - _print_elapsed("[part_2_cubed]", start, stop) - return result - - -def part_2_cubed(inputs: tuple[str]) -> int: - return _do_part_2(_parse(inputs), _cubed, "[part_2_cubed]") - - -def part_2_seen(inputs: tuple[str]) -> int: - return _do_part_2(_parse(inputs), _seen_3, "[part_2_seen]") - - -def part_1(inputs: tuple[str]) -> int: - return part_1_seen(inputs) - - -def part_2(inputs: tuple[str]) -> int: - return part_2_seen(inputs) - +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples TEST = """\ 1721 @@ -107,23 +16,52 @@ def part_2(inputs: tuple[str]) -> int: 299 675 1456 -""".splitlines() +""" +Input = list[int] +Output1 = int +Output2 = int -def main() -> None: - my_aocd.print_header(2020, 1) - assert part_1_squared(TEST) == 514579 - assert part_2_cubed(TEST) == 241861950 - assert part_1_seen(TEST) == 514579 - assert part_2_seen(TEST) == 241861950 +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [int(_) for _ in input_data] + + def part_1(self, numbers: Input) -> int: + seen = set[int]() + for n1 in numbers: + seen.add(n1) + n2 = 2020 - n1 + if n2 in seen: + return n1 * n2 + raise RuntimeError() + + def part_2(self, numbers: Input) -> int: + seen = set[int]() + for i, n1 in enumerate(numbers): + seen.add(n1) + for n2 in numbers[i:]: + n3 = 2020 - n1 - n2 + if n3 in seen: + return n1 * n2 * n3 + raise RuntimeError() + + @aoc_samples( + ( + ("part_1", TEST, 514_579), + ("part_2", TEST, 241_861_950), + ) + ) + def samples(self) -> None: + pass - inputs = my_aocd.get_input(2020, 1, 200) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + +solution = Solution(2020, 1) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2020_02.py b/src/main/python/AoC2020_02.py index 69359e31..076e0fb6 100755 --- a/src/main/python/AoC2020_02.py +++ b/src/main/python/AoC2020_02.py @@ -2,61 +2,68 @@ # # Advent of Code 2020 Day 2 # -import re -from operator import xor -from typing import Callable -import aocd +import sys -from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +TEST = """\ +1-3 a: abcde +2-9 c: ccccccccc +1-3 b: cdefg +""" -def _solve( - inputs: tuple[str], check_valid: Callable[[int, int, str, str], bool] -) -> str: - def _parse(line: str) -> (int, int, str, str): - m = re.search(r"^(\d{1,2})-(\d{1,2}) ([a-z]{1}): ([a-z]+)$", line) - return (int(m.group(1)), int(m.group(2)), m.group(3), m.group(4)) - return str(sum(1 for line in inputs if check_valid(*_parse(line)))) +Input = list[tuple[int, int, str, str]] +Output1 = int +Output2 = int -def part_1(inputs: tuple[str]) -> str: - def check_valid(first: int, second: int, wanted: str, passw: str): - return passw.count(wanted) in range(first, second + 1) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + def parse(line: str) -> tuple[int, int, str, str]: + splits = line.split(": ") + left_and_right = splits[0].split(" ") + first, second = left_and_right[0].split("-") + wanted = left_and_right[1][0] + password = splits[1] + return (int(first), int(second), wanted, password) - return _solve(inputs, check_valid) + return [parse(line) for line in input_data] + def part_1(self, inputs: Input) -> int: + def check_valid( + first: int, second: int, wanted: str, passw: str + ) -> bool: + return first <= passw.count(wanted) <= second -def part_2(inputs: tuple[str]) -> str: - def check_valid(first: int, second: int, wanted: str, passw: str): - first_matched = passw[first - 1] == wanted - second_matched = passw[second - 1] == wanted - return xor(first_matched, second_matched) + return sum(check_valid(*line) for line in inputs) - return _solve(inputs, check_valid) + def part_2(self, inputs: Input) -> int: + def check_valid( + first: int, second: int, wanted: str, passw: str + ) -> bool: + return (passw[first - 1] == wanted) ^ (passw[second - 1] == wanted) + return sum(check_valid(*line) for line in inputs) -TEST = """\ -1-3 a: abcde -2-9 c: ccccccccc -1-3 b: cdefg -""".splitlines() + @aoc_samples( + ( + ("part_1", TEST, 2), + ("part_2", TEST, 1), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2020, 2) def main() -> None: - puzzle = aocd.models.Puzzle(2020, 2) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST) == 2 - assert part_2(TEST) == 1 - - inputs = my_aocd.get_input_data(puzzle, 1000) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) if __name__ == "__main__": diff --git a/src/main/python/AoC2020_07.py b/src/main/python/AoC2020_07.py index 99b49061..ffc68c8f 100755 --- a/src/main/python/AoC2020_07.py +++ b/src/main/python/AoC2020_07.py @@ -2,72 +2,16 @@ # # Advent of Code 2020 Day 7 # -from typing import NamedTuple -from functools import reduce, cache -from aoc import my_aocd -from aoc.common import log - -SHINY_GOLD = "shiny gold" - - -def _parse_line(line: str) -> tuple[str, set[tuple[int, str]]]: - sp = line.split(" contain ") - source = sp[0].split(" bags")[0] - right = sp[1].split(", ") - destinations = set[tuple[int, str]]() - for right_part in right: - if right_part != "no other bags.": - contained = right_part.split() - destinations.add((int(contained[0]), - contained[1] + " " + contained[2])) - result = (source, destinations) - log(f"{source} -> {destinations}") - return result - - -class Edge(NamedTuple): - src: str - dst: str - cnt: int - - -def _build_edges(inputs: tuple[str]) -> frozenset[Edge]: - parsed_lines = [_parse_line(line) for line in inputs] - log(parsed_lines) - return frozenset({Edge(parsed_line[0], dst[1], dst[0]) - for parsed_line in parsed_lines - for dst in parsed_line[1]}) - - -def part_1(inputs: tuple[str]) -> int: - @cache - def exists_path(src: str, dst: str) -> bool: - return reduce( - lambda x, y: x or y, - map(lambda edge: edge.dst == dst or exists_path(edge.dst, dst), - [edge for edge in edges if edge.src == src]), - False) - - edges = _build_edges(inputs) - return len({edge.src - for edge in edges - if edge.src != SHINY_GOLD - and exists_path(edge.src, SHINY_GOLD)}) - -def part_2(inputs: tuple[str]) -> int: - @cache - def count_contained(src: str) -> int: - return reduce( - lambda x, y: x + y, - map(lambda edge: edge.cnt + edge.cnt * count_contained(edge.dst), - [edge for edge in edges if edge.src == src]), - 0) - - edges = _build_edges(inputs) - return count_contained(SHINY_GOLD) +import sys +from functools import cache +from typing import NamedTuple +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +SHINY_GOLD = "shiny gold" TEST1 = """\ light red bags contain 1 bright white bag, 2 muted yellow bags. dark orange bags contain 3 bright white bags, 4 muted yellow bags. @@ -78,7 +22,7 @@ def count_contained(src: str) -> int: vibrant plum bags contain 5 faded blue bags, 6 dotted black bags. faded blue bags contain no other bags. dotted black bags contain no other bags. -""".splitlines() +""" TEST2 = """\ shiny gold bags contain 2 dark red bags. dark red bags contain 2 dark orange bags. @@ -87,22 +31,84 @@ def count_contained(src: str) -> int: dark green bags contain 2 dark blue bags. dark blue bags contain 2 dark violet bags. dark violet bags contain no other bags. -""".splitlines() +""" -def main() -> None: - my_aocd.print_header(2020, 7) +class Edge(NamedTuple): + src: str + dst: str + cnt: int - assert part_1(TEST1) == 4 - assert part_2(TEST1) == 32 - assert part_2(TEST2) == 126 - inputs = my_aocd.get_input(2020, 7, 594) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") +Input = set[Edge] +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + def parse_line(line: str) -> tuple[str, set[tuple[int, str]]]: + sp = line.split(" contain ") + source = sp[0].split(" bags")[0] + right = sp[1].split(", ") + destinations = { + (int(count), adj + " " + color) + for count, adj, color, _ in ( + r.split() for r in right if r != "no other bags." + ) + } + return source, destinations + + return { + Edge(source, dst, cnt) + for source, destinations in ( + parse_line(line) for line in input_data + ) + for cnt, dst in destinations + } + + def part_1(self, edges: Input) -> int: + @cache + def exists_path(src: str, dst: str) -> bool: + return any( + edge.dst == dst or exists_path(edge.dst, dst) + for edge in edges + if edge.src == src + ) + + return sum( + src != SHINY_GOLD and exists_path(src, SHINY_GOLD) + for src in {edge.src for edge in edges} + ) + + def part_2(self, edges: Input) -> int: + @cache + def count_contained(src: str) -> int: + return sum( + edge.cnt + edge.cnt * count_contained(edge.dst) + for edge in edges + if edge.src == src + ) + + return count_contained(SHINY_GOLD) + + @aoc_samples( + ( + ("part_1", TEST1, 4), + ("part_2", TEST1, 32), + ("part_2", TEST2, 126), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2020, 7) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2020_09.py b/src/main/python/AoC2020_09.py index cf8db744..566528be 100755 --- a/src/main/python/AoC2020_09.py +++ b/src/main/python/AoC2020_09.py @@ -2,73 +2,11 @@ # # Advent of Code 2020 Day 9 # -from aoc import my_aocd -from aoc.common import log +import sys -def _parse(inputs: tuple[str]) -> tuple[int]: - return tuple([int(_) for _ in inputs]) - - -def find_two_summands(numbers: list[int], sum_: int): - for n1 in numbers: - n2 = sum_ - n1 - if n2 in numbers: - return (n1, n2) - return None - - -def _do_part_1(inputs: tuple[str], window_size: int) -> int: - inputs = _parse(inputs) - log(inputs) - _range = range(window_size, len(inputs)) - for i in _range: - n = inputs[i] - search_window = inputs[i-window_size:i] - log(search_window) - if find_two_summands(search_window, sum_=n) is None: - return n - raise ValueError("Unsolvable") - - -def part_1(inputs: tuple[str]) -> int: - return _do_part_1(inputs, window_size=25) - - -def _sublists(inputs: tuple[int], min_size: int) -> [[int]]: - sublists = [[]] - for i in range(len(inputs) + 1): - for j in range(i + 1, len(inputs) + 1): - if j-i < min_size: - continue - sli = inputs[i:j] - sublists.append(sli) - return sublists - - -def _collect_all_sublists_before_target_with_minimum_size_2( - inputs: tuple[int], target: int) -> [[]]: - return _sublists(inputs[:target], min_size=2) - - -def _do_part_2(inputs: tuple[str], window_size: int) -> int: - inputs = _parse(inputs) - log(inputs) - target = _do_part_1(inputs, window_size) - target_pos = inputs.index(target) - log(target_pos) - sublists = _collect_all_sublists_before_target_with_minimum_size_2( - inputs, target_pos) - ss = [s for s in sublists if sum(s) == target] - log(f"Found: {ss}") - if len(ss) == 1: - return min(ss[0])+max(ss[0]) - raise ValueError("Unsolvable") - - -def part_2(inputs: tuple[str]) -> int: - return _do_part_2(inputs, window_size=25) - +from aoc.common import InputData +from aoc.common import SolutionBase TEST = """\ 35 @@ -91,21 +29,57 @@ def part_2(inputs: tuple[str]) -> int: 277 309 576 -""".splitlines() +""" +Input = list[int] +Output1 = int +Output2 = int -def main() -> None: - my_aocd.print_header(2020, 9) - assert _do_part_1(TEST, 5) == 127 - assert _do_part_2(TEST, 5) == 62 +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [int(_) for _ in input_data] + + def solve_1(self, numbers: list[int], window_size: int) -> int: + return next( + i + for i in range(window_size, len(numbers)) + if not any( + numbers[i] - n in numbers[i - window_size : i] # noqa E203 + for n in numbers[i - window_size : i] # noqa E203 + ) + ) + + def part_1(self, numbers: list[int]) -> int: + invalid_idx = self.solve_1(numbers, window_size=25) + return numbers[invalid_idx] + + def solve_2(self, numbers: list[int], window_size: int) -> int: + invalid_idx = self.solve_1(numbers, window_size) + sublists = ( + numbers[i:j] + for i in range(invalid_idx + 1) + for j in range(i + 1, invalid_idx + 1) + if j - i >= 2 + ) + return next( + min(s) + max(s) for s in sublists if sum(s) == numbers[invalid_idx] + ) - inputs = my_aocd.get_input(2020, 9, 1000) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + def part_2(self, numbers: list[int]) -> int: + return self.solve_2(numbers, window_size=25) + + def samples(self) -> None: + assert self.solve_1(self.parse_input(TEST.splitlines()), 5) == 14 + assert self.solve_2(self.parse_input(TEST.splitlines()), 5) == 62 + + +solution = Solution(2020, 9) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2020_10.py b/src/main/python/AoC2020_10.py index 36f2d8b9..580d3ce7 100755 --- a/src/main/python/AoC2020_10.py +++ b/src/main/python/AoC2020_10.py @@ -2,41 +2,14 @@ # # Advent of Code 2020 Day 10 # -from collections import defaultdict, Counter -from aoc import my_aocd -from aoc.common import log +import sys +from collections import Counter +from collections import defaultdict -def _parse(inputs: tuple[str]) -> tuple[int]: - sorted_ = [int(_) for _ in inputs] - sorted_.append(0) - sorted_.append(max(sorted_) + 3) - sorted_.sort() - return tuple(sorted_) - - -def part_1(inputs: tuple[str]) -> int: - inputs = _parse(inputs) - log(inputs) - cnt = Counter((inputs[i] - inputs[i-1] - for i in range(1, len(inputs)))) - return cnt[1] * cnt[3] - - -def part_2(inputs: tuple[str]) -> int: - inputs = _parse(inputs) - log(inputs) - seen = defaultdict(lambda: 0) - seen[0] = 1 - for i in inputs[1:]: - for j in (inputs[k] - for k in range(inputs.index(i) - 1, -1, -1) - if i - inputs[k] <= 3): - seen[i] += seen[j] - log(seen) - log(seen[inputs[-1]]) - return seen[inputs[-1]] - +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples TEST1 = """\ 16 @@ -50,7 +23,7 @@ def part_2(inputs: tuple[str]) -> int: 6 12 4 -""".splitlines() +""" TEST2 = """\ 28 33 @@ -83,23 +56,53 @@ def part_2(inputs: tuple[str]) -> int: 34 10 3 -""".splitlines() +""" +Input = list[int] +Output1 = int +Output2 = int -def main() -> None: - my_aocd.print_header(2020, 10) - assert part_1(TEST1) == 35 - assert part_1(TEST2) == 220 - assert part_2(TEST1) == 8 - assert part_2(TEST2) == 19208 +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + numbers = [0] + sorted(int(_) for _ in input_data) + numbers.append(numbers[-1] + 3) + return numbers + + def part_1(self, numbers: Input) -> int: + cnt = Counter( + numbers[i] - numbers[i - 1] for i in range(1, len(numbers)) + ) + return cnt[1] * cnt[3] - inputs = my_aocd.get_input(2020, 10, 101) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + def part_2(self, numbers: Input) -> int: + seen = defaultdict(lambda: 0) + seen[0] = 1 + for i, n in enumerate(numbers[1:]): + for j in ( + numbers[k] for k in range(i, -1, -1) if n - numbers[k] <= 3 + ): + seen[n] += seen[j] + return seen[numbers[-1]] + + @aoc_samples( + ( + ("part_1", TEST1, 35), + ("part_1", TEST2, 220), + ("part_2", TEST1, 8), + ("part_2", TEST2, 19208), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2020, 10) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2020_13.py b/src/main/python/AoC2020_13.py index 381fc588..6a7363a3 100644 --- a/src/main/python/AoC2020_13.py +++ b/src/main/python/AoC2020_13.py @@ -3,94 +3,93 @@ # Advent of Code 2020 Day 13 # -from aoc import my_aocd - - -def parse(inputs: tuple[str]): - assert len(inputs) == 2 - buses = [] - for b in inputs[1].split(","): - if b == "x": - buses.append(-1) - else: - buses.append(int(b)) - return int(inputs[0]), buses - - -def part_1(inputs: tuple[int]) -> int: - target, buses = parse(inputs) - cnt = 0 - while True: - t = target + cnt - for b in buses: - if b == -1: - continue - if t % b == 0: - return b * cnt - cnt += 1 - - -def part_2(inputs: tuple[int]) -> int: - _, buses = parse(inputs) - bs = {buses.index(b): b for b in buses if b != -1} - idxs = [i for i in bs] - r = 0 - lcm = 1 - for i in range(len(idxs) - 1): - cur = idxs[i] - nxt = idxs[i+1] - lcm *= bs[cur] - offset = nxt - while True: - r += lcm - if (r + offset) % bs[nxt] == 0: - break - return r +import itertools +import sys +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples TEST1 = """\ 939 7,13,x,x,59,x,31,19 -""".splitlines() +""" TEST2 = """\ 0 17,x,13,19 -""".splitlines() +""" TEST3 = """\ 0 67,7,59,61 -""".splitlines() +""" TEST4 = """\ 0 67,x,7,59,61 -""".splitlines() +""" TEST5 = """\ 0 67,7,x,59,61 -""".splitlines() +""" TEST6 = """\ 0 1789,37,47,1889 -""".splitlines() +""" +Input = tuple[int, list[tuple[int, int]]] +Output1 = int +Output2 = int -def main() -> None: - my_aocd.print_header(2020, 13) - assert part_1(TEST1) == 295 - assert part_2(TEST1) == 1068781 - assert part_2(TEST2) == 3417 - assert part_2(TEST3) == 754018 - assert part_2(TEST4) == 779210 - assert part_2(TEST5) == 1261476 - assert part_2(TEST6) == 1202161486 +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + inputs = list(input_data) + buses = [ + (int(period), offset) + for offset, period in enumerate(inputs[1].split(",")) + if period != "x" + ] + return int(inputs[0]), buses - inputs = my_aocd.get_input(2020, 13, 2) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + def part_1(self, input: Input) -> int: + target, buses = input + return next( + period * cnt + for cnt in itertools.count(0) + for period, _ in buses + if (target + cnt) % period == 0 + ) + + def part_2(self, input: Input) -> int: + _, buses = input + r = 0 + lcm = buses[0][0] + for period, offset in buses[1:]: + while (r + offset) % period != 0: + r += lcm + lcm *= period + return r + + @aoc_samples( + ( + ("part_1", TEST1, 295), + ("part_2", TEST1, 1068781), + ("part_2", TEST2, 3417), + ("part_2", TEST3, 754018), + ("part_2", TEST4, 779210), + ("part_2", TEST5, 1261476), + ("part_2", TEST6, 1202161486), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2020, 13) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2020_17.py b/src/main/python/AoC2020_17.py index 1124d5de..3c786559 100644 --- a/src/main/python/AoC2020_17.py +++ b/src/main/python/AoC2020_17.py @@ -3,58 +3,68 @@ # Advent of Code 2020 Day 17 # -from aoc import my_aocd -from aoc.game_of_life import GameOfLife, InfiniteGrid, ClassicRules -import aocd +import sys +from typing import Callable -ON = "#" -GENERATIONS = 6 +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.game_of_life import ClassicRules +from aoc.game_of_life import GameOfLife +from aoc.game_of_life import InfiniteGrid +TEST = """\ +.#. +..# +### +""" -def _parse(inputs: tuple[str, ...], dim: int) -> GameOfLife: - alive = {(r, c, 0, 0) - for r, row in enumerate(inputs) - for c, state in enumerate(row) - if state == ON} - return GameOfLife(alive, InfiniteGrid(dim), ClassicRules()) +ON = "#" +GENERATIONS = 6 +CellFactory = Callable[[int, int], tuple[int, ...]] +Input = InputData +Output1 = int +Output2 = int -def _solve(inputs: tuple[str, ...], dim: int) -> int: - game_of_life = _parse(inputs, dim) - for i in range(GENERATIONS): - game_of_life.next_generation() - return sum(1 for _ in game_of_life.alive) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return input_data + def solve(self, inputs: Input, cell_factory: CellFactory) -> int: + alive = { + cell_factory(r, c) + for r, row in enumerate(inputs) + for c, state in enumerate(row) + if state == ON + } + game_of_life = GameOfLife(alive, InfiniteGrid(), ClassicRules()) + for i in range(GENERATIONS): + game_of_life.next_generation() + return sum(1 for _ in game_of_life.alive) -def part_1(inputs: tuple[str, ...]) -> int: - return _solve(inputs, 3) + def part_1(self, inputs: Input) -> int: + return self.solve(inputs, cell_factory=lambda r, c: (0, r, c)) + def part_2(self, inputs: Input) -> int: + return self.solve(inputs, cell_factory=lambda r, c: (0, 0, r, c)) -def part_2(inputs: tuple[str, ...]) -> int: - return _solve(inputs, 4) + @aoc_samples( + ( + ("part_1", TEST, 112), + ("part_2", TEST, 848), + ) + ) + def samples(self) -> None: + pass -TEST = """\ -.#. -..# -### -""".splitlines() +solution = Solution(2020, 17) def main() -> None: - puzzle = aocd.models.Puzzle(2020, 17) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST) == 112 # type:ignore[arg-type] - assert part_2(TEST) == 848 # type:ignore[arg-type] - - inputs = my_aocd.get_input_data(puzzle, 8) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2020_19.py b/src/main/python/AoC2020_19.py index 43b1d00a..02b99c95 100644 --- a/src/main/python/AoC2020_19.py +++ b/src/main/python/AoC2020_19.py @@ -4,201 +4,17 @@ # from __future__ import annotations -from typing import NamedTuple + import re -from dataclasses import dataclass +import sys +from copy import deepcopy + from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples from aoc.common import log - -@dataclass -class Rule: - idx: int - rules: list - is_sink: bool - - def determine_sink(self): - isnumeric = False - for r in self.rules: - for _ in r: - isnumeric = isnumeric or _.isnumeric() - self.is_sink = not isnumeric - - def find(self, value: str) -> list[tuple[int, int]]: - found = list[tuple[int, int]]() - for i, rule in enumerate(self.rules): - for j, v in enumerate(rule): - if v == value: - found.append((i, j)) - return found - - -def _parse(inputs: tuple[str]): - blocks = my_aocd.to_blocks(inputs) - rules = dict() - for input_ in blocks[0]: - splits = input_.split(": ") - splits[1] = splits[1].replace("\"", "") - rules[int(splits[0])] = splits[1] - messages = blocks[1] - return rules, messages - - -def _reduce_rules(rules: dict) -> None: - sinks = [r[0] for r in rules.items() - if not bool(re.search(r'\d', r[1]))] - for r in rules.items(): - for sink in sinks: - sink_key = str(sink) - to_find = r'\b{s}\b'.format(s=sink_key) - if bool(re.search(to_find, r[1])): - sink_value = rules[sink] - old_value = rules[r[0]] - to_replace = r'\b{s}\b'.format(s=sink_key) - rules[r[0]] = re.sub(to_replace, - "(" + sink_value + ")", - old_value) - new_sinks = [r[0] for r in rules.items() - if not bool(re.search(r'\d', r[1]))] - if new_sinks == sinks: - return - for sink in sinks: - del rules[sink] - log(rules) - _reduce_rules(rules) - - -def _compact_rules(rules: dict) -> dict: - for r in rules.items(): - regex = r"\((\w{1})\) \((\w{1})\)" - subst = "(\\1*\\2)" - result = re.sub(regex, subst, r[1], flags=re.MULTILINE) - rules[r[0]] = result - - -def _translate_rules(rules: dict) -> dict: - for r in rules.items(): - regex = r"\) \(" - subst = ")*(" - result = re.sub(regex, subst, r[1], flags=re.MULTILINE) - rules[r[0]] = result - for r in rules.items(): - regex = r" \| " - subst = "+" - result = re.sub(regex, subst, r[1], flags=re.MULTILINE) - rules[r[0]] = result - for r in rules.items(): - regex = r"\(a\)" - subst = "a" - result = re.sub(regex, subst, r[1], flags=re.MULTILINE) - rules[r[0]] = result - for r in rules.items(): - regex = r"\(b\)" - subst = "b" - result = re.sub(regex, subst, r[1], flags=re.MULTILINE) - rules[r[0]] = result - - -class ResultAndPosition(NamedTuple): - result: list - position: int - - -def _build_rule(rule: str, pos: int = 0) -> ResultAndPosition: - """ build chain: * = append, + = new item(=list)""" - stack = ["", "*"] - result = list() - i = pos - while i < len(rule): - _ = rule[i] - breakpoint() - if _ in {"+", "*"}: - stack.append([_]) - elif _ == ")": - return ResultAndPosition(result, i) - else: - if _ == "(": - sub = _build_rule(rule, i+1) - operand_2 = sub.result - i = sub.position - elif _ not in {'a', 'b'}: - raise ValueError("invalid input") - else: - operand_2 = _ - operator = stack.pop()[0] - operand_1 = stack.pop() - if operator == "*": - operand_1.append(operand_2) - result.append(operand_1) - else: - result.append([operand_1, operand_2]) - stack.append(result) - i += 1 - return ResultAndPosition(result, i) - - -def _log_tree(tree): - if type(tree) is list: - for tree_item in tree: - _log_tree(tree_item) - else: - for tree_item in tree: - log(tree_item) - - -def _to_regex(rule: str) -> str: - rule = rule.replace("*", "") - return "^" + rule.replace(" ", "") + "$" - - -def _construct_rule(rules: dict) -> str: - _reduce_rules(rules) - # log(rules) - _compact_rules(rules) - # log(rules) - # _translate_rules(rules) - # log(rules) - # _log_tree(_build_rule(rules[0])[0]) - return rules[0] - - -def part_1(inputs: tuple[str]) -> int: - rules, messages = _parse(inputs) - # log(rules) - # log(messages) - rule = _construct_rule(rules) - assert len(rules) == 1 - assert rules.keys() == {0} - regex = _to_regex(rule) - log(regex) - - return sum([1 for message in messages if re.match(regex, message)]) - - -def part_2(inputs: tuple[str]) -> int: - rules, messages = _parse(inputs) - # log(rules) - # log(messages) - # rules[8] = "42 | 42 8" - # rules[11] = "42 31 | 42 11 31" - rules[8] = "(42)+" - # rules[11] = "(42)+ (31)+" - rules[11] = "42 31 | 42 42 31 31 | 42 42 42 31 31 31"\ - " | 42 42 42 42 31 31 31 31" - _reduce_rules(rules) - log(rules) - assert len(rules) == 1 - assert rules.keys() == {0} - rule = rules[0] - regex = "^" + rule.replace(" ", "") + "$" - log(regex) - - result = sum([1 for message in messages - if re.match(regex, message)]) - log(result) - return result - - TEST1 = """\ 0: 4 1 5 1: 2 3 | 3 2 @@ -212,7 +28,7 @@ def part_2(inputs: tuple[str]) -> int: abbbab aaabbb aaaabbb -""".splitlines() +""" TEST2 = """\ 42: 9 14 | 10 1 9: 14 27 | 1 26 @@ -261,22 +77,98 @@ def part_2(inputs: tuple[str]) -> int: aaaabbaabbaaaaaaabbbabbbaaabbaabaaa babaaabbbaaabaababbaabababaaab aabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba -""".splitlines() +""" -def main() -> None: - my_aocd.print_header(2020, 19) +def _parse(inputs: tuple[str, ...]) -> tuple[dict[int, str], list[str]]: + blocks = my_aocd.to_blocks(inputs) + rules = dict() + for input_ in blocks[0]: + splits = input_.split(": ") + splits[1] = splits[1].replace('"', "") + rules[int(splits[0])] = splits[1] + messages = blocks[1] + return rules, messages + + +def _to_regex(rules: dict[int, str]) -> str: + def reduce_rules() -> None: + sinks = [r[0] for r in rules.items() if not re.search(r"\d", r[1])] + for r in rules.items(): + for sink in sinks: + sink_key = str(sink) + to_find = r"\b{s}\b".format(s=sink_key) + if bool(re.search(to_find, r[1])): + sink_value = rules[sink] + old_value = rules[r[0]] + to_replace = r"\b{s}\b".format(s=sink_key) + rules[r[0]] = re.sub( + to_replace, "(" + sink_value + ")", old_value + ) + new_sinks = [r[0] for r in rules.items() if not re.search(r"\d", r[1])] + if new_sinks == sinks: + assert len(rules) == 1 + assert rules.keys() == {0} + return + for sink in sinks: + del rules[sink] + log(rules) + reduce_rules() + + def compact_rules() -> None: + for r in rules.items(): + regex = r"\((\w{1})\) \((\w{1})\)" + subst = "(\\1*\\2)" + result = re.sub(regex, subst, r[1], flags=re.MULTILINE) + rules[r[0]] = result + + reduce_rules() + # log(rules) + compact_rules() + # log(rules) + return "^" + rules[0].replace("*", "").replace(" ", "") + "$" + + +Input = tuple[dict[int, str], list[str]] +Output1 = int +Output2 = int - assert part_1(TEST1) == 2 - assert part_1(TEST2) == 3 - assert part_2(TEST2) == 12 - inputs = my_aocd.get_input(2020, 19, 534) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, inputs: InputData) -> Input: + return _parse(tuple(inputs)) + + def part_1(self, inputs: Input) -> int: + rules, messages = deepcopy(inputs[0]), inputs[1] + regex = _to_regex(rules) + return sum(1 for message in messages if re.match(regex, message)) + + def part_2(self, inputs: Input) -> int: + rules, messages = deepcopy(inputs[0]), inputs[1] + # rules[8] = "42 | 42 8" + rules[8] = "(42)+" + # rules[11] = "42 31 | 42 11 31" + rules[11] = "|".join("42 " * i + "31 " * i for i in range(1, 6)) + regex = _to_regex(rules) + return sum(1 for message in messages if re.match(regex, message)) + + @aoc_samples( + ( + ("part_1", TEST1, 2), + ("part_1", TEST2, 3), + ("part_2", TEST2, 12), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2020, 19) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2020_23.py b/src/main/python/AoC2020_23.py index 17f82737..87928843 100644 --- a/src/main/python/AoC2020_23.py +++ b/src/main/python/AoC2020_23.py @@ -4,134 +4,96 @@ # from __future__ import annotations -from dataclasses import dataclass -from aoc import my_aocd -from aoc.common import log - - -@dataclass -class Cup: - label: int - next_: Cup - - -def _parse(inputs: tuple[str]) -> list[int]: - assert len(inputs) == 1 - return [int(c) for c in inputs[0]] - - -def _log(size: int, msg): - if size <= 10: - log(msg) - - -def _print_cups(move: int, cups: dict[int, Cup], current: Cup) -> str: - if len(cups) > 10: - return - p = current - result = "" - while True: - result += f"{p.label} " - p = p.next_ - if p == current: - break - result = result.replace(str(current.label), "(" + str(current.label) + ")") - return result - - -def _prepare_cups(labels: list[int]) -> (dict[int, Cup], int, int, int): - cups = dict[int, Cup]() - first = labels[0] - last = labels[-1] - tail = Cup(last, None) - prev = tail - for label in reversed(labels[1:-1]): - cup = Cup(label, prev) - cups[label] = cup - prev = cup - head = Cup(first, prev) - cups[first] = head - tail.next_ = head - cups[last] = tail - return cups, len(labels), min(labels), max(labels) - - -def _do_move(move: int, cups: dict[int, Cup], - current: Cup, size: int, min_val: int, - max_val: int) -> (dict[int, Cup], int): - # _log(size, f"-- move {move+1} --") - # _log(size, f"cups: {_print_cups(move, cups, current)}") - c = current - p1 = c.next_ - p2 = p1.next_ - p3 = p2.next_ - c.next_ = p3.next_ - pickup = (p1.label, p2.label, p3.label) - # _log(size, f"pick up: {p1.label}, {p2.label}, {p3.label}") - d = c.label-1 - if d < min_val: - d = max_val - while d in pickup: - d -= 1 - if d < min_val: - d = max_val - # _log(size, f"destination: {d}") - destination = cups[d] - p3.next_ = destination.next_ - destination.next_ = p1 - current = c.next_ - # _log(size, "") - return cups, current - - -def part_1(inputs: tuple[str]) -> int: - cups = _parse(inputs) - cd, size, min_val, max_val = _prepare_cups(cups) - current = cd[cups[0]] - for move in range(100): - cd, current = _do_move(move, cd, current, size, min_val, max_val) - cup = cd[1] - result = "" - while cup.next_.label != 1: - result += str(cup.next_.label) - cup = cup.next_ - log(result) - return int(result) - - -def part_2(inputs: tuple[str]) -> int: - cups = _parse(inputs) - cups.extend([i for i in range(max(cups)+1, 1_000_001)]) - cd, size, min_val, max_val = _prepare_cups(cups) - current = cd[cups[0]] - for move in range(10_000_000): - cd, current = _do_move(move, cd, current, size, min_val, max_val) - # if move % 100_000 == 0: - # print('.', end='', flush=True) - # print("") - one = cd[1] - star1 = one.next_.label - star2 = one.next_.next_.label - return star1 * star2 + +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = list[int] +Output1 = str +Output2 = int TEST = """\ 389125467 -""".splitlines() +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [int(c) for c in list(input_data)[0]] + + def prepare_cups(self, labels: list[int]) -> list[int]: + cups = [0] * (len(labels) + 1) + for i in range(len(labels)): + cups[labels[i - 1]] = labels[i] + return cups + + def do_move( + self, + cups: list[int], + current: int, + min_val: int, + max_val: int, + ) -> int: + c = current + p1 = cups[c] + p2 = cups[p1] + p3 = cups[p2] + cups[c] = cups[p3] + pickup = (p1, p2, p3) + d = c - 1 + if d < min_val: + d = max_val + while d in pickup: + d -= 1 + if d < min_val: + d = max_val + cups[p3] = cups[d] + cups[d] = p1 + current = cups[c] + return current + + def part_1(self, labels: Input) -> Output1: + cups = self.prepare_cups(labels) + current = labels[0] + for _ in range(100): + current = self.do_move(cups, current, 1, 9) + ans = "" + cup = cups[1] + while True: + if cup == 1: + break + ans += str(cup) + cup = cups[cup] + return ans + + def part_2(self, labels: Input) -> Output2: + labels.extend([i for i in range(max(labels) + 1, 1_000_001)]) + cups = self.prepare_cups(labels) + current = labels[0] + for _ in range(10_000_000): + current = self.do_move(cups, current, 1, 1_000_000) + return cups[1] * cups[cups[1]] + + @aoc_samples( + ( + ("part_1", TEST, "67384529"), + ("part_2", TEST, 149245887792), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2020, 23) def main() -> None: - my_aocd.print_header(2020, 23) - - assert part_1(TEST) == 67384529 - assert part_2(TEST) == 149245887792 - - inputs = my_aocd.get_input(2020, 23, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2020_24.py b/src/main/python/AoC2020_24.py index c06249f9..cc1af147 100644 --- a/src/main/python/AoC2020_24.py +++ b/src/main/python/AoC2020_24.py @@ -10,136 +10,64 @@ """ from __future__ import annotations -import aocd -import re -from functools import lru_cache -from dataclasses import dataclass -from typing import Iterator -from aoc import my_aocd -from aoc.common import log -from aoc.geometry import Position -from aoc.navigation import Heading, Waypoint, NavigationWithWaypoint +import re +import sys +from collections import Counter +from typing import Iterable +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.game_of_life import GameOfLife + + +class Direction(NamedTuple): + q: int + r: int + + +DIRS = { + "ne": Direction(1, -1), + "nw": Direction(0, -1), + "se": Direction(0, 1), + "sw": Direction(-1, 1), + "e": Direction(1, 0), + "w": Direction(-1, 0), +} Tile = tuple[int, int] -E = "e" -SE = "se" -SW = "sw" -W = "w" -NW = "nw" -NE = "ne" +class Floor(NamedTuple): + tiles: set[Tile] -@dataclass(frozen=True) -class NavigationInstruction: - heading: Heading - value: int + @classmethod + def from_input(self, input: list[str]) -> Floor: + p = re.compile(r"(n?(e|w)|s?(e|w))") + tiles = set[Tile]() + for line in input: + tile = (0, 0) + for m in p.finditer(line): + d = DIRS[m.group(0)] + tile = (tile[0] + d.q, tile[1] + d.r) + if tile in tiles: + tiles.remove(tile) + else: + tiles.add(tile) + return Floor(tiles) -@dataclass(frozen=True) -class Floor: - tiles: set[Tile] +class HexGrid(GameOfLife.Universe[Tile]): + def neighbour_count(self, alive: Iterable[Tile]) -> dict[Tile, int]: + return Counter( + (t[0] + d.q, t[1] + d.r) for d in DIRS.values() for t in alive + ) - def flip(self, tile: Tile) -> None: - if tile in self.tiles: - self.tiles.remove(tile) - else: - self.tiles.add(tile) - - -def _parse(inputs: tuple[str, ...]) -> list[list[NavigationInstruction]]: - navs = list[list[NavigationInstruction]]() - for input_ in inputs: - nav = list[NavigationInstruction]() - m = re.findall(r"(n?(e|w)|s?(e|w))", input_) - for _ in m: - heading = _[0] - if heading == E: - nav.append(NavigationInstruction(Heading.EAST, 1)) - elif heading == SE: - nav.append(NavigationInstruction(Heading.SOUTH, 1)) - nav.append(NavigationInstruction(Heading.EAST, 1)) - elif heading == W: - nav.append(NavigationInstruction(Heading.WEST, 1)) - elif heading == SW: - nav.append(NavigationInstruction(Heading.SOUTH, 1)) - elif heading == NW: - nav.append(NavigationInstruction(Heading.NORTH, 1)) - nav.append(NavigationInstruction(Heading.WEST, 1)) - elif heading == NE: - nav.append(NavigationInstruction(Heading.NORTH, 1)) - else: - raise ValueError("invalid input") - navs.append(nav) - return navs - - -def _navigate_with_waypoint( - navigation: NavigationWithWaypoint, nav: NavigationInstruction -) -> None: - navigation.update_waypoint(nav.heading, nav.value) - - -def _build_floor(navs: list[list[NavigationInstruction]]) -> Floor: - floor = Floor(set()) - for nav in navs: - navigation = NavigationWithWaypoint(Position(0, 0), Waypoint(0, 0)) - for n in nav: - _navigate_with_waypoint(navigation, n) - floor.flip((navigation.waypoint.x, navigation.waypoint.y)) - return floor - - -def part_1(inputs: tuple[str, ...]) -> int: - navs = _parse(inputs) - floor = _build_floor(navs) - return len(floor.tiles) - - -@lru_cache(maxsize=11000) -def _get_neighbours(x: int, y: int) -> list[Tile]: - return [ - (x + dx, y + dy) - for dx, dy in [(-1, 1), (0, 1), (-1, 0), (1, 0), (0, -1), (1, -1)] - ] - - -def _run_cycle(floor: Floor) -> Floor: - def to_check() -> Iterator[Tile]: - for tile in floor.tiles: - # check the positions of all existing black tiles - yield tile - # also check all their neighbour positions (a position can only - # become black if it is adjacent to at least 1 black tile) - for n in _get_neighbours(*tile): - yield n - - new_tiles = set() - # for each position: - for p in to_check(): - # does it have black neighbour? yes: if that neighbour - # is an existing black tile; no: if it isn't - bn = 0 - for n in _get_neighbours(*p): - if n in floor.tiles: - bn += 1 - # apply rules - if p in floor.tiles and bn in {1, 2}: - new_tiles.add(p) - if p not in floor.tiles and bn == 2: - new_tiles.add(p) - return Floor(new_tiles) - - -def part_2(inputs: tuple[str, ...]) -> int: - navs = _parse(inputs) - floor = _build_floor(navs) - log(len(floor.tiles)) - for _ in range(100): - floor = _run_cycle(floor) - log(len(floor.tiles)) - log(_get_neighbours.cache_info()) - return len(floor.tiles) + +class Rules(GameOfLife.Rules[Tile]): + def alive(self, tile: Tile, count: int, alive: Iterable[Tile]) -> bool: + return count == 2 or (count == 1 and tile in alive) TEST = """\ @@ -163,22 +91,42 @@ def part_2(inputs: tuple[str, ...]) -> int: eneswnwswnwsenenwnwnwwseeswneewsenese neswnwewnwnwseenwseesewsenwsweewe wseweeenwnesenwwwswnew -""".splitlines() +""" + + +Input = Floor +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return Floor.from_input(list(input_data)) + + def part_1(self, floor: Floor) -> int: + return len(floor.tiles) + + def part_2(self, floor: Floor) -> int: + gol = GameOfLife(floor.tiles, HexGrid(), Rules()) + for _ in range(100): + gol.next_generation() + return sum(1 for _ in gol.alive) + + @aoc_samples( + ( + ("part_1", TEST, 10), + ("part_2", TEST, 2208), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2020, 24) def main() -> None: - puzzle = aocd.models.Puzzle(2020, 24) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST) == 10 # type:ignore[arg-type] - assert part_2(TEST) == 2208 # type:ignore[arg-type] - - inputs = my_aocd.get_input_data(puzzle, 316) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) if __name__ == "__main__": diff --git a/src/main/python/AoC2021_01.py b/src/main/python/AoC2021_01.py index 8c901e94..52f7e67e 100644 --- a/src/main/python/AoC2021_01.py +++ b/src/main/python/AoC2021_01.py @@ -3,25 +3,11 @@ # Advent of Code 2021 Day 1 # -from aoc import my_aocd - - -def _parse(inputs: tuple[str]) -> tuple[int]: - return tuple(int(_) for _ in inputs) - - -def _solve(depths: tuple[int], window: int) -> int: - return sum([depths[i] > depths[i-window] - for i in range(window, len(depths))]) - - -def part_1(inputs: tuple[str]) -> int: - return _solve(_parse(inputs), 1) - - -def part_2(inputs: tuple[str]) -> int: - return _solve(_parse(inputs), 3) +import sys +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples TEST = """\ 199 @@ -34,21 +20,45 @@ def part_2(inputs: tuple[str]) -> int: 269 260 263 -""".splitlines() +""" -def main() -> None: - my_aocd.print_header(2021, 1) +Input = list[int] +Output1 = int +Output2 = int - assert part_1(TEST) == 7 - assert part_2(TEST) == 5 - inputs = my_aocd.get_input(2021, 1, 2000) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [int(_) for _ in input_data] + + def count_increases(self, depths: Input, window: int) -> int: + return sum( + depths[i] > depths[i - window] for i in range(window, len(depths)) + ) + + def part_1(self, inputs: Input) -> Output1: + return self.count_increases(inputs, window=1) + + def part_2(self, inputs: Input) -> Output2: + return self.count_increases(inputs, window=3) + + @aoc_samples( + ( + ("part_1", TEST, 7), + ("part_2", TEST, 5), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2021, 1) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2021_02.py b/src/main/python/AoC2021_02.py index dc31e451..628f76b9 100644 --- a/src/main/python/AoC2021_02.py +++ b/src/main/python/AoC2021_02.py @@ -3,53 +3,15 @@ # Advent of Code 2021 Day 2 # -from aoc import my_aocd -from typing import NamedTuple - -FORWARD = "forward" -UP = "up" -DOWN = "down" - - -class Command(NamedTuple): - dir: str - amount: int - - @classmethod - def create(cls, dir: str, amount: str): - if dir not in {FORWARD, UP, DOWN}: - raise ValueError - return cls(dir, int(amount)) - - -def _parse(inputs: tuple[str]) -> list[Command]: - return [Command.create(*input.split()) for input in inputs] - - -def part_1(inputs: tuple[str]) -> int: - hor = ver = 0 - for command in _parse(inputs): - if command.dir == UP: - ver -= command.amount - elif command.dir == DOWN: - ver += command.amount - else: - hor += command.amount - return hor * ver +from __future__ import annotations +import sys +from enum import Enum +from typing import NamedTuple -def part_2(inputs: tuple[str]) -> int: - hor = ver = aim = 0 - for command in _parse(inputs): - if command.dir == UP: - aim -= command.amount - elif command.dir == DOWN: - aim += command.amount - else: - hor += command.amount - ver += aim * command.amount - return hor * ver - +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples TEST = """\ forward 5 @@ -58,19 +20,82 @@ def part_2(inputs: tuple[str]) -> int: up 3 down 8 forward 2 -""".splitlines() +""" -def main() -> None: - my_aocd.print_header(2021, 2) +class Direction(Enum): + FORWARD = "forward" + UP = "up" + DOWN = "down" + + @classmethod + def from_str(cls, s: str) -> Direction: + for v in Direction: + if v.value == s: + return v + raise ValueError + + +class Command(NamedTuple): + dir: Direction + amount: int + + @classmethod + def create(cls, input: str) -> Command: + dir, amount = input.split() + return cls(Direction.from_str(dir), int(amount)) + + +Input = list[Command] +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [Command.create(line) for line in input_data] + + def part_1(self, commands: Input) -> Output1: + hor = ver = 0 + for command in commands: + match command.dir: + case Direction.UP: + ver -= command.amount + case Direction.DOWN: + ver += command.amount + case _: + hor += command.amount + return hor * ver + + def part_2(self, commands: Input) -> Output2: + hor = ver = aim = 0 + for command in commands: + match command.dir: + case Direction.UP: + aim -= command.amount + case Direction.DOWN: + aim += command.amount + case _: + hor += command.amount + ver += aim * command.amount + return hor * ver + + @aoc_samples( + ( + ("part_1", TEST, 150), + ("part_2", TEST, 900), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2021, 2) - assert part_1(TEST) == 150 - assert part_2(TEST) == 900 - inputs = my_aocd.get_input(2021, 2, 1000) - print(f"Part 1: {part_1(inputs)}") - print(f"Part 2: {part_2(inputs)}") +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2021_03.py b/src/main/python/AoC2021_03.py index 1366b30c..78296604 100644 --- a/src/main/python/AoC2021_03.py +++ b/src/main/python/AoC2021_03.py @@ -3,81 +3,100 @@ # Advent of Code 2021 Day 3 # +from __future__ import annotations + +import sys +from typing import Callable from typing import NamedTuple -from aoc import my_aocd + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +TEST = """\ +00100 +11110 +10110 +10111 +10101 +01111 +00111 +11100 +10000 +11001 +00010 +01010 +""" class BitCount(NamedTuple): ones: int zeroes: int + @classmethod + def at_pos(cls, strings: list[str], pos: int) -> BitCount: + zeroes = sum(s[pos] == "0" for s in strings) + return BitCount(len(strings) - zeroes, zeroes) + def most_common(self) -> str: - return '1' if self.ones >= self.zeroes else '0' + return "1" if self.ones >= self.zeroes else "0" def least_common(self) -> str: - return '1' if self.ones < self.zeroes else '0' + return "1" if self.ones < self.zeroes else "0" -def _ans(value1: str, value2: str) -> int: - return int(value1, 2) * int(value2, 2) +Input = list[str] +Output1 = int +Output2 = int -def _bit_counts(strings: list[str], pos: int) -> BitCount: - zeroes = sum(s[pos] == '0' for s in strings) - return BitCount(len(strings) - zeroes, zeroes) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data) + def ans(self, value1: str, value2: str) -> int: + return int(value1, 2) * int(value2, 2) -def part_1(inputs: tuple[str]) -> int: - gamma = "" - epsilon = "" - for i in range(len(inputs[0])): - bit_count = _bit_counts(inputs, i) - gamma += bit_count.most_common() - epsilon += bit_count.least_common() - return _ans(gamma, epsilon) + def part_1(self, inputs: Input) -> Output1: + gamma = "" + epsilon = "" + for i in range(len(inputs[0])): + bit_count = BitCount.at_pos(inputs, i) + gamma += bit_count.most_common() + epsilon += bit_count.least_common() + return self.ans(gamma, epsilon) + def reduce( + self, strings: list[str], keep: Callable[[BitCount], str] + ) -> str: + pos = 0 + while len(strings) > 1: + to_keep = keep(BitCount.at_pos(strings, pos)) + strings = [s for s in strings if s[pos] == to_keep] + pos += 1 + return strings[0] -def _reduce(strings: list[str], keep) -> str: - pos = 0 - while len(strings) > 1: - to_keep = keep(_bit_counts(strings, pos)) - strings = [s for s in strings if s[pos] == to_keep] - pos += 1 - return strings[0] + def part_2(self, inputs: Input) -> Output2: + o2 = self.reduce(inputs, lambda x: x.most_common()) + co2 = self.reduce(inputs, lambda x: x.least_common()) + return self.ans(o2, co2) + @aoc_samples( + ( + ("part_1", TEST, 198), + ("part_2", TEST, 230), + ) + ) + def samples(self) -> None: + pass -def part_2(inputs: tuple[str]) -> int: - o2 = _reduce(list(inputs), lambda x: x.most_common()) - co2 = _reduce(list(inputs), lambda x: x.least_common()) - return _ans(o2, co2) - -TEST = """\ -00100 -11110 -10110 -10111 -10101 -01111 -00111 -11100 -10000 -11001 -00010 -01010 -""".splitlines() +solution = Solution(2021, 3) def main() -> None: - my_aocd.print_header(2021, 3) - - assert part_1(TEST) == 198 - assert part_2(TEST) == 230 - - inputs = my_aocd.get_input(2021, 3, 1000) - print(f"Part 1: {part_1(inputs)}") - print(f"Part 2: {part_2(inputs)}") + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2021_04.py b/src/main/python/AoC2021_04.py index 8a3bd918..7bc7be10 100644 --- a/src/main/python/AoC2021_04.py +++ b/src/main/python/AoC2021_04.py @@ -3,8 +3,16 @@ # Advent of Code 2021 Day 4 # +from __future__ import annotations + +import itertools +import sys from typing import NamedTuple + from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples class Board: @@ -15,9 +23,9 @@ class Board: def __init__(self, numbers: list[str]): self.complete = False - self.numbers = list() - for i, s in enumerate(numbers): - self.numbers.append([int(_) for _ in s.split()]) + self.numbers = [ + [int(_) for _ in s.split()] for i, s in enumerate(numbers) + ] def __repr__(self) -> str: return f"Board(complete: {self.complete}, numbers: {self.numbers})" @@ -35,23 +43,19 @@ def mark(self, number: int) -> None: row[i] = Board.MARKED def win(self) -> bool: - for row in self.numbers: - if all(_ == Board.MARKED for _ in row): - return True - for i in range(self._get_width()): - if all(_ == Board.MARKED for _ in self._get_column(i)): - return True - return False + return any( + all(val == Board.MARKED for val in rc) + for rc in itertools.chain( + (row for row in self.numbers), + (self._get_column(col) for col in range(self._get_width())), + ) + ) def value(self) -> int: - return sum(c - for row in self.numbers - for c in row - if c != Board.MARKED) + return sum(c for row in self.numbers for c in row if c != Board.MARKED) def _get_column(self, col: int) -> list[int]: - return [self.numbers[row][col] - for row in range(self._get_height())] + return [self.numbers[row][col] for row in range(self._get_height())] def _get_height(self) -> int: return len(self.numbers) @@ -65,42 +69,37 @@ class Bingo(NamedTuple): board: Board -def _parse(inputs: tuple[str]) -> tuple[list[int], list[Board]]: - blocks = my_aocd.to_blocks(inputs) - draws = [int(_) for _ in blocks[0][0].split(',')] - boards = [Board(_) for _ in blocks[1:]] - return draws, boards - - -def _play(draws: list[int], boards: list[Board], stop) -> None: - for draw in draws: - [b.mark(draw) for b in boards] - winners = [b for b in boards if not b.is_complete() and b.win()] - for winner in winners: - winner.set_complete() - if stop(Bingo(draw, winner)): - return - - -def _solve(draws: list[int], boards: list[Board], stop_count: int) -> int: - bingoes = [] - - def stop(bingo: Bingo) -> bool: - bingoes.append(bingo) - return len(bingoes) == stop_count - - _play(draws, boards, stop) - return bingoes[-1].draw * bingoes[-1].board.value() +class BingoGame(NamedTuple): + draws: list[int] + boards: list[Board] + @classmethod + def from_input(cls, inputs: list[str]) -> BingoGame: + blocks = my_aocd.to_blocks(inputs) + draws = [int(_) for _ in blocks[0][0].split(",")] + boards = [Board(_) for _ in blocks[1:]] + return BingoGame(draws, boards) -def part_1(inputs: tuple[str]) -> int: - draws, boards = _parse(inputs) - return _solve(draws, boards, 1) + def play(self, stop_count: int) -> list[Bingo]: + bingoes = list[Bingo]() + for draw in self.draws: + for b in self.boards: + b.mark(draw) + winners = [ + b for b in self.boards if not b.is_complete() and b.win() + ] + for winner in winners: + winner.set_complete() + bingo = Bingo(draw, winner) + bingoes.append(bingo) + if len(bingoes) == stop_count: + return bingoes + raise RuntimeError("unreachable") -def part_2(inputs: tuple[str]) -> int: - draws, boards = _parse(inputs) - return _solve(draws, boards, len(boards)) +Input = list[str] +Output1 = int +Output2 = int TEST = """\ @@ -123,21 +122,41 @@ def part_2(inputs: tuple[str]) -> int: 18 8 23 26 20 22 11 13 6 5 2 0 12 3 7 -""".splitlines() +""" -def main() -> None: - my_aocd.print_header(2021, 4) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data) + + def solve(self, game: BingoGame, stop_count: int) -> int: + bingoes = game.play(stop_count) + return bingoes[-1].draw * bingoes[-1].board.value() + + def part_1(self, inputs: Input) -> int: + game = BingoGame.from_input(inputs) + return self.solve(game, 1) + + def part_2(self, inputs: Input) -> int: + game = BingoGame.from_input(inputs) + return self.solve(game, len(game.boards)) - assert part_1(TEST) == 4512 - assert part_2(TEST) == 1924 + @aoc_samples( + ( + ("part_1", TEST, 4512), + ("part_2", TEST, 1924), + ) + ) + def samples(self) -> None: + pass - inputs = my_aocd.get_input(2021, 4, 601) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + +solution = Solution(2021, 4) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2021_05.py b/src/main/python/AoC2021_05.py index 8e55d75d..f44f7e35 100644 --- a/src/main/python/AoC2021_05.py +++ b/src/main/python/AoC2021_05.py @@ -3,36 +3,17 @@ # Advent of Code 2021 Day 5 # -import re -from collections import defaultdict -from aoc import my_aocd -from aoc.common import log - - -def _solve(inputs: tuple[str], diag: bool) -> int: - d = defaultdict(int) - for s in inputs: - x1, y1, x2, y2 = tuple(map(int, re.findall(r'\d+', s))) - assert sum(_ < 0 for _ in (x1, y1, x2, y2)) == 0 - log((x1, y1, x2, y2)) - mx = 0 if x1 == x2 else 1 if x1 < x2 else -1 - my = 0 if y1 == y2 else 1 if y1 < y2 else -1 - if not diag and mx != 0 and my != 0: - continue - length = max(abs(x1 - x2), abs(y1 - y2)) - for i in range(0, length + 1): - d[(x1 + mx * i, y1 + my * i)] += 1 - log(d) - return sum(x > 1 for x in d.values()) - +from __future__ import annotations -def part_1(inputs: tuple[str]) -> int: - return _solve(inputs, False) - - -def part_2(inputs: tuple[str]) -> int: - return _solve(inputs, True) +import re +import sys +from collections import Counter +from typing import Iterator +from typing import NamedTuple +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples TEST = """\ 0,9 -> 5,9 @@ -45,21 +26,70 @@ def part_2(inputs: tuple[str]) -> int: 3,4 -> 1,4 0,0 -> 8,8 5,5 -> 8,2 -""".splitlines() +""" -def main() -> None: - my_aocd.print_header(2021, 5) +class LineSegment(NamedTuple): + x1: int + y1: int + x2: int + y2: int + mx: int + my: int - assert part_1(TEST) == 5 - assert part_2(TEST) == 12 + @classmethod + def from_input(cls, s: str) -> LineSegment: + x1, y1, x2, y2 = map(int, re.findall(r"\d+", s)) + mx = 0 if x1 == x2 else 1 if x1 < x2 else -1 + my = 0 if y1 == y2 else 1 if y1 < y2 else -1 + return LineSegment(x1, y1, x2, y2, mx, my) - inputs = my_aocd.get_input(2021, 5, 500) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + def positions(self) -> Iterator[tuple[int, int]]: + length = max(abs(self.x1 - self.x2), abs(self.y1 - self.y2)) + for i in range(0, length + 1): + yield (self.x1 + self.mx * i, self.y1 + self.my * i) + + +Input = list[LineSegment] +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [LineSegment.from_input(s) for s in input_data] + + def count_intersections(self, lines: Input, diagonals: bool) -> int: + counter = Counter( + p + for line in lines + for p in line.positions() + if diagonals or line.mx == 0 or line.my == 0 + ) + return sum(v > 1 for v in counter.values()) + + def part_1(self, lines: Input) -> Output1: + return self.count_intersections(lines, diagonals=False) + + def part_2(self, lines: Input) -> Output2: + return self.count_intersections(lines, diagonals=True) + + @aoc_samples( + ( + ("part_1", TEST, 5), + ("part_2", TEST, 12), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2021, 5) + + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2021_06.py b/src/main/python/AoC2021_06.py index 1e14cc93..653515d4 100644 --- a/src/main/python/AoC2021_06.py +++ b/src/main/python/AoC2021_06.py @@ -3,47 +3,57 @@ # Advent of Code 2021 Day 6 # +import sys from collections import deque -from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples -def _solve(inputs: tuple[str], days: int) -> int: - assert len(inputs) == 1 - fishies = deque([0] * 9) - for n in inputs[0].split(','): - fishies[int(n)] += 1 - for i in range(days): - zeroes = fishies[0] - fishies.rotate(-1) - fishies[6] += zeroes - return sum(fishies) +TEST = "3,4,3,1,2" -def part_1(inputs: tuple[str]) -> int: - return _solve(inputs, 80) +Input = list[int] +Output1 = int +Output2 = int -def part_2(inputs: tuple[str]) -> int: - return _solve(inputs, 256) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [int(_) for _ in list(input_data)[0].split(",")] + def solve(self, inputs: list[int], days: int) -> int: + fishies = deque([0] * 9) + for n in inputs: + fishies[n] += 1 + for i in range(days): + zeroes = fishies[0] + fishies.rotate(-1) + fishies[6] += zeroes + return sum(fishies) -TEST = """\ -3,4,3,1,2 -""".splitlines() + def part_1(self, inputs: Input) -> Output1: + return self.solve(inputs, days=80) + def part_2(self, inputs: Input) -> Output2: + return self.solve(inputs, days=256) + + @aoc_samples( + ( + ("part_1", TEST, 5_934), + ("part_2", TEST, 26_984_457_539), + ) + ) + def samples(self) -> None: + pass -def main() -> None: - my_aocd.print_header(2021, 6) - assert part_1(TEST) == 5_934 - assert part_2(TEST) == 26_984_457_539 +solution = Solution(2021, 6) - inputs = my_aocd.get_input(2021, 6, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2021_07.py b/src/main/python/AoC2021_07.py index 535dfb62..83834712 100644 --- a/src/main/python/AoC2021_07.py +++ b/src/main/python/AoC2021_07.py @@ -3,41 +3,57 @@ # Advent of Code 2021 Day 7 # -from aoc import my_aocd +import sys +from typing import Callable +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples -def _solve(inputs: tuple[str], calc) -> int: - assert len(inputs) == 1 - positions = [int(_) for _ in inputs[0].split(',')] - return min(sum(calc(a, b) for b in positions) - for a in range(min(positions), max(positions) + 1)) +Input = list[int] +Output1 = int +Output2 = int -def part_1(inputs: tuple[str]) -> int: - return _solve(inputs, lambda a, b: abs(a - b)) +TEST = "16,1,2,0,4,2,7,1,2,14" -def part_2(inputs: tuple[str]) -> int: - return _solve(inputs, lambda a, b: abs(a - b) * (abs(a - b) + 1) // 2) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [int(_) for _ in list(input_data)[0].split(",")] + def solve( + self, positions: list[int], calc: Callable[[int, int], int] + ) -> int: + return min( + sum(calc(a, b) for b in positions) + for a in range(min(positions), max(positions) + 1) + ) -TEST = """\ -16,1,2,0,4,2,7,1,2,14 -""".splitlines() + def part_1(self, positions: Input) -> Output1: + return self.solve(positions, lambda a, b: abs(a - b)) + def part_2(self, positions: Input) -> Output2: + return self.solve( + positions, lambda a, b: abs(a - b) * (abs(a - b) + 1) // 2 + ) + + @aoc_samples( + ( + ("part_1", TEST, 37), + ("part_2", TEST, 168), + ) + ) + def samples(self) -> None: + pass -def main() -> None: - my_aocd.print_header(2021, 7) - assert part_1(TEST) == 37 - assert part_2(TEST) == 168 +solution = Solution(2021, 7) - inputs = my_aocd.get_input(2021, 7, 1) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") + +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2021_08.py b/src/main/python/AoC2021_08.py index b9e66545..f84a6875 100644 --- a/src/main/python/AoC2021_08.py +++ b/src/main/python/AoC2021_08.py @@ -3,93 +3,14 @@ # Advent of Code 2021 Day 8 # -import aocd -from aoc import my_aocd - - -def _parse(inputs: tuple[str]) -> tuple[list[list[str]], list[list[str]]]: - return ([[''.join(sorted(_)) - for _ in input_.split(' | ')[i].split()] - for input_ in inputs] - for i in {0, 1}) - - -def part_1(inputs: tuple[str]) -> int: - _, outputs = _parse(inputs) - return sum(len(word) in {2, 3, 4, 7} - for line in outputs - for word in line) - - -def _find(input_: list[str], segments: int, expected: int) -> set[str]: - ans = {_ for _ in input_ if len(_) == segments} - assert len(ans) == expected - return ans - - -def _shares_all_segments(container: str, contained: str) -> bool: - cd = set(contained) - return set(container) & cd == cd - - -def _solve(signals: list[str], outputs: list[str]) -> int: - digits = dict[str, str]() - # 1 - twos = _find(signals, 2, 1) - one = list(twos)[0] - digits[one] = '1' - # 7 - threes = _find(signals, 3, 1) - digits[list(threes)[0]] = '7' - # 4 - fours = _find(signals, 4, 1) - four = list(fours)[0] - digits[four] = '4' - # 8 - sevens = _find(signals, 7, 1) - digits[list(sevens)[0]] = '8' - # 9 - sixes = _find(signals, 6, 3) - possible_nines = {six for six in sixes if _shares_all_segments(six, four)} - assert len(possible_nines) == 1 - nine = list(possible_nines)[0] - digits[nine] = '9' - # 0 - possible_zeroes = {six for six in sixes - if six != nine and _shares_all_segments(six, one)} - assert len(possible_zeroes) == 1 - zero = list(possible_zeroes)[0] - digits[zero] = '0' - # 6 - possible_sixes = {six for six in sixes - if six != nine and six != zero} - assert len(possible_sixes) == 1 - digits[list(possible_sixes)[0]] = '6' - # 3 - fives = _find(signals, 5, 3) - possible_threes = {five for five in fives - if _shares_all_segments(five, one)} - assert len(possible_threes) == 1 - three = list(possible_threes)[0] - digits[three] = '3' - # 5 - possible_fives = {five for five in fives - if five != three and _shares_all_segments(nine, five)} - assert len(possible_fives) == 1 - five = list(possible_fives)[0] - digits[five] = '5' - # 2 - possible_twos = {f for f in fives if f != five and f != three} - assert len(possible_twos) == 1 - digits[list(possible_twos)[0]] = '2' - - return int("".join(digits[o] for o in outputs)) - - -def part_2(inputs: tuple[str]) -> int: - signals, outputs = _parse(inputs) - return sum(_solve(signals[i], outputs[i]) for i in range(len(signals))) +from __future__ import annotations +import sys +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples TEST = """\ be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | \ @@ -112,23 +33,127 @@ def part_2(inputs: tuple[str]) -> int: gbdfcae bgc cg cgb gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | \ fgae cfgab fg bagce -""".splitlines() +""" + + +class SignalAndOutput(NamedTuple): + signals: list[str] + outputs: list[str] + + @classmethod + def from_str(cls, s: str) -> SignalAndOutput: + signals = ["".join(sorted(_)) for _ in s.split(" | ")[0].split()] + outputs = ["".join(sorted(_)) for _ in s.split(" | ")[1].split()] + return SignalAndOutput(signals, outputs) + + +Input = list[SignalAndOutput] +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [SignalAndOutput.from_str(s) for s in input_data] + + def part_1(self, input: Input) -> int: + return sum( + len(word) in {2, 3, 4, 7} + for line in (line for _, line in input) + for word in line + ) + + def part_2(self, input: Input) -> int: + def solve(input: SignalAndOutput) -> int: + def find(segments: int, expected: int) -> set[str]: + ans = {_ for _ in input.signals if len(_) == segments} + assert len(ans) == expected + return ans + + def shares_all_segments(container: str, contained: str) -> bool: + cd = set(contained) + return set(container) & cd == cd + + digits = dict[str, str]() + # 1 + twos = find(2, 1) + one = list(twos)[0] + digits[one] = "1" + # 7 + threes = find(3, 1) + digits[list(threes)[0]] = "7" + # 4 + fours = find(4, 1) + four = list(fours)[0] + digits[four] = "4" + # 8 + sevens = find(7, 1) + digits[list(sevens)[0]] = "8" + # 9 + sixes = find(6, 3) + possible_nines = { + six for six in sixes if shares_all_segments(six, four) + } + assert len(possible_nines) == 1 + nine = list(possible_nines)[0] + digits[nine] = "9" + # 0 + possible_zeroes = { + six + for six in sixes + if six != nine and shares_all_segments(six, one) + } + assert len(possible_zeroes) == 1 + zero = list(possible_zeroes)[0] + digits[zero] = "0" + # 6 + possible_sixes = { + six for six in sixes if six != nine and six != zero + } + assert len(possible_sixes) == 1 + digits[list(possible_sixes)[0]] = "6" + # 3 + fives = find(5, 3) + possible_threes = { + five for five in fives if shares_all_segments(five, one) + } + assert len(possible_threes) == 1 + three = list(possible_threes)[0] + digits[three] = "3" + # 5 + possible_fives = { + five + for five in fives + if five != three and shares_all_segments(nine, five) + } + assert len(possible_fives) == 1 + five = list(possible_fives)[0] + digits[five] = "5" + # 2 + possible_twos = {f for f in fives if f != five and f != three} + assert len(possible_twos) == 1 + digits[list(possible_twos)[0]] = "2" + + return int("".join(digits[o] for o in input.outputs)) + + return sum(solve(_) for _ in input) + + @aoc_samples( + ( + ("part_1", TEST, 26), + ("part_2", TEST, 61229), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2021, 8) def main() -> None: - puzzle = aocd.models.Puzzle(2021, 8) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST) == 26 - assert part_2(TEST) == 61229 - - inputs = my_aocd.get_input(2021, 8, 200) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2021_09.py b/src/main/python/AoC2021_09.py index ba077ec8..5e10f09e 100644 --- a/src/main/python/AoC2021_09.py +++ b/src/main/python/AoC2021_09.py @@ -3,83 +3,82 @@ # Advent of Code 2021 Day 9 # -from collections.abc import Iterator +import sys from math import prod -from aoc import my_aocd -from aoc.grid import Cell, IntGrid -from aoc.common import log -from aoc.graph import flood_fill -import aocd - - -def _parse(inputs: tuple[str, ...]) -> IntGrid: - return IntGrid([[int(_) for _ in list(r)] for r in inputs]) +from typing import Iterator +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.graph import flood_fill +from aoc.grid import Cell +from aoc.grid import IntGrid -def _find_lows(grid: IntGrid) -> Iterator[Cell]: - for low in ( - c - for c in grid.get_cells() - if ( - all( - grid.get_value(c) < grid.get_value(n) - for n in grid.get_capital_neighbours(c) +TEST = """\ +2199943210 +3987894921 +9856789892 +8767896789 +9899965678 +""" + +Input = IntGrid +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return IntGrid.from_strings(list(input_data)) + + def find_lows(self, grid: IntGrid) -> Iterator[Cell]: + return ( + c + for c in grid.get_cells() + if ( + all( + grid.get_value(c) < grid.get_value(n) + for n in grid.get_capital_neighbours(c) + ) ) ) - ): - log(low) - yield low - - -def part_1(inputs: tuple[str, ...]) -> int: - grid = _parse(inputs) - log(grid) - return sum([grid.get_value(c) + 1 for c in _find_lows(grid)]) + def part_1(self, grid: Input) -> int: + return sum(grid.get_value(c) + 1 for c in self.find_lows(grid)) + + def part_2(self, grid: Input) -> int: + def size_of_basin_around_low(c: Cell) -> int: + basin = flood_fill( + c, + lambda c: ( + n + for n in grid.get_capital_neighbours(c) + if grid.get_value(n) != 9 + ), + ) + return len(basin) -def _size_of_basin_around_low(grid: IntGrid, c: Cell) -> int: - basin = flood_fill( - c, - lambda c: ( - n for n in grid.get_capital_neighbours(c) if grid.get_value(n) != 9 - ), - ) - log(basin) - log(len(basin)) - return len(basin) - + return prod( + sorted( + size_of_basin_around_low(low) for low in self.find_lows(grid) + )[-3:] + ) -def part_2(inputs: tuple[str, ...]) -> int: - grid = _parse(inputs) - return prod( - sorted( - _size_of_basin_around_low(grid, low) for low in _find_lows(grid) - )[-3:] + @aoc_samples( + ( + ("part_1", TEST, 15), + ("part_2", TEST, 1134), + ) ) + def samples(self) -> None: + pass -TEST = """\ -2199943210 -3987894921 -9856789892 -8767896789 -9899965678 -""".splitlines() +solution = Solution(2021, 9) def main() -> None: - puzzle = aocd.models.Puzzle(2021, 9) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST) == 15 # type:ignore[arg-type] - assert part_2(TEST) == 1134 # type:ignore[arg-type] - - inputs = my_aocd.get_input(puzzle.year, puzzle.day, 100) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) if __name__ == "__main__": diff --git a/src/main/python/AoC2021_10.py b/src/main/python/AoC2021_10.py index 688d2747..00d94ca2 100644 --- a/src/main/python/AoC2021_10.py +++ b/src/main/python/AoC2021_10.py @@ -4,22 +4,25 @@ # from __future__ import annotations -from typing import NamedTuple + +import sys from collections import deque from functools import reduce -from aoc import my_aocd -from aoc.common import log -import aocd +from typing import NamedTuple +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.common import log -PAREN_OPEN = '(' -PAREN_CLOSE = ')' -SQUARE_OPEN = '[' -SQUARE_CLOSE = ']' -CURLY_OPEN = '{' -CURLY_CLOSE = '}' -ANGLE_OPEN = '<' -ANGLE_CLOSE = '>' +PAREN_OPEN = "(" +PAREN_CLOSE = ")" +SQUARE_OPEN = "[" +SQUARE_CLOSE = "]" +CURLY_OPEN = "{" +CURLY_CLOSE = "}" +ANGLE_OPEN = "<" +ANGLE_CLOSE = ">" OPEN = (PAREN_OPEN, SQUARE_OPEN, CURLY_OPEN, ANGLE_OPEN) MAP = { PAREN_OPEN: PAREN_CLOSE, @@ -44,78 +47,86 @@ CURLY_OPEN: 3, ANGLE_OPEN: 4, } +TEST = """\ +[({(<(())[]>[[{[]{<()<>> +[(()[<>])]({[<{<<[]>>( +{([(<{}[<>[]}>{[]{[(<()> +(((({<>}<{<{<>}{[]{[]{} +[[<[([]))<([[{}[[()]]] +[{[{({}]{}}([{[{{{}}([] +{<[[]]>}<{[{[{[]{()[[[] +[<(<(<(<{}))><([]([]() +<{([([[(<>()){}]>(<<{{ +<{([{{}}[<[[[<>{}]]]>[]] +""" class Result(NamedTuple): - corrupt: str - incomplete: list[str] + corrupt: str | None + incomplete: list[str] | None @classmethod - def corrupt(cls, c: str) -> Result: + def for_corrupt(cls, c: str) -> Result: return Result(c, None) @classmethod - def incomplete(cls, q: list[str]) -> Result: + def for_incomplete(cls, q: list[str]) -> Result: return Result(None, q) -def _check(line: str) -> Result: - stack = deque[str]() - for c in list(line): - if c in OPEN: - stack.appendleft(c) - else: - if MAP[c] != stack.popleft(): - return Result(c, None) - return Result(None, list(stack)) - - -def part_1(inputs: tuple[str]) -> int: - return sum(CORRUPTION_SCORES[result.corrupt] - for result in filter(lambda x: x.corrupt is not None, - (_check(line) for line in inputs))) - - -def part_2(inputs: tuple[str]) -> int: - scores = sorted( - reduce(lambda a, b: 5 * a + b, - (INCOMPLETE_SCORES[x] for x in result.incomplete)) - for result in filter(lambda x: x.incomplete is not None, - (_check(line) for line in inputs)) +Input = InputData +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return input_data + + def check(self, line: str) -> Result: + stack = deque[str]() + for c in line: + if c in OPEN: + stack.appendleft(c) + elif MAP[c] != stack.popleft(): + return Result.for_corrupt(c) + return Result.for_incomplete(list(stack)) + + def part_1(self, inputs: Input) -> int: + return sum( + CORRUPTION_SCORES[corrupt] + for corrupt in (self.check(line).corrupt for line in inputs) + if corrupt is not None + ) + + def part_2(self, inputs: Input) -> int: + scores = sorted( + reduce( + lambda a, b: 5 * a + b, + (INCOMPLETE_SCORES[i] for i in incomplete), + ) + for incomplete in (self.check(line).incomplete for line in inputs) + if incomplete is not None + ) + log(scores) + return scores[len(scores) // 2] + + @aoc_samples( + ( + ("part_1", TEST, 26_397), + ("part_2", TEST, 288_957), + ) ) - log(scores) - assert len(scores) % 2 == 1 - return scores[len(scores) // 2] + def samples(self) -> None: + pass -TEST = """\ -[({(<(())[]>[[{[]{<()<>> -[(()[<>])]({[<{<<[]>>( -{([(<{}[<>[]}>{[]{[(<()> -(((({<>}<{<{<>}{[]{[]{} -[[<[([]))<([[{}[[()]]] -[{[{({}]{}}([{[{{{}}([] -{<[[]]>}<{[{[{[]{()[[[] -[<(<(<(<{}))><([]([]() -<{([([[(<>()){}]>(<<{{ -<{([{{}}[<[[[<>{}]]]>[]] -""".splitlines() +solution = Solution(2021, 10) def main() -> None: - puzzle = aocd.models.Puzzle(2021, 10) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST) == 26_397 - assert part_2(TEST) == 288_957 - - inputs = my_aocd.get_input(puzzle.year, puzzle.day, 94) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2021_11.py b/src/main/python/AoC2021_11.py index cb27e125..59296d03 100644 --- a/src/main/python/AoC2021_11.py +++ b/src/main/python/AoC2021_11.py @@ -3,15 +3,32 @@ # Advent of Code 2021 Day 11 # -from aoc import my_aocd -from aoc.grid import Cell, IntGrid -import aocd +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.grid import Cell +from aoc.grid import IntGrid + +TEST = """\ +5483143223 +2745854711 +5264556173 +6141336146 +6357385478 +4167524645 +2176841721 +6882881134 +4846848554 +5283751526 +""" class Flashes: value: int - def __init__(self): + def __init__(self) -> None: self.value = 0 def increment(self) -> None: @@ -21,78 +38,61 @@ def get(self) -> int: return self.value -def _parse(inputs: tuple[str]) -> IntGrid: - return IntGrid([[int(_) for _ in list(r)] for r in inputs]) +Input = list[str] +Output1 = int +Output2 = int -def _flash(grid: IntGrid, c: Cell, flashes: Flashes) -> None: - grid.set_value(c, 0) - flashes.increment() - for n in grid.get_all_neighbours(c): - if grid.get_value(n) == 0: - continue - grid.increment(n) - if grid.get_value(n) > 9: - _flash(grid, n, flashes) +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data) + def flash(self, grid: IntGrid, c: Cell, flashes: int) -> int: + grid.set_value(c, 0) + flashes += 1 + for n in grid.get_all_neighbours(c): + if grid.get_value(n) == 0: + continue + grid.increment(n) + if grid.get_value(n) > 9: + flashes = self.flash(grid, n, flashes) + return flashes -def _cycle(grid: IntGrid) -> int: - [grid.increment(c) for c in grid.get_cells()] - flashes = Flashes() - [ - _flash(grid, c, flashes) - for c in grid.get_cells() - if grid.get_value(c) > 9 - ] - return flashes.get() + def _cycle(self, grid: IntGrid) -> int: + for c in grid.get_cells(): + grid.increment(c) + flashes = 0 + for c in (c for c in grid.get_cells() if grid.get_value(c) > 9): + flashes = self.flash(grid, c, flashes) + return flashes + def part_1(self, input: Input) -> Output1: + grid = IntGrid.from_strings(input) + return sum(self._cycle(grid) for _ in range(100)) -def part_1(inputs: tuple[str]) -> int: - grid = _parse(inputs) - flashes = 0 - for _ in range(100): - flashes += _cycle(grid) - return flashes + def part_2(self, input: Input) -> Output2: + grid = IntGrid.from_strings(input) + cycle = flashes = 0 + while flashes != grid.size(): + cycle += 1 + flashes = self._cycle(grid) + return cycle + @aoc_samples( + ( + ("part_1", TEST, 1656), + ("part_2", TEST, 195), + ) + ) + def samples(self) -> None: + pass -def part_2(inputs: tuple[str]) -> int: - grid = _parse(inputs) - cycle = 1 - while True: - flashes = _cycle(grid) - if flashes == grid.size(): - break - cycle += 1 - return cycle - -TEST = """\ -5483143223 -2745854711 -5264556173 -6141336146 -6357385478 -4167524645 -2176841721 -6882881134 -4846848554 -5283751526 -""".splitlines() +solution = Solution(2021, 11) def main() -> None: - puzzle = aocd.models.Puzzle(2021, 11) - my_aocd.print_header(puzzle.year, puzzle.day) - - assert part_1(TEST) == 1656 - assert part_2(TEST) == 195 - - inputs = my_aocd.get_input(puzzle.year, puzzle.day, 10) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) + solution.run(sys.argv) if __name__ == "__main__": diff --git a/src/main/python/AoC2021_12.py b/src/main/python/AoC2021_12.py index c1bb1161..3f08bd1b 100644 --- a/src/main/python/AoC2021_12.py +++ b/src/main/python/AoC2021_12.py @@ -4,100 +4,15 @@ # from __future__ import annotations -from typing import NamedTuple, Callable -from collections import defaultdict -from aoc import my_aocd -import aocd - - -class Cave(NamedTuple): - name: str - - def is_small(self) -> bool: - return self.name.islower() - - def is_start(self): - return self.name == "start" - - def is_end(self): - return self.name == "end" - -class System(NamedTuple): - tunnels: dict - start: Cave - end: Cave - - -class State(NamedTuple): - small_caves_seen: set[Cave] - small_caves_seen_twice: set[Cave] - - @classmethod - def copy_of(cls, state: State) -> State: - return State({_ for _ in state.small_caves_seen}, - {_ for _ in state.small_caves_seen_twice}) - - -def _parse(inputs: tuple[str]) -> System: - tunnels = defaultdict(list) - for line in inputs: - from_, to = [Cave(_) for _ in line.split('-')] - tunnels[from_].append(to) - if from_.is_start(): - start = from_ - elif to.is_end(): - end = to - tunnels[to].append(from_) - return System(tunnels, start, end) - - -def _dfs(system: System, start: Cave, end: Cave, state: State, - proceed: Callable, on_path: Callable) -> None: - if start == end: - on_path() - return - for to in system.tunnels[start]: - if proceed(to, state): - new_state = State.copy_of(state) - if to.is_small(): - if to in new_state.small_caves_seen: - new_state.small_caves_seen_twice.add(to) - new_state.small_caves_seen.add(to) - _dfs(system, to, end, new_state, proceed, on_path) - - -def _solve(system: System, proceed: Callable) -> int: - cnt = 0 - - def increment_cnt() -> None: - nonlocal cnt - cnt += 1 - - state = State({system.start}, {}) - _dfs(system, system.start, system.end, state, - proceed, increment_cnt) - return cnt - - -def part_1(inputs: tuple[str]) -> int: - system = _parse(inputs) - return _solve(system, - lambda to, state: - not to.is_small() - or to not in state.small_caves_seen) - - -def part_2(inputs: tuple[str]) -> int: - system = _parse(inputs) - return _solve(system, - lambda to, state: - not to.is_small() - or to not in state.small_caves_seen - or (not to.is_start() - and not to.is_end() - and not state.small_caves_seen_twice)) +import sys +from collections import defaultdict +from typing import Callable +from typing import NamedTuple +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples TEST1 = """\ start-A @@ -107,7 +22,7 @@ def part_2(inputs: tuple[str]) -> int: b-d A-end b-end -""".splitlines() +""" TEST2 = """\ dc-end HN-start @@ -119,7 +34,7 @@ def part_2(inputs: tuple[str]) -> int: kj-sa kj-HN kj-dc -""".splitlines() +""" TEST3 = """\ fs-end he-DX @@ -139,27 +54,134 @@ def part_2(inputs: tuple[str]) -> int: zg-he pj-fs start-RW -""".splitlines() +""" -def main() -> None: - puzzle = aocd.models.Puzzle(2021, 12) - my_aocd.print_header(puzzle.year, puzzle.day) +class Cave(NamedTuple): + name: str + + def is_small(self) -> bool: + return self.name.islower() + + def is_start(self) -> bool: + return self.name == "start" + + def is_end(self) -> bool: + return self.name == "end" + + +class System(NamedTuple): + tunnels: dict[Cave, list[Cave]] + start: Cave + end: Cave + + +class State(NamedTuple): + small_caves_seen: set[Cave] + small_caves_seen_twice: set[Cave] + + @classmethod + def copy_of(cls, state: State) -> State: + return State( + {_ for _ in state.small_caves_seen}, + {_ for _ in state.small_caves_seen_twice}, + ) + + +Input = System +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, inputs: InputData) -> Input: + tunnels = defaultdict(list) + for line in inputs: + from_, to = [Cave(_) for _ in line.split("-")] + tunnels[from_].append(to) + for _ in {from_, to}: + if _.is_start(): + start = _ + if _.is_end(): + end = _ + tunnels[to].append(from_) + return System(tunnels, start, end) + + def _dfs( + self, + system: System, + start: Cave, + end: Cave, + state: State, + proceed: Callable[[Cave, State], bool], + on_path: Callable[[], None], + ) -> None: + if start == end: + on_path() + return + for to in system.tunnels[start]: + if proceed(to, state): + new_state = State.copy_of(state) + if to.is_small(): + if to in new_state.small_caves_seen: + new_state.small_caves_seen_twice.add(to) + new_state.small_caves_seen.add(to) + self._dfs(system, to, end, new_state, proceed, on_path) + + def _solve( + self, system: System, proceed: Callable[[Cave, State], bool] + ) -> int: + cnt = 0 + + def increment_cnt() -> None: + nonlocal cnt + cnt += 1 + + state = State({system.start}, set()) + self._dfs( + system, system.start, system.end, state, proceed, increment_cnt + ) + return cnt + + def part_1(self, system: Input) -> int: + return self._solve( + system, + lambda to, state: not to.is_small() + or to not in state.small_caves_seen, + ) + + def part_2(self, system: Input) -> int: + return self._solve( + system, + lambda to, state: not to.is_small() + or to not in state.small_caves_seen + or ( + not to.is_start() + and not to.is_end() + and not state.small_caves_seen_twice + ), + ) + + @aoc_samples( + ( + ("part_1", TEST1, 10), + ("part_1", TEST2, 19), + ("part_1", TEST3, 226), + ("part_2", TEST1, 36), + ("part_2", TEST2, 103), + ("part_2", TEST3, 3509), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2021, 12) - assert part_1(TEST1) == 10 - assert part_1(TEST2) == 19 - assert part_1(TEST3) == 226 - assert part_2(TEST1) == 36 - assert part_2(TEST2) == 103 - assert part_2(TEST3) == 3509 - inputs = my_aocd.get_input(puzzle.year, puzzle.day, 21) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2021_15.py b/src/main/python/AoC2021_15.py index aca90596..14d2dc03 100644 --- a/src/main/python/AoC2021_15.py +++ b/src/main/python/AoC2021_15.py @@ -4,11 +4,12 @@ # from collections.abc import Iterator + +import aocd from aoc import my_aocd from aoc.geometry import Direction -from aoc.graph import a_star +from aoc.graph import Dijkstra from aoc.grid import IntGrid -import aocd START = (0, 0) Cell = tuple[int, int] @@ -47,11 +48,11 @@ def _find_neighbours( def _solve(grid: IntGrid, tiles: int) -> int: seen = set[Cell]() end = (tiles * grid.get_height() - 1, tiles * grid.get_width() - 1) - risk, _, _ = a_star( + risk = Dijkstra.distance( START, lambda cell: cell == end, lambda cell: _find_neighbours(grid, tiles, seen, *cell), - lambda cell: _get_risk(grid, *cell), + lambda curr, nxt: _get_risk(grid, *nxt), ) return risk diff --git a/src/main/python/AoC2021_23.py b/src/main/python/AoC2021_23.py index 5deb28fc..0f10eece 100644 --- a/src/main/python/AoC2021_23.py +++ b/src/main/python/AoC2021_23.py @@ -4,23 +4,21 @@ # from __future__ import annotations + import heapq +import sys from typing import NamedTuple -from copy import deepcopy -from aoc import my_aocd -import aocd -from aoc.common import log +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.common import log -A = 'A' -B = 'B' -C = 'C' -D = 'D' -EMPTY = '.' -WALL = '#' -ROOMS = {'room_a': 2, 'room_b': 4, 'room_c': 6, 'room_d': 8} +A, B, C, D, EMPTY, WALL = "A", "B", "C", "D", ".", "#" +ROOMS = {"room_a": 2, "room_b": 4, "room_c": 6, "room_d": 8} ENERGY = {A: 1, B: 10, C: 100, D: 1_000} HALLWAY_SIZE = 11 +EMPTY_HALL = [EMPTY] * 11 class Room(NamedTuple): @@ -29,11 +27,9 @@ class Room(NamedTuple): amphipods: list[str] def __hash__(self) -> int: - return hash(( - self.destination_for, - self.capacity, - tuple(self.amphipods) - )) + return hash( + (self.destination_for, self.capacity, tuple(self.amphipods)) + ) def completeness(self) -> int: return sum(_ == self.destination_for for _ in self.amphipods) @@ -44,14 +40,15 @@ def empty_count(self) -> int: def is_complete(self) -> bool: return self.capacity == self.completeness() - def available_for_move(self) -> int: + def available_for_move(self) -> int | None: if self.completeness() + self.empty_count() == self.capacity: return None for i, _ in enumerate(reversed(self.amphipods)): if _ != EMPTY: return self.capacity - 1 - i + raise RuntimeError - def vacancy_for(self, amphipod: str) -> int: + def vacancy_for(self, amphipod: str) -> int | None: assert amphipod != EMPTY if amphipod != self.destination_for: return None @@ -74,6 +71,35 @@ class Diagram(NamedTuple): room_c: Room room_d: Room + @classmethod + def from_strings(cls, strings: list[str]) -> Diagram: + level = 0 + hallway = [_ for _ in strings[1].strip(WALL)] + amphipods_a, amphipods_b, amphipods_c, amphipods_d = [], [], [], [] + for line in reversed(strings[2:-1]): + a, b, c, d = (_ for _ in line.strip() if _ != WALL) + amphipods_a.append(a) + amphipods_b.append(b) + amphipods_c.append(c) + amphipods_d.append(d) + level += 1 + return Diagram( + Room(EMPTY, HALLWAY_SIZE, hallway), + Room(A, level, amphipods_a), + Room(B, level, amphipods_b), + Room(C, level, amphipods_c), + Room(D, level, amphipods_d), + ) + + def copy(self) -> Diagram: + return Diagram( + Room(EMPTY, HALLWAY_SIZE, self.hallway.amphipods[:]), + Room(A, self.room_a.capacity, self.room_a.amphipods[:]), + Room(B, self.room_b.capacity, self.room_b.amphipods[:]), + Room(C, self.room_c.capacity, self.room_c.amphipods[:]), + Room(D, self.room_d.capacity, self.room_d.amphipods[:]), + ) + def assert_valid(self) -> None: assert len(self.hallway.amphipods) == self.hallway.capacity assert len(self.room_a.amphipods) == self.room_a.capacity @@ -89,11 +115,13 @@ def assert_valid(self) -> None: assert room.amphipods[i + 1] == EMPTY def is_complete(self) -> bool: - return self.hallway.amphipods == [EMPTY] * 11 \ - and self.room_a.is_complete() \ - and self.room_b.is_complete() \ - and self.room_c.is_complete() \ - and self.room_d.is_complete() + return ( + self.hallway.amphipods == EMPTY_HALL + and self.room_a.is_complete() + and self.room_b.is_complete() + and self.room_c.is_complete() + and self.room_d.is_complete() + ) def empty_in_hallway_in_range(self, range_: list[int]) -> set[int]: free = set[int]() @@ -108,11 +136,13 @@ def empty_in_hallway_in_range(self, range_: list[int]) -> set[int]: def empty_in_hallway_left_from(self, room: str) -> set[int]: return self.empty_in_hallway_in_range( - list(range(ROOMS[room] - 1, -1, -1))) + list(range(ROOMS[room] - 1, -1, -1)) + ) def empty_in_hallway_right_from(self, room: str) -> set[int]: return self.empty_in_hallway_in_range( - list(range(ROOMS[room] + 1, self.hallway.capacity))) + list(range(ROOMS[room] + 1, self.hallway.capacity)) + ) def moves_to_hallway(self) -> set[tuple[str, int, int]]: moves = set[tuple[str, int, int]]() @@ -128,7 +158,7 @@ def moves_to_hallway(self) -> set[tuple[str, int, int]]: return moves def all_empty(self, pos: int, room_name: str) -> bool: - assert room_name not in ROOMS.values() + assert room_name in ROOMS assert self.hallway.amphipods[pos] != EMPTY room_pos = ROOMS[room_name] assert room_pos != pos @@ -147,8 +177,11 @@ def moves_from_hallway(self) -> set[tuple[str, int, int]]: for from_, amphipod in enumerate(self.hallway.amphipods): if amphipod == EMPTY: continue - rooms = [r for r in ROOMS.keys() - if getattr(self, r).destination_for == amphipod] + rooms = [ + r + for r in ROOMS.keys() + if getattr(self, r).destination_for == amphipod + ] assert len(rooms) == 1 room_name = rooms[0] if not self.all_empty(from_, room_name): @@ -164,18 +197,18 @@ def energy_for_move_to_hallway(self, move: tuple[str, int, int]) -> int: room = getattr(self, room_name) hor = abs(ROOMS[room_name] - to) ver = room.capacity - from_ - return (hor + ver) * ENERGY[room.amphipods[from_]] + return int((hor + ver) * ENERGY[room.amphipods[from_]]) def energy_for_move_from_hallway(self, move: tuple[str, int, int]) -> int: room_name, from_, to = move room = getattr(self, room_name) hor = abs(ROOMS[room_name] - from_) ver = room.capacity - to - return (hor + ver) * ENERGY[self.hallway.amphipods[from_]] + return int((hor + ver) * ENERGY[self.hallway.amphipods[from_]]) def do_move_from_hallway(self, move: tuple[str, int, int]) -> Diagram: room_name, from_, to = move - copy = deepcopy(self) + copy = self.copy() room = getattr(copy, room_name) assert copy.hallway.amphipods[from_] == room.destination_for temp = room.amphipods[to] @@ -188,7 +221,7 @@ def do_move_from_hallway(self, move: tuple[str, int, int]) -> Diagram: def do_move_to_hallway(self, move: tuple[str, int, int]) -> Diagram: room_name, from_, to = move - copy = deepcopy(self) + copy = self.copy() room = getattr(copy, room_name) temp = copy.hallway.amphipods[to] assert temp == EMPTY @@ -199,91 +232,83 @@ def do_move_to_hallway(self, move: tuple[str, int, int]) -> Diagram: return copy -def _parse(inputs: tuple[str]) -> int: - level = 0 - hallway = [_ for _ in inputs[1].strip(WALL)] - amphipods_a, amphipods_b, amphipods_c, amphipods_d = [], [], [], [] - for line in reversed(inputs[2:-1]): - a, b, c, d = (_ for _ in line.strip() if _ != WALL) - amphipods_a.append(a) - amphipods_b.append(b) - amphipods_c.append(c) - amphipods_d.append(d) - level += 1 - return Diagram( - Room(EMPTY, HALLWAY_SIZE, hallway), - Room(A, level, amphipods_a), - Room(B, level, amphipods_b), - Room(C, level, amphipods_c), - Room(D, level, amphipods_d)) - - -def _solve(start: Diagram) -> int: - visited = set[Diagram]() - to_visit = list[tuple[int, Diagram]]([(0, start)]) - while to_visit: - energy, diagram = heapq.heappop(to_visit) - if diagram.is_complete(): - return energy - if diagram in visited: - continue - visited.add(diagram) - - for move in diagram.moves_from_hallway(): - new_diagram = diagram.do_move_from_hallway(move) - new_energy = energy + diagram.energy_for_move_from_hallway(move) - heapq.heappush(to_visit, (new_energy, new_diagram)) - for move in diagram.moves_to_hallway(): - new_diagram = diagram.do_move_to_hallway(move) - new_energy = energy + diagram.energy_for_move_to_hallway(move) - heapq.heappush(to_visit, (new_energy, new_diagram)) - - -def part_1(inputs: tuple[str]) -> int: - diagram = _parse(inputs) - log(diagram) - diagram.assert_valid() - return _solve(diagram) - - -def part_2(inputs: tuple[str]) -> int: - inputs_2 = ( - inputs[0], - inputs[1], - inputs[2], - " #D#C#B#A#", - " #D#B#A#C#", - inputs[3], - inputs[4]) - diagram = _parse(inputs_2) - log(diagram) - diagram.assert_valid() - return _solve(diagram) - - TEST = """\ ############# #...........# ###B#C#B#D### #A#D#C#A# ######### -""".splitlines() +""" +Input = list[str] +Output1 = int +Output2 = int -def main() -> None: - puzzle = aocd.models.Puzzle(2021, 23) - my_aocd.print_header(puzzle.year, puzzle.day) - assert part_1(TEST) == 12_521 - assert part_2(TEST) == 44_169 +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data) + + def solve(self, start: Diagram) -> int: + visited = set[Diagram]() + to_visit = list[tuple[int, Diagram]]([(0, start)]) + while to_visit: + energy, diagram = heapq.heappop(to_visit) + if diagram.is_complete(): + return energy + if diagram in visited: + continue + visited.add(diagram) + + for move in diagram.moves_from_hallway(): + new_diagram = diagram.do_move_from_hallway(move) + new_energy = energy + diagram.energy_for_move_from_hallway( + move + ) + heapq.heappush(to_visit, (new_energy, new_diagram)) + for move in diagram.moves_to_hallway(): + new_diagram = diagram.do_move_to_hallway(move) + new_energy = energy + diagram.energy_for_move_to_hallway(move) + heapq.heappush(to_visit, (new_energy, new_diagram)) + raise RuntimeError("unsolvable") + + def part_1(self, input: Input) -> Output1: + diagram = Diagram.from_strings(input) + log(diagram) + diagram.assert_valid() + return self.solve(diagram) + + def part_2(self, input: Input) -> Output2: + input_2 = [ + input[0], + input[1], + input[2], + " #D#C#B#A#", + " #D#B#A#C#", + input[3], + input[4], + ] + diagram = Diagram.from_strings(input_2) + log(diagram) + diagram.assert_valid() + return self.solve(diagram) + + @aoc_samples( + ( + ("part_1", TEST, 12_521), + ("part_2", TEST, 44_169), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2021, 23) + - inputs = my_aocd.get_input(puzzle.year, puzzle.day, 5) - result1 = part_1(inputs) - print(f"Part 1: {result1}") - result2 = part_2(inputs) - print(f"Part 2: {result2}") - my_aocd.check_results(puzzle, result1, result2) +def main() -> None: + solution.run(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/main/python/AoC2022_08.py b/src/main/python/AoC2022_08.py index 237bba78..b1811baa 100644 --- a/src/main/python/AoC2022_08.py +++ b/src/main/python/AoC2022_08.py @@ -21,14 +21,7 @@ def capital_directions(grid: IntGrid, cell: Cell) -> list[Iterator[Cell]]: def ignoring_borders(grid: IntGrid) -> Iterator[Cell]: - return ( - c - for c in grid.get_cells() - if 1 <= c.row - and c.row < grid.get_max_row_index() - and 1 <= c.col - and c.col < grid.get_max_col_index() - ) + return grid.get_cells_without_border() def visible_from_outside(grid: IntGrid, cell: Cell) -> bool: diff --git a/src/main/python/AoC2022_15.py b/src/main/python/AoC2022_15.py index 34354e78..ea1a5294 100644 --- a/src/main/python/AoC2022_15.py +++ b/src/main/python/AoC2022_15.py @@ -3,83 +3,22 @@ # Advent of Code 2022 Day 15 # - +import itertools import re -from collections import defaultdict - -from aoc.common import aoc_main - -Sensors = list[tuple[int, int, int]] -Beacons = dict[int, set[int]] -Range = tuple[int, int] - - -def _parse(inputs: tuple[str, ...]) -> tuple[Sensors, Beacons]: - sensors = Sensors() - beacons = defaultdict[int, set[int]](set) - for line in inputs: - sx, sy, bx, by = map(int, re.findall(r"-?[0-9]+", line)) - sensors.append((sx, sy, abs(sx - bx) + abs(sy - by))) - beacons[by].add(bx) - return sensors, beacons - - -def _get_ranges(sensors: Sensors, y: int) -> list[Range]: - ranges = list[Range]() - for sx, sy, d in sensors: - dy = abs(sy - y) - if dy > d: - continue - ranges.append((sx - d + dy, sx + d - dy)) - merged = list[Range]() - for s_min, s_max in sorted(ranges): - if len(merged) == 0: - merged.append((s_min, s_max)) - continue - last_min, last_max = merged[-1] - if s_min <= last_max: - merged[-1] = (last_min, max(last_max, s_max)) - continue - merged.append((s_min, s_max)) - return merged - - -def solve_1(inputs: tuple[str, ...], y: int) -> int: - sensors, beacons = _parse(inputs) - return sum( - ( - r_max - - r_min - + 1 - - sum(1 for bx in beacons[y] if r_min <= bx <= r_max) - ) - for r_min, r_max in _get_ranges(sensors, y) - ) - - -def solve_2(inputs: tuple[str, ...], the_max: int) -> int: - sensors, _ = _parse(inputs) - for y in range(the_max, 0, -1): - ranges = _get_ranges(sensors, y) - x = 0 - while x <= the_max: - for r_min, r_max in ranges: - if x < r_min: - return x * 4_000_000 + y - x = r_max + 1 - raise RuntimeError() +import sys +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.geometry import Position +from aoc.range import RangeInclusive -def part_1(inputs: tuple[str, ...]) -> int: - return solve_1(inputs, 2_000_000) +Sensors = dict[Position, int] +Beacons = set[Position] +Input = tuple[Sensors, Beacons] +Output1 = int +Output2 = int - -def part_2(inputs: tuple[str, ...]) -> int: - return solve_2(inputs, 4_000_000) - - -TEST = tuple( - """\ +TEST = """\ Sensor at x=2, y=18: closest beacon is at x=-2, y=15 Sensor at x=9, y=16: closest beacon is at x=10, y=16 Sensor at x=13, y=2: closest beacon is at x=15, y=3 @@ -94,14 +33,76 @@ def part_2(inputs: tuple[str, ...]) -> int: Sensor at x=16, y=7: closest beacon is at x=15, y=3 Sensor at x=14, y=3: closest beacon is at x=15, y=3 Sensor at x=20, y=1: closest beacon is at x=15, y=3 -""".splitlines() -) +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + sensors, beacons = Sensors(), Beacons() + for line in input_data: + sx, sy, bx, by = map(int, re.findall(r"-?[0-9]+", line)) + s, b = Position.of(sx, sy), Position.of(bx, by) + sensors[s] = s.manhattan_distance(b) + beacons.add(b) + return sensors, beacons + + def solve_1(self, input: Input, y: int) -> Output1: + sensors, beacons = input + + def get_ranges(y: int) -> list[RangeInclusive]: + ranges = list[RangeInclusive]() + for s, md in sensors.items(): + dy = abs(s.y - y) + if dy > md: + continue + ranges.append( + RangeInclusive.between(s.x - md + dy, s.x + md - dy) + ) + return RangeInclusive.merge(ranges) + + return sum( + r.len - len({b for b in beacons if b.y == y and r.contains(b.x)}) + for r in get_ranges(y) + ) + + def solve_2(self, input: Input, the_max: int) -> Output2: + """https://old.reddit.com/r/adventofcode/comments/zmcn64/2022_day_15_solutions/j0b90nr/""" # noqa E501 + sensors, _ = input + a_coeffs, b_coeffs = set(), set() + for s, md in sensors.items(): + a_coeffs.add(s.y - s.x + md + 1) + a_coeffs.add(s.y - s.x - md - 1) + b_coeffs.add(s.x + s.y + md + 1) + b_coeffs.add(s.x + s.y - md - 1) + return next( + 4_000_000 * p.x + p.y + for p in ( + Position.of((ab[1] - ab[0]) // 2, (ab[0] + ab[1]) // 2) + for ab in itertools.product(a_coeffs, b_coeffs) + if ab[0] < ab[1] and (ab[1] - ab[0]) % 2 == 0 + ) + if 0 < p.x < the_max + and 0 < p.y < the_max + and all(p.manhattan_distance(s) > sensors[s] for s in sensors) + ) + + def part_1(self, input: Input) -> Output1: + return self.solve_1(input, 2_000_000) + + def part_2(self, input: Input) -> Output2: + return self.solve_2(input, 4_000_000) + + def samples(self) -> None: + input = self.parse_input(TEST.splitlines()) + assert self.solve_1(input, 10) == 26 + assert self.solve_2(input, 20) == 56_000_011 + + +solution = Solution(2022, 15) -@aoc_main(2022, 15, part_1, part_2) def main() -> None: - assert solve_1(TEST, 10) == 26 - assert solve_2(TEST, 20) == 56_000_011 + solution.run(sys.argv) if __name__ == "__main__": diff --git a/src/main/python/AoC2022_16.py b/src/main/python/AoC2022_16.py index e11811c1..544dc7b3 100644 --- a/src/main/python/AoC2022_16.py +++ b/src/main/python/AoC2022_16.py @@ -10,7 +10,7 @@ from typing import NamedTuple from aoc.common import aoc_main -from aoc.graph import a_star +from aoc.graph import Dijkstra class Cave(NamedTuple): @@ -51,14 +51,14 @@ def get_distances(self) -> list[list[int]]: ] distances = [[0] * size for i in range(size)] for i in relevant_valves: - _, distance, _ = a_star( + result = Dijkstra.all( i, lambda v: False, lambda v: (_ for _ in self.tunnels[v]), - lambda v: 1, + lambda curr, nxt: 1, ) for j in relevant_valves: - distances[i][j] = distance[j] + distances[i][j] = result.get_distance(j) return distances diff --git a/src/main/python/AoC2022_17.py b/src/main/python/AoC2022_17.py new file mode 100644 index 00000000..67d29f1c --- /dev/null +++ b/src/main/python/AoC2022_17.py @@ -0,0 +1,196 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2022 Day 17 +# + +from __future__ import annotations + +import itertools +import sys +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry import Direction +from aoc.geometry import Position +from aoc.geometry import Vector + +Input = list[Direction] +Output1 = int +Output2 = int + + +TEST = ">>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>" +SHAPES = [ + # 🔲🔲🔲🔲 + {Position(0, 0), Position(1, 0), Position(2, 0), Position(3, 0)}, + # 🔲 + # 🔲🔲🔲 + # 🔲 + { + Position(1, 2), + Position(0, 1), + Position(1, 1), + Position(2, 1), + Position(1, 0), + }, + # 🔲 + # 🔲 + # 🔲🔲 + { + Position(2, 2), + Position(2, 1), + Position(0, 0), + Position(1, 0), + Position(2, 0), + }, + # 🔲 + # 🔲 + # 🔲 + # 🔲 + {Position(0, 0), Position(0, 1), Position(0, 2), Position(0, 3)}, + # 🔲🔲 + # 🔲🔲 + {Position(0, 0), Position(0, 1), Position(1, 0), Position(1, 1)}, +] +WIDTH = 7 +FLOOR = set(map(lambda x: Position(x, -1), range(WIDTH))) +KEEP_ROWS = 55 +LOOP_TRESHOLD = 3_000 +OFFSET_X = 2 +OFFSET_Y = 3 + + +class Rock(NamedTuple): + idx: int + shape: set[Position] + + def move(self, vector: Vector) -> Rock: + new_shape = set(map(lambda p: p.translate(vector), self.shape)) + return Rock(self.idx, new_shape) + + def inside_x(self, start_inclusive: int, end_exclusive: int) -> bool: + return all(start_inclusive <= p.x < end_exclusive for p in self.shape) + + +class State(NamedTuple): + shape: int + tops: tuple[int, ...] + jet: Direction + + +class Cycle(NamedTuple): + cycle: int + top: int + + +class Stack: + def __init__(self, positions: set[Position]) -> None: + self.positions = {_ for _ in positions} + self.tops = Stack.get_tops(positions) + self.top = 0 + + @classmethod + def get_tops(cls, positions: set[Position]) -> dict[int, int]: + return { + k: max(p.y for p in v) + for k, v in itertools.groupby( + sorted(positions, key=lambda p: p.x), lambda p: p.x + ) + } + + def get_tops_normalized(self) -> tuple[int, ...]: + return tuple(map(lambda i: self.top - self.tops[i], range(WIDTH))) + + def overlapped_by(self, rock: Rock) -> bool: + return any(p in self.positions for p in rock.shape) + + def add(self, rock: Rock) -> None: + for p in rock.shape: + self.tops[p.x] = max(self.tops[p.x], p.y) + self.positions.add(p) + self.top = max(self.top, p.y + 1) + self.positions = self.get_top_rows(KEEP_ROWS) + + def get_top_rows(self, n: int) -> set[Position]: + return set(filter(lambda p: p.y > self.top - n, self.positions)) + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [Direction.from_str(s) for s in list(input_data)[0]] + + def solve(self, jets: Input, requested_drops: int) -> int: + stack = Stack(FLOOR) + states = dict[State, list[Cycle]]() + jet_supplier = itertools.cycle(jets) + shape_supplier = enumerate(itertools.cycle(SHAPES)) + + def drop(drop_idx: int) -> State: + shape_idx, shape = next(shape_supplier) + rock = Rock(shape_idx % len(SHAPES), shape).move( + Vector(OFFSET_X, stack.top + OFFSET_Y) + ) + cnt = 0 + while True: + assert cnt < 10_000, "infinite loop" + jet = next(jet_supplier) + state = State(rock.idx, stack.get_tops_normalized(), jet) + if cnt == 1: + states.setdefault(state, []).append( + Cycle(drop_idx, stack.top) + ) + cnt += 1 + moved = rock.move(jet.vector) + if moved.inside_x(0, WIDTH) and not stack.overlapped_by(moved): + rock = moved + moved = rock.move(Direction.DOWN.vector) + if stack.overlapped_by(moved): + break + rock = moved + stack.add(rock) + return state + + drops = 0 + while True: + state = drop(drops) + drops += 1 + if drops == requested_drops: + return stack.top + if drops >= LOOP_TRESHOLD and len(states.get(state, [])) > 1: + cycles = states[state] + loop_size = cycles[1].cycle - cycles[0].cycle + diff = cycles[1].top - cycles[0].top + loops = (requested_drops - drops) // loop_size + left = requested_drops - (drops + loops * loop_size) + for i in range(left): + drop(drops) + drops += 1 + return stack.top + loops * diff + + def part_1(self, jets: Input) -> Output1: + return self.solve(jets, 2022) + + def part_2(self, jets: Input) -> Output2: + return self.solve(jets, 1_000_000_000_000) + + @aoc_samples( + ( + ("part_1", TEST, 3_068), + ("part_2", TEST, 1_514_285_714_288), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2022, 17) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_14.py b/src/main/python/AoC2023_14.py new file mode 100644 index 00000000..e38af5a0 --- /dev/null +++ b/src/main/python/AoC2023_14.py @@ -0,0 +1,181 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 14 +# + +import sys +from collections import defaultdict + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.common import log +from aoc.grid import CharGrid + +Input = CharGrid +Output1 = int +Output2 = int + + +TEST = """\ +O....#.... +O.OO#....# +.....##... +OO.#O....O +.O.....O#. +O.#..O.#.# +..O..#O..O +.......O.. +#....###.. +#OO..#.... +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + grid: CharGrid + height: int + width: int + + def parse_input(self, input_data: InputData) -> Input: + return CharGrid.from_strings([line for line in input_data]) + + def tilt_up(self) -> set[tuple[int, int]]: + os = set[tuple[int, int]]() + for c in range(self.width): + last_cube = 0 + count = 0 + for r in range(self.height): + val = self.grid.values[r][c] + if val == "#": + last_cube = r + 1 + count = 0 + elif val == "O": + new_row = last_cube + count + os.add((new_row, c)) + count += 1 + return os + + def tilt_down(self) -> set[tuple[int, int]]: + os = set[tuple[int, int]]() + for c in range(self.width): + last_cube = self.height - 1 + count = 0 + for r in range(self.height - 1, -1, -1): + val = self.grid.values[r][c] + if val == "#": + last_cube = r - 1 + count = 0 + elif val == "O": + new_row = last_cube - count + os.add((new_row, c)) + count += 1 + return os + + def tilt_left(self) -> set[tuple[int, int]]: + os = set[tuple[int, int]]() + for r in range(self.height): + last_cube = 0 + count = 0 + for c in range(self.width): + val = self.grid.values[r][c] + if val == "#": + last_cube = c + 1 + count = 0 + elif val == "O": + new_col = last_cube + count + os.add((r, new_col)) + count += 1 + return os + + def tilt_right(self) -> set[tuple[int, int]]: + os = set[tuple[int, int]]() + for r in range(self.height): + last_cube = self.width - 1 + count = 0 + for c in range(self.width - 1, -1, -1): + val = self.grid.values[r][c] + if val == "#": + last_cube = c - 1 + count = 0 + elif val == "O": + new_col = last_cube - count + os.add((r, new_col)) + count += 1 + return os + + def spin_cycle(self) -> set[tuple[int, int]]: + os = self.tilt_up() + self.redraw(os) + os = self.tilt_left() + self.redraw(os) + os = self.tilt_down() + self.redraw(os) + os = self.tilt_right() + self.redraw(os) + return os + + def redraw(self, os: set[tuple[int, int]]) -> None: + for r in range(self.height): + for c in range(self.width): + val = self.grid.values[r][c] + if (r, c) in os: + self.grid.values[r][c] = "O" + elif val != "#": + self.grid.values[r][c] = "." + + def calc_load(self, os: set[tuple[int, int]]) -> int: + return sum(self.height - row for row, _ in os) + + def part_1(self, grid_in: Input) -> Output1: + self.grid = grid_in + self.height = self.grid.get_height() + self.width = self.grid.get_width() + os = self.tilt_up() + self.redraw(os) + return self.calc_load(os) + + def part_2(self, grid_in: Input) -> Output2: + self.grid = grid_in + self.height = self.grid.get_height() + self.width = self.grid.get_width() + d = defaultdict[frozenset[tuple[int, int]], list[int]](list) + i = 0 + while True: + if i % 100 == 0: + log(i) + i += 1 + os = self.spin_cycle() + key = frozenset(os) + d[key].append(i) + if i > 100: + cycle = d[key] + if len(cycle) < 2: + continue + period = cycle[1] - cycle[0] + loops = (1_000_000_000 - i) // period + left = 1_000_000_000 - (i + loops * period) + log(f"{i=}, {d[key]=}, {period=}, {loops=}, {left=}") + assert i + loops * period + left == 1_000_000_000 + for i in range(left): + os = self.spin_cycle() + return self.calc_load(os) + + @aoc_samples( + ( + ("part_1", TEST, 136), + ("part_2", TEST, 64), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2023, 14) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_15.py b/src/main/python/AoC2023_15.py new file mode 100644 index 00000000..c4765f80 --- /dev/null +++ b/src/main/python/AoC2023_15.py @@ -0,0 +1,85 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 15 +# + +import sys +from functools import reduce + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = list[str] +Output1 = int +Output2 = int + + +TEST = """\ +rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7 +""" + + +def hash(s: str) -> int: + return reduce( + lambda acc, ch: ((acc + ord(ch)) * 17) % 256, (ch for ch in s), 0 + ) + + +class Boxes: + def __init__(self) -> None: + self.boxes: list[dict[str, int]] = [{} for _ in range(256)] + + def add_lens(self, label: str, focal_length: int) -> None: + self.boxes[hash(label)][label] = focal_length + + def remove_lens(self, label: str) -> None: + box = self.boxes[hash(label)] + if label in box: + del box[label] + + def get_total_focusing_power(self) -> int: + return sum( + b * i * focal_length + for b, box in enumerate(self.boxes, start=1) + for i, focal_length in enumerate(box.values(), start=1) + ) + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(input_data)[0].split(",") + + def part_1(self, steps: Input) -> Output1: + return sum(hash(step) for step in steps) + + def part_2(self, steps: Input) -> Output2: + boxes = Boxes() + for step in steps: + if "=" in step: + label, fl = step.split("=") + boxes.add_lens(label, int(fl)) + else: + label = step[:-1] + boxes.remove_lens(label) + return boxes.get_total_focusing_power() + + @aoc_samples( + ( + ("part_1", TEST, 1320), + ("part_2", TEST, 145), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2023, 15) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_16.py b/src/main/python/AoC2023_16.py new file mode 100644 index 00000000..f07118e8 --- /dev/null +++ b/src/main/python/AoC2023_16.py @@ -0,0 +1,142 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 16 +# + +import itertools +import sys +from typing import Iterator +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry import Direction +from aoc.geometry import Turn +from aoc.graph import flood_fill +from aoc.grid import Cell +from aoc.grid import CharGrid + +TEST = """\ +.|...\\.... +|.-.\\..... +.....|-... +........|. +.......... +.........\\ +..../.\\\\.. +.-.-/..|.. +.|....-|.\\ +..//.|.... +""" + + +class Beam(NamedTuple): + cell: Cell + dir: Direction + + +class Contraption(NamedTuple): + grid: CharGrid + + def _get_energised(self, initial_beam: Beam) -> int: + def adjacent(beam: Beam) -> Iterator[Beam]: + val = self.grid.get_value(beam.cell) + if ( + val == "." + or (val == "|" and beam.dir.is_vertical) + or (val == "-" and beam.dir.is_horizontal) + ): + yield Beam(beam.cell.at(beam.dir), beam.dir) + elif val == "/" and beam.dir.is_horizontal: + new_dir = beam.dir.turn(Turn.LEFT) + yield Beam(beam.cell.at(new_dir), new_dir) + elif val == "/" and beam.dir.is_vertical: + new_dir = beam.dir.turn(Turn.RIGHT) + yield Beam(beam.cell.at(new_dir), new_dir) + elif val == "\\" and beam.dir.is_horizontal: + new_dir = beam.dir.turn(Turn.RIGHT) + yield Beam(beam.cell.at(new_dir), new_dir) + elif val == "\\" and beam.dir.is_vertical: + new_dir = beam.dir.turn(Turn.LEFT) + yield Beam(beam.cell.at(new_dir), new_dir) + elif val == "|" and beam.dir.is_horizontal: + yield Beam(beam.cell.at(Direction.UP), Direction.UP) + yield Beam(beam.cell.at(Direction.DOWN), Direction.DOWN) + elif val == "-" and beam.dir.is_vertical: + yield Beam(beam.cell.at(Direction.LEFT), Direction.LEFT) + yield Beam(beam.cell.at(Direction.RIGHT), Direction.RIGHT) + else: + raise RuntimeError("unsolvable") + + energised = flood_fill( + initial_beam, + lambda beam: ( + b for b in adjacent(beam) if self.grid.is_in_bounds(b.cell) + ), + ) + return len({beam.cell for beam in energised}) + + def get_initial_energy(self) -> int: + return self._get_energised(Beam(Cell(0, 0), Direction.RIGHT)) + + def get_maximal_energy(self) -> int: + return max( + self._get_energised(beam) + for beam in itertools.chain( + ( + Beam(Cell(row, 0), Direction.RIGHT) + for row in range(self.grid.get_height()) + ), + ( + Beam(Cell(row, self.grid.get_width() - 1), Direction.LEFT) + for row in range(self.grid.get_height()) + ), + ( + Beam(Cell(0, col), Direction.DOWN) + for col in range(self.grid.get_width()) + ), + ( + Beam(Cell(self.grid.get_height() - 1, col), Direction.UP) + for col in range(self.grid.get_width()) + ), + ) + ) + + +Input = Contraption +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return Contraption( + CharGrid.from_strings([line for line in input_data]) + ) + + def part_1(self, contraption: Input) -> Output1: + return contraption.get_initial_energy() + + def part_2(self, contraption: Input) -> Output2: + return contraption.get_maximal_energy() + + @aoc_samples( + ( + ("part_1", TEST, 46), + ("part_2", TEST, 51), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2023, 16) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_17.py b/src/main/python/AoC2023_17.py new file mode 100644 index 00000000..ae59989e --- /dev/null +++ b/src/main/python/AoC2023_17.py @@ -0,0 +1,98 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 17 +# + +import sys +from collections import defaultdict +from queue import PriorityQueue + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.grid import IntGrid + +Input = IntGrid +Output1 = int +Output2 = int + + +TEST = """\ +2413432311323 +3215453535623 +3255245654254 +3446585845452 +4546657867536 +1438598798454 +4457876987766 +3637877979653 +4654967986887 +4564679986453 +1224686865563 +2546548887735 +4322674655533 +""" + +DIRS = {(0, 1), (0, -1), (1, 0), (-1, 0)} +Move = tuple[int, int, int, int] + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return IntGrid.from_strings([line for line in input_data]) + + def solve(self, grid: Input, min_moves: int, max_moves: int) -> int: + max_r = grid.get_height() - 1 + max_c = grid.get_width() - 1 + + q: PriorityQueue[tuple[int, Move]] = PriorityQueue() + q.put((0, (0, 0, 0, 0))) + best: defaultdict[Move, int] = defaultdict(lambda: int(1e9)) + best[(0, 0, 0, 0)] = 0 + while not q.empty(): + cost, move = q.get() + r, c, dr, dc = move + if r == max_r and c == max_c: + return cost + heatloss = best[move] + for drr, dcc in DIRS - {(dr, dc), (-dr, -dc)}: + rr, cc, hl = r, c, 0 + for i in range(1, max_moves + 1): + rr += drr + cc += dcc + if not (0 <= rr <= max_r and 0 <= cc <= max_c): + break + hl += grid.values[rr][cc] + if i >= min_moves: + n = (rr, cc, drr, dcc) + new_heatloss = heatloss + hl + if new_heatloss < best[n]: + best[n] = new_heatloss + q.put((new_heatloss, n)) + raise RuntimeError("unsolvable") + + def part_1(self, grid: Input) -> Output1: + return self.solve(grid, 1, 3) + + def part_2(self, grid: Input) -> Output2: + return self.solve(grid, 4, 10) + + @aoc_samples( + ( + ("part_1", TEST, 102), + ("part_2", TEST, 94), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2023, 17) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_18.py b/src/main/python/AoC2023_18.py new file mode 100644 index 00000000..d7ca5bdd --- /dev/null +++ b/src/main/python/AoC2023_18.py @@ -0,0 +1,119 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 18 +# + +import sys +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry import Direction +from aoc.geometry import Position + + +class DigInstruction(NamedTuple): + direction: Direction + amount: int + big_direction: Direction + big_amount: int + + +Input = InputData +Output1 = int +Output2 = int + + +TEST = """\ +R 6 (#70c710) +D 5 (#0dc571) +L 2 (#5713f0) +D 2 (#d2c081) +R 2 (#59c680) +D 2 (#411b91) +L 5 (#8ceee2) +U 2 (#caa173) +L 1 (#1b58a2) +U 2 (#caa171) +R 2 (#7807d2) +U 3 (#a77fa3) +L 2 (#015232) +U 2 (#7a21e3) +""" + +DIRS = [ + Direction.RIGHT, + Direction.DOWN, + Direction.LEFT, + Direction.UP, +] + + +class Polygon(NamedTuple): + vertices: list[Position] + + def shoelace(self) -> int: + size = len(self.vertices) + s = sum( + self.vertices[i].x + * (self.vertices[(i + 1) % size].y - self.vertices[i - 1].y) + for i in range(size) + ) + return abs(s) // 2 + + def circumference(self) -> int: + return sum( + self.vertices[i].manhattan_distance(self.vertices[i - 1]) + for i in range(1, len(self.vertices)) + ) + + def inside_area(self) -> int: + return self.shoelace() + self.circumference() // 2 + 1 + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return input_data + + def solve(self, instructions: list[tuple[Direction, int]]) -> int: + vertices = [Position(0, 0)] + for instruction in instructions: + vertices.append( + vertices[-1].translate(instruction[0].vector, instruction[1]) + ) + return Polygon(vertices).inside_area() + + def part_1(self, input: Input) -> Output1: + instructions = [ + (Direction.from_str(d), int(a)) + for d, a, _ in (line.split() for line in input) + ] + return self.solve(instructions) + + def part_2(self, input: Input) -> Output2: + instructions = [ + (DIRS[int(hx[7])], int(hx[2:7], 16)) + for _, _, hx in (line.split() for line in input) + ] + return self.solve(instructions) + + @aoc_samples( + ( + ("part_1", TEST, 62), + ("part_2", TEST, 952408144115), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2023, 18) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_19.py b/src/main/python/AoC2023_19.py new file mode 100644 index 00000000..d86bedbe --- /dev/null +++ b/src/main/python/AoC2023_19.py @@ -0,0 +1,249 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 19 +# + +from __future__ import annotations + +import sys +from collections import defaultdict +from math import prod +from typing import NamedTuple + +from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.common import clog + +TEST = """\ +px{a<2006:qkq,m>2090:A,rfg} +pv{a>1716:R,A} +lnx{m>1548:A,A} +rfg{s<537:gd,x>2440:R,A} +qs{s>3448:A,lnx} +qkq{x<1416:A,crn} +crn{x>2662:A,R} +in{s<1351:px,qqz} +qqz{s>2770:qs,m<1801:hdj,R} +gd{a>3333:R,R} +hdj{m>838:A,pv} + +{x=787,m=2655,a=1222,s=2876} +{x=1679,m=44,a=2067,s=496} +{x=2036,m=264,a=79,s=2244} +{x=2461,m=1339,a=466,s=291} +{x=2127,m=1623,a=2188,s=1013} +""" + +Range = tuple[int, int] +ACCEPTED = "A" +REJECTED = "R" +CONTINUE = "=continue=" +CATCHALL = "catch-all" +IN = "in" + + +class Part(NamedTuple): + x: int + m: int + a: int + s: int + + @classmethod + def from_str(cls, string: str) -> Part: + x, m, a, s = [int(sp.split("=")[1]) for sp in string[1:-1].split(",")] + return Part(x, m, a, s) + + def score(self) -> int: + return sum([self.x, self.m, self.a, self.s]) + + +class PartRange(NamedTuple): + x: Range + m: Range + a: Range + s: Range + + @classmethod + def from_part(cls, part: Part) -> PartRange: + return PartRange( + (part.x, part.x), + (part.m, part.m), + (part.a, part.a), + (part.s, part.s), + ) + + def copy_with(self, prop: str, value: Range) -> PartRange: + return PartRange( + value if prop == "x" else self.x, + value if prop == "m" else self.m, + value if prop == "a" else self.a, + value if prop == "s" else self.s, + ) + + def copy(self) -> PartRange: + return PartRange(self.x, self.m, self.a, self.s) + + def matches(self, part: Part) -> bool: + return ( + self.x[0] <= part.x <= self.x[1] + and self.m[0] <= part.m <= self.m[1] + and self.a[0] <= part.a <= self.a[1] + and self.s[0] <= part.s <= self.s[1] + ) + + def score(self) -> int: + return prod(r[1] - r[0] + 1 for r in [self.x, self.m, self.a, self.s]) + + +class Rule(NamedTuple): + operand1: str | None + operation: str + operand2: int | None + result: str + + @classmethod + def from_str(cls, string: str) -> Rule: + if ":" in string: + op, res = string.split(":") + return Rule(op[0], op[1], int(op[2:]), res) + else: + return Rule(None, CATCHALL, None, string) + + def eval(self, range: PartRange) -> list[tuple[PartRange, str]]: + if self.operation == CATCHALL: + return [(range.copy(), self.result)] + else: + assert self.operand1 is not None and self.operand2 is not None + lo, hi = getattr(range, self.operand1) + if self.operation == "<": + match = (lo, self.operand2 - 1) + nomatch = (self.operand2, hi) + else: + match = (self.operand2 + 1, hi) + nomatch = (lo, self.operand2) + ans = list[tuple[PartRange, str]]() + if match[0] <= match[1]: + nr = range.copy_with(self.operand1, match) + ans.append((nr, self.result)) + if nomatch[0] <= nomatch[1]: + nr = range.copy_with(self.operand1, nomatch) + ans.append((nr, CONTINUE)) + return ans + + +class Workflow(NamedTuple): + name: str + rules: list[Rule] + + @classmethod + def from_str(cls, string: str) -> Workflow: + i = string.index("{") + name = string[:i] + rules = [ + Rule.from_str(r) + for r in string[:-1][i + 1 :].split(",") # noqa[E203] + ] + return Workflow(name, rules) + + def eval(self, range: PartRange) -> list[tuple[PartRange, str]]: + ans = list[tuple[PartRange, str]]() + ranges = [range] + for rule in self.rules: + new_ranges = list[PartRange]() + for r in ranges: + ress = rule.eval(r) + for res in ress: + if res[1] is not CONTINUE: + ans.append((res[0], res[1])) + else: + new_ranges.append(res[0]) + clog( + lambda: f"eval [{self.name}: " + f"{rule.operand1}{rule.operation}{rule.operand2}]" + f" on {r} -> {ress}" + ) + ranges = new_ranges + return ans + + +class System(NamedTuple): + workflows: dict[str, Workflow] + parts: list[Part] + + +Input = System +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + ww, pp = my_aocd.to_blocks(input_data) + return System( + {wf.name: wf for wf in [Workflow.from_str(w) for w in ww]}, + [Part.from_str(p) for p in pp], + ) + + def solve( + self, workflows: dict[str, Workflow], prs: list[tuple[PartRange, str]] + ) -> dict[str, list[PartRange]]: + solution = defaultdict[str, list[PartRange]](list) + while prs: + new_prs = list[tuple[PartRange, str]]() + for pr in prs: + for res in workflows[pr[1]].eval(pr[0]): + if res[1] == REJECTED or res[1] == ACCEPTED: + solution[res[1]].append(res[0]) + else: + new_prs.append((res[0], res[1])) + prs = new_prs + return solution + + def part_1(self, system: Input) -> Output1: + solution = self.solve( + system.workflows, + [(PartRange.from_part(part), IN) for part in system.parts], + ) + return sum( + part.score() + for part in system.parts + if [acc for acc in solution[ACCEPTED] if acc.matches(part)] + ) + + def part_2(self, system: Input) -> Output2: + solution = self.solve( + system.workflows, + [(PartRange((1, 4000), (1, 4000), (1, 4000), (1, 4000)), IN)], + ) + rej, acc = [ + sum( + sum(range.score() for range in ranges) + for k, ranges in solution.items() + if k == kk + ) + for kk in (REJECTED, ACCEPTED) + ] + assert acc + rej == 4000**4 + return acc + + @aoc_samples( + ( + ("part_1", TEST, 19114), + ("part_2", TEST, 167409079868000), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2023, 19) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_20.py b/src/main/python/AoC2023_20.py new file mode 100644 index 00000000..f248f26e --- /dev/null +++ b/src/main/python/AoC2023_20.py @@ -0,0 +1,233 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 20 +# + +from __future__ import annotations + +import sys +from collections import defaultdict +from collections import deque +from enum import IntEnum +from functools import reduce +from math import lcm +from typing import NamedTuple +from typing import Protocol + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +TEST1 = """\ +broadcaster -> a, b, c +%a -> b +%b -> c +%c -> inv +&inv -> a +""" +TEST2 = """\ +broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output +""" + + +class ModuleType(IntEnum): + FLIPFLOP = 1 + CONJUNCTION = 2 + BROADCAST = 3 + + +class PulseType(IntEnum): + LOW = 0 + HIGH = 1 + + def flipped(self) -> PulseType: + return PulseType.LOW if self == PulseType.HIGH else PulseType.HIGH + + +class Pulse(NamedTuple): + src: str + dst: str + value: PulseType + + @classmethod + def low(cls, src: str, dst: str) -> Pulse: + return Pulse(src, dst, PulseType.LOW) + + @classmethod + def high(cls, src: str, dst: str) -> Pulse: + return Pulse(src, dst, PulseType.HIGH) + + @property + def is_high(self) -> bool: + return self.value == PulseType.HIGH + + @property + def is_low(self) -> bool: + return self.value == PulseType.LOW + + +class PulseListener(Protocol): + def on_pulse(self, pulse: Pulse) -> None: + pass + + +class Module: + BROADCASTER = "broadcaster" + BUTTON = "button" + SELF = "*self*" + + def __init__(self, type: str, outputs: list[str]) -> None: + self.type = type + self.outputs = outputs + self.state = dict[str, PulseType]() + + @property + def is_conjunction(self) -> bool: + return self.type == "&" + + @property + def is_flip_flop(self) -> bool: + return self.type == "%" + + @property + def is_broadcaster(self) -> bool: + return self.type == Module.BROADCASTER + + def process(self, pulse: Pulse) -> list[Pulse]: + if self.is_broadcaster: + return [ + Pulse(Module.BROADCASTER, o, pulse.value) for o in self.outputs + ] + elif self.is_flip_flop: + if pulse.value == PulseType.LOW: + new_state = self.state[Module.SELF].flipped() + self.state[Module.SELF] = new_state + return [ + Pulse.low(pulse.dst, o) + if new_state == PulseType.LOW + else Pulse.high(pulse.dst, o) + for o in self.outputs + ] + else: + return [] + else: + self.state[pulse.src] = pulse.value + all_high = all(v == PulseType.HIGH for v in self.state.values()) + return [ + Pulse.low(pulse.dst, o) + if all_high + else Pulse.high(pulse.dst, o) + for o in self.outputs + ] + + +class Modules: + def __init__(self, modules: dict[str, Module]) -> None: + self.modules = modules + + @classmethod + def from_input(cls, input_data: InputData) -> Modules: + modules = dict[str, Module]() + for line in input_data: + a, b = line.split(" -> ") + outputs = [_ for _ in b.split(", ")] + if a == Module.BROADCASTER: + modules[Module.BROADCASTER] = Module( + Module.BROADCASTER, outputs + ) + else: + modules[a[1:]] = Module(a[0], outputs) + for m in modules: + module = modules[m] + for o in module.outputs: + m1 = modules.get(o, None) + if m1 and m1.is_conjunction: + m1.state[m] = PulseType.LOW + if module.is_flip_flop: + module.state[Module.SELF] = PulseType.LOW + return Modules(modules) + + def get_module_with_output_rx(self) -> str: + return next( + k + for k, v in self.modules.items() + if any(o == "rx" for o in v.outputs) + ) + + def push_button(self, listener: PulseListener) -> None: + q = deque[Pulse]() + q.append(Pulse.low(Module.BUTTON, Module.BROADCASTER)) + while len(q) > 0: + pulse = q.popleft() + listener.on_pulse(pulse) + if target := self.modules.get(pulse.dst, None): + for p in target.process(pulse): + q.append(p) + + +Input = Modules +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return Modules.from_input(input_data) + + def part_1(self, modules: Input) -> Output1: + class Listener(PulseListener): + def __init__(self) -> None: + self.lo = 0 + self.hi = 0 + + def on_pulse(self, pulse: Pulse) -> None: + if pulse.is_low: + self.lo += 1 + else: + self.hi += 1 + + listener = Listener() + for i in range(1000): + modules.push_button(listener) + return listener.lo * listener.hi + + def part_2(self, modules: Input) -> Output2: + class Listener(PulseListener): + def on_pulse(self, pulse: Pulse) -> None: + if pulse.dst == to_rx and pulse.is_high: + memo[pulse.src].append(pushes) + + memo = defaultdict[str, list[int]](list) + to_rx = modules.get_module_with_output_rx() + pushes = 0 + listener = Listener() + for i in range(10_000): + pushes += 1 + modules.push_button(listener) + if memo and all(len(v) > 1 for v in memo.values()): + return reduce(lcm, (v[1] - v[0] for v in memo.values())) + raise RuntimeError("unsolvable") + + @aoc_samples( + ( + ("part_1", TEST1, 32000000), + ("part_1", TEST2, 11687500), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2023, 20) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_21.py b/src/main/python/AoC2023_21.py new file mode 100644 index 00000000..9b11721e --- /dev/null +++ b/src/main/python/AoC2023_21.py @@ -0,0 +1,94 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 21 +# + +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.common import log +from aoc.grid import CharGrid + +Input = CharGrid +Output1 = int +Output2 = int + + +TEST = """\ +........... +.....###.#. +.###.##..#. +..#.#...#.. +....#.#.... +.##..S####. +.##..#...#. +.......##.. +.##.#.####. +.##..##.##. +........... +""" + +DIRS = {(0, 1), (0, -1), (1, 0), (-1, 0)} +STEPS = 26_501_365 + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return CharGrid.from_strings([_ for _ in input_data]) + + def solve(self, grid: CharGrid, steps: list[int]) -> list[int]: + w = grid.get_width() + start = (w // 2, w // 2) + plots = set[tuple[int, int]]([start]) + ans = [] + for i in range(1, max(steps) + 1): + new_plots = set[tuple[int, int]]() + for r, c in plots: + for dr, dc in DIRS: + rr, cc = r + dr, c + dc + wr, wc = rr % w, cc % w + if ( + 0 <= wr < w + and 0 <= wc < w + and grid.values[wr][wc] != "#" + ): + new_plots.add((rr, cc)) + plots = new_plots + if i in steps: + ans.append(len(plots)) + return ans + + def part_1(self, grid: Input) -> Output1: + return self.solve(grid, [64])[0] + + def part_2(self, grid: Input) -> Output2: + w = grid.get_width() + modulo = STEPS % w + x = STEPS // w + # https://old.reddit.com/r/adventofcode/comments/18nni6t/2023_21_part_2_optimization_hint/ + steps = [w - modulo - 2] + [i * w + modulo for i in range(2)] + log(steps) + values = self.solve(grid, steps) + log(values) + a = (values[2] + values[0]) // 2 - values[1] + b = (values[2] - values[0]) // 2 + c = values[1] + log((f"{a=}", f"{b=}", f"{c=}")) + return a * x * x + b * x + c + + @aoc_samples(()) + def samples(self) -> None: + pass + + +solution = Solution(2023, 21) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_22.py b/src/main/python/AoC2023_22.py new file mode 100644 index 00000000..af678048 --- /dev/null +++ b/src/main/python/AoC2023_22.py @@ -0,0 +1,181 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 22 +# + +from __future__ import annotations + +import itertools +import sys +from typing import Callable +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry3d import Cuboid + +TEST = """\ +1,0,1~1,2,1 +0,0,2~2,0,2 +0,2,3~2,2,3 +0,0,4~0,2,4 +2,0,5~2,2,5 +0,1,6~2,1,6 +1,1,8~1,1,9 +""" + + +class Stack(NamedTuple): + bricks: set[Cuboid] + supportees: dict[Cuboid, set[Cuboid]] + supporters: dict[Cuboid, set[Cuboid]] + bricks_by_z1: dict[int, list[Cuboid]] + bricks_by_z2: dict[int, list[Cuboid]] + + @classmethod + def from_input(cls, input: InputData) -> Stack: + def overlap_xy(lhs: Cuboid, rhs: Cuboid) -> bool: + return lhs.overlap_x(rhs) and lhs.overlap_y(rhs) + + def group_bricks_by( + f: Callable[[Cuboid], int] + ) -> dict[int, list[Cuboid]]: + return { + k: list(v) + for k, v in itertools.groupby(sorted(bricks, key=f), f) + } + + def group_by_touching( + f: Callable[[Cuboid], list[Cuboid]] + ) -> dict[Cuboid, set[Cuboid]]: + return { + brick: set( + filter( + lambda b: overlap_xy(b, brick), + f(brick), + ) + ) + for brick in bricks + } + + def stack( + bricks: set[Cuboid], + bricks_by_z1: dict[int, list[Cuboid]], + bricks_by_z2: dict[int, list[Cuboid]], + ) -> None: + def is_not_supported(brick: Cuboid) -> bool: + return not any( + overlap_xy(brick, other) + for other in bricks_by_z2.get(brick.z1 - 1, []) + ) + + def move_down(brick: Cuboid) -> None: + def move_to_z(brick: Cuboid, dz: int) -> Cuboid: + return Cuboid( + brick.x1, + brick.x2, + brick.y1, + brick.y2, + brick.z1 + dz, + brick.z2 + dz, + ) + + moved_brick = move_to_z(brick, -1) + bricks_by_z1[brick.z1].remove(brick) + bricks_by_z2[brick.z2].remove(brick) + bricks_by_z1.setdefault(moved_brick.z1, []).append(moved_brick) + bricks_by_z2.setdefault(moved_brick.z2, []).append(moved_brick) + + moved = True + while moved: + moved = False + for z in filter(lambda z: z > 1, sorted(bricks_by_z1.keys())): + for brick in filter(is_not_supported, bricks_by_z1[z][:]): + move_down(brick) + moved = True + bricks.clear() + for v in bricks_by_z2.values(): + bricks |= set(v) + + bricks = set[Cuboid]() + for line in input: + splits = line.split("~") + x1, y1, z1 = map(int, splits[0].split(",")) + x2, y2, z2 = map(int, splits[1].split(",")) + bricks.add(Cuboid.of(x1, x2, y1, y2, z1, z2)) + bricks_by_z1 = group_bricks_by(lambda b: b.z1) + bricks_by_z2 = group_bricks_by(lambda b: b.z2) + stack(bricks, bricks_by_z1, bricks_by_z2) + supportees = group_by_touching( + lambda brick: bricks_by_z1.get(brick.z2 + 1, []) + ) + supporters = group_by_touching( + lambda brick: bricks_by_z2.get(brick.z1 - 1, []) + ) + + return Stack( + bricks, supportees, supporters, bricks_by_z1, bricks_by_z2 + ) + + def get_deletable(self) -> set[Cuboid]: + def is_not_single_supporter(brick: Cuboid) -> bool: + return not any( + {brick} == self.supporters[b] for b in self.supportees[brick] + ) + + return set(filter(is_not_single_supporter, self.bricks)) + + def get_not_deletable(self) -> set[Cuboid]: + return self.bricks - self.get_deletable() + + def delete(self, brick: Cuboid) -> set[Cuboid]: + q = [brick] + falling = set[Cuboid]([brick]) + while len(q) > 0: + b = q.pop(0) + for s in self.supportees[b]: + if all(sp in falling for sp in self.supporters[s]): + if s not in falling: + q.append(s) + falling.add(s) + falling.remove(brick) + return falling + + +Input = Stack +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return Stack.from_input(input_data) + + def part_1(self, stack: Input) -> Output1: + return len(stack.get_deletable()) + + def part_2(self, stack: Input) -> Output2: + return sum( + map(lambda b: len(stack.delete(b)), stack.get_not_deletable()) + ) + + @aoc_samples( + ( + ("part_1", TEST, 5), + ("part_2", TEST, 7), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2023, 22) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_23.py b/src/main/python/AoC2023_23.py new file mode 100644 index 00000000..9054bfb1 --- /dev/null +++ b/src/main/python/AoC2023_23.py @@ -0,0 +1,173 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 23 +# + +import sys +from collections import defaultdict +from collections import deque + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry import Direction +from aoc.grid import Cell +from aoc.grid import CharGrid + +Input = CharGrid +Output1 = int +Output2 = int +Edge = tuple[Cell, int] + + +TEST = """\ +#.##################### +#.......#########...### +#######.#########.#.### +###.....#.>.>.###.#.### +###v#####.#v#.###.#.### +###.>...#.#.#.....#...# +###v###.#.#.#########.# +###...#.#.#.......#...# +#####.#.#.#######.#.### +#.....#.#.#.......#...# +#.#####.#.#.#########v# +#.#...#...#...###...>.# +#.#.#v#######v###.###v# +#...#.>.#...>.>.#.###.# +#####v#.#.###v#.#.###.# +#.....#...#...#.#.#...# +#.#########.###.#.#.### +#...###...#...#...#.### +###.###.#.###v#####v### +#...#...#.#.>.>.#.>.### +#.###.###.#.###.#.#v### +#.....###...###...#...# +#####################.# +""" + + +class PathFinder: + def __init__(self, grid: CharGrid) -> None: + self.grid = grid + self.start = Cell(0, 1) + self.end = Cell(grid.get_height() - 1, grid.get_width() - 2) + + def find_longest_hike_length_with_only_downward_slopes(self) -> int: + forks = self._find_forks() + graph = self._build_graph(forks, downward_slope_only=True) + return self._find_longest(graph, self.start, self.end, set[Cell]()) + + def find_longest_hike_length(self) -> int: + forks = self._find_forks() + graph = self._build_graph(forks, downward_slope_only=False) + dist_from_start: int = next(_ for _ in graph[self.start])[1] + dist_to_end: int = next(_ for _ in graph[self.end])[1] + new_start = next( + k for k, v in graph.items() if (self.start, dist_from_start) in v + ) + new_end = next( + k for k, v in graph.items() if (self.end, dist_to_end) in v + ) + return ( + dist_from_start + + dist_to_end + + self._find_longest(graph, new_start, new_end, set[Cell]()) + ) + + def _find_forks(self) -> set[Cell]: + def is_fork(cell: Cell) -> bool: + return ( + cell == self.start + or cell == self.end + or self.grid.get_value(cell) != "#" + and sum( + 1 + for n in self.grid.get_capital_neighbours(cell) + if self.grid.get_value(n) != "#" + ) + > 2 + ) + + return {cell for cell in self.grid.get_cells() if is_fork(cell)} + + def _build_graph( + self, forks: set[Cell], downward_slope_only: bool + ) -> dict[Cell, set[Edge]]: + graph = defaultdict[Cell, set[Edge]](set) + for fork in forks: + q = deque([(fork, 0)]) + seen = set[Cell]({fork}) + while len(q) > 0: + cell, distance = q.popleft() + if cell in forks and cell != fork: + graph[fork].add((cell, distance)) + continue + for d in Direction.capitals(): + n = cell.at(d) + if not self.grid.is_in_bounds(n): + continue + val = self.grid.get_value(n) + if val == "#" or ( + downward_slope_only + and val in {"^", ">", "v", "<"} + and Direction.from_str(val) != d + ): + continue + if n not in seen: + seen.add(n) + q.append((n, distance + 1)) + return graph + + def _find_longest( + self, + graph: dict[Cell, set[Edge]], + curr: Cell, + end: Cell, + seen: set[Cell], + ) -> int: + if curr == end: + return 0 + ans = -1_000_000_000 + seen.add(curr) + for vertex, distance in graph[curr]: + if vertex in seen: + continue + ans = max( + ans, distance + self._find_longest(graph, vertex, end, seen) + ) + seen.remove(curr) + return ans + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return CharGrid.from_strings([line for line in input_data]) + + def part_1(self, grid: Input) -> Output1: + return PathFinder( + grid + ).find_longest_hike_length_with_only_downward_slopes() + + def part_2(self, grid: Input) -> Output2: + return PathFinder(grid).find_longest_hike_length() + + @aoc_samples( + ( + ("part_1", TEST, 94), + ("part_2", TEST, 154), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2023, 23) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_24.py b/src/main/python/AoC2023_24.py new file mode 100644 index 00000000..36da382f --- /dev/null +++ b/src/main/python/AoC2023_24.py @@ -0,0 +1,165 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 24 +# + +import itertools +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.common import log +from aoc.geometry import Line +from aoc.geometry import Position +from aoc.geometry import Vector +from aoc.geometry3d import Position3D +from aoc.geometry3d import Vector3D + +Input = list[tuple[Position3D, Vector3D]] +Output1 = int +Output2 = int + + +TEST = """\ +19, 13, 30 @ -2, 1, -2 +18, 19, 22 @ -1, -1, -2 +20, 25, 34 @ -2, -2, -4 +12, 31, 28 @ -1, -2, -1 +20, 19, 15 @ 1, -5, -3 +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def solve_1(self, hailstones: Input, xy_min: int, xy_max: int) -> int: + def paths_will_intersect_inside_area( + hs_a: tuple[Position3D, Vector3D], + hs_b: tuple[Position3D, Vector3D], + xy_min: int, + xy_max: int, + ) -> bool: + p_a, v_a = hs_a + p_b, v_b = hs_b + line_a = Line.of( + Position.of(p_a.x, p_a.y), Vector.of(v_a.x, v_a.y) + ) + line_b = Line.of( + Position.of(p_b.x, p_b.y), Vector.of(v_b.x, v_b.y) + ) + ix = line_a.intersection(line_b) + if ix is None: + return False + ix_x, ix_y = ix + t_ix_a = (ix_x - p_a.x) / v_a.x + t_ix_b = (ix_x - p_b.x) / v_b.x + return ( + t_ix_a >= 0 + and t_ix_b >= 0 + and xy_min <= ix_x <= xy_max + and xy_min <= ix_y <= xy_max + ) + + return sum( + paths_will_intersect_inside_area(hs_a, hs_b, xy_min, xy_max) + for hs_a, hs_b in itertools.combinations(hailstones, 2) + ) + + def solve_2(self, hailstones: Input) -> int: + # (DistanceDifference % (RockVelocity-HailVelocity) = 0 + def find_rock_velocity(hailstones: Input) -> Vector3D: + v_x, v_y, v_z = set[int](), set[int](), set[int]() + for hs_a, hs_b in itertools.combinations(hailstones, 2): + p_a, v_a = hs_a + p_b, v_b = hs_b + if v_a.x == v_b.x and v_a.x != 0: + dp_x = p_a.x - p_b.x + tmp = set[int]() + for v in range(-1000, 1000): + if v != 0 and v != v_a.x and dp_x % (v - v_a.x) == 0: + tmp.add(v) + v_x = v_x | tmp if len(v_x) == 0 else v_x & tmp + if v_a.y == v_b.y and v_a.y != 0: + dp_y = p_a.y - p_b.y + tmp = set[int]() + for v in range(-1000, 1000): + if v != 0 and v != v_a.y and dp_y % (v - v_a.y) == 0: + tmp.add(v) + v_y = v_y | tmp if len(v_y) == 0 else v_y & tmp + if v_a.z == v_b.z and v_a.z != 0: + dp_z = p_a.z - p_b.z + tmp = set[int]() + for v in range(-1000, 1000): + if v != 0 and v != v_a.z and dp_z % (v - v_a.z) == 0: + tmp.add(v) + v_z = v_z | tmp if len(v_z) == 0 else v_z & tmp + log((v_x, v_y, v_z)) + if not len(v_x) > 0 and len(v_y) > 0 and len(v_z) > 0: + raise RuntimeError() + return Vector3D(v_x.pop(), v_y.pop(), v_z.pop()) + + def find_rock_start_position( + hailstones: Input, v_r: Vector3D + ) -> Position3D: + # px1 + vx1*t = px2 + vx2*t + # px1 - px2 = vx2*t - vx1*t + # (px1-px2)/(vx2-vx1) = t + # + # y = mx + b + # m = vy/vx + # b = py - m*px + # m1x + b1 = m2x + b2 + # m1x - m2x = b2 - b1 + # x = (b2-b1)/(m1-m2) + p_a, v_a = hailstones[0] + p_b, v_b = hailstones[1] + m_a = (v_a.y - v_r.y) / (v_a.x - v_r.x) + m_b = (v_b.y - v_r.y) / (v_b.x - v_r.x) + c_a = p_a.y - (m_a * p_a.x) + c_b = p_b.y - (m_b * p_b.x) + x = int((c_b - c_a) / (m_a - m_b)) + y = int(m_a * x + c_a) + t = (x - p_a.x) // (v_a.x - v_r.x) + z = p_a.z + (v_a.z - v_r.z) * t + return Position3D(x, y, z) + + v_r = find_rock_velocity(hailstones) + p_r = find_rock_start_position(hailstones, v_r) + return p_r.x + p_r.y + p_r.z + + def parse_input(self, input_data: InputData) -> Input: + hs = [] + for line in input_data: + pp, vv = line.split(" @ ") + pos = Position3D.of(*map(int, pp.split(", "))) + vel = Vector3D.of(*map(int, vv.split(", "))) + hs.append((pos, vel)) + return hs + + def sample_1(self, input: Input) -> int: + return self.solve_1(input, 7, 27) + + def part_1(self, input: Input) -> Output1: + return self.solve_1(input, 200000000000000, 400000000000000) + + def part_2(self, input: Input) -> Output2: + return self.solve_2(input) + + @aoc_samples( + ( + ("sample_1", TEST, 2), + ("part_2", TEST, 47), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2023, 24) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2023_25.py b/src/main/python/AoC2023_25.py new file mode 100644 index 00000000..22409bf6 --- /dev/null +++ b/src/main/python/AoC2023_25.py @@ -0,0 +1,106 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2023 Day 25 +# + +from __future__ import annotations + +import random +import sys +from collections import defaultdict +from copy import deepcopy +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.graph import flood_fill + +TEST = """\ +jqt: rhn xhk nvd +rsh: frs pzl lsr +xhk: hfx +cmg: qnr nvd lhk bvb +rhn: xhk bvb hfx +bvb: xhk hfx +pzl: lsr hfx nvd +qnr: nvd +ntq: jqt hfx bvb xhk +nvd: lhk +lsr: lhk +rzs: qnr cmg lsr rsh +frs: qnr lhk lsr +""" + + +class Graph(NamedTuple): + edges: dict[str, list[str]] + + @classmethod + def from_input(cls, input: InputData) -> Graph: + edges = defaultdict[str, list[str]](list) + for line in input: + key, values = line.split(": ") + edges[key] += [_ for _ in values.split()] + for v in values.split(): + edges[v].append(key) + return Graph(edges) + + def combine_nodes(self, node_1: str, node_2: str, new_node: str) -> Graph: + self.edges[new_node] = [ + d for d in self.edges[node_1] if d != node_2 + ] + [d for d in self.edges[node_2] if d != node_1] + for node in {node_1, node_2}: + for dst in self.edges[node]: + self.edges[dst] = [ + new_node if d == node else d for d in self.edges[dst] + ] + del self.edges[node] + return Graph(self.edges) + + def get_connected(self, node: str) -> set[str]: + return flood_fill(node, lambda n: (_ for _ in self.edges[n])) + + +Input = Graph +Output1 = int +Output2 = str + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return Graph.from_input(input_data) + + def part_1(self, graph: Input) -> Output1: + while True: + g = deepcopy(graph) + counts = {node: 1 for node in g.edges.keys()} + while len(g.edges) > 2: + a = random.sample(list(g.edges.keys()), 1)[0] + b = random.sample(list(g.edges[a]), 1)[0] + new_node = f"{a}-{b}" + counts[new_node] = counts.get(a, 0) + counts.get(b, 0) + del counts[a] + del counts[b] + g = g.combine_nodes(a, b, new_node) + a, b = g.edges.keys() + if len(g.edges[a]) == 3: + return counts[a] * counts[b] + + def part_2(self, input: Input) -> Output2: + return "🎄" + + @aoc_samples((("part_1", TEST, 54),)) + def samples(self) -> None: + pass + + +solution = Solution(2023, 25) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_01.py b/src/main/python/AoC2024_01.py new file mode 100644 index 00000000..439d0029 --- /dev/null +++ b/src/main/python/AoC2024_01.py @@ -0,0 +1,61 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 1 +# + +import sys +from collections import Counter + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = tuple[tuple[int, ...], tuple[int, ...]] +Output1 = int +Output2 = int + + +TEST = """\ +3 4 +4 3 +2 5 +1 3 +3 9 +3 3 +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return tuple( + _ for _ in zip(*[map(int, line.split()) for line in input_data]) + ) + + def part_1(self, lists: Input) -> Output1: + left, right = lists + return sum(abs(n1 - n2) for n1, n2 in zip(sorted(left), sorted(right))) + + def part_2(self, lists: Input) -> Output2: + left, right = lists + ctr = Counter(right) + return sum(n * ctr[n] for n in left) + + @aoc_samples( + ( + ("part_1", TEST, 11), + ("part_2", TEST, 31), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 1) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_02.py b/src/main/python/AoC2024_02.py new file mode 100644 index 00000000..9b360fae --- /dev/null +++ b/src/main/python/AoC2024_02.py @@ -0,0 +1,67 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 2 +# + +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = list[list[int]] +Output1 = int +Output2 = int + + +TEST = """\ +7 6 4 2 1 +1 2 7 8 9 +9 7 6 2 1 +1 3 2 4 5 +8 6 4 4 1 +1 3 6 7 9 +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [list(map(int, line.split())) for line in input_data] + + def safe(self, levels: list[int]) -> bool: + diffs = [levels[i + 1] - levels[i] for i in range(len(levels) - 1)] + return all(1 <= diff <= 3 for diff in diffs) or all( + -1 >= diff >= -3 for diff in diffs + ) + + def part_1(self, reports: Input) -> Output1: + return sum(self.safe(report) for report in reports) + + def part_2(self, reports: Input) -> Output2: + return sum( + any( + self.safe(report[:i] + report[i + 1 :]) # noqa E203 + for i in range(len(report)) + ) + for report in reports + ) + + @aoc_samples( + ( + ("part_1", TEST, 2), + ("part_2", TEST, 4), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 2) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_03.py b/src/main/python/AoC2024_03.py new file mode 100644 index 00000000..7f0138cd --- /dev/null +++ b/src/main/python/AoC2024_03.py @@ -0,0 +1,69 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 3 +# + +import re +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = str +Output1 = int +Output2 = int + + +TEST_1 = """\ +xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5)) +""" +TEST_2 = """\ +xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5)) +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return "\n".join(line for line in input_data) + + def solve(self, input: str, use_conditionals: bool) -> int: + enabled = True + ans = 0 + for do, _, a, b in re.findall( + r"(do(n't)?)\(\)|mul\((\d{1,3}),(\d{1,3})\)", input + ): + if do == "do": + enabled = True + elif do == "don't": + enabled = False + else: + if not use_conditionals or enabled: + ans += int(a) * int(b) + return ans + + def part_1(self, input: Input) -> Output1: + return self.solve(input, use_conditionals=False) + + def part_2(self, input: Input) -> Output2: + return self.solve(input, use_conditionals=True) + + @aoc_samples( + ( + ("part_1", TEST_1, 161), + ("part_2", TEST_2, 48), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 3) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_04.py b/src/main/python/AoC2024_04.py new file mode 100644 index 00000000..0b729989 --- /dev/null +++ b/src/main/python/AoC2024_04.py @@ -0,0 +1,82 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 4 +# + +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry import Direction +from aoc.grid import CharGrid + +Input = CharGrid +Output1 = int +Output2 = int + + +TEST = """\ +MMMSXXMASM +MSAMXMSMSA +AMXSXMAAMM +MSAMASMSMX +XMASAMXAMM +XXAMMXXAMA +SMSMSASXSS +SAXAMASAAA +MAMMMXMMMM +MXMXAXMASX +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return CharGrid.from_strings(list(input_data)) + + def part_1(self, grid: Input) -> Output1: + return sum( + all( + (n := next(it, None)) is not None and grid.get_value(n) == v + for v in ["M", "A", "S"] + ) + for it in ( + grid.get_cells_dir(cell, d) + for cell in grid.get_all_equal_to("X") + for d in Direction.octants() + ) + ) + + def part_2(self, grid: Input) -> Output2: + dirs = [ + Direction.LEFT_AND_UP, + Direction.RIGHT_AND_DOWN, + Direction.RIGHT_AND_UP, + Direction.LEFT_AND_DOWN, + ] + matches = {"MSMS", "SMSM", "MSSM", "SMMS"} + return sum( + grid.get_value(cell) == "A" + and "".join(grid.get_value(cell.at(d)) for d in dirs) in matches + for cell in grid.get_cells_without_border() + ) + + @aoc_samples( + ( + ("part_1", TEST, 18), + ("part_2", TEST, 9), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 4) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_05.py b/src/main/python/AoC2024_05.py new file mode 100644 index 00000000..b3419e87 --- /dev/null +++ b/src/main/python/AoC2024_05.py @@ -0,0 +1,109 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 5 +# + +import sys +from collections import defaultdict +from enum import Enum +from enum import auto +from enum import unique +from functools import cmp_to_key + +from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = tuple[dict[int, list[int]], list[list[int]]] +Output1 = int +Output2 = int + + +TEST = """\ +47|53 +97|13 +97|61 +97|47 +75|29 +61|13 +75|53 +29|13 +97|29 +53|29 +61|53 +97|53 +61|29 +47|13 +75|47 +97|75 +47|61 +75|61 +47|29 +75|13 +53|13 + +75,47,61,53,29 +97,61,53,29,13 +75,29,13 +75,97,47,61,53 +61,13,29 +97,13,75,29,47 +""" + + +@unique +class Mode(Enum): + USE_CORRECT = auto() + USE_INCORRECT = auto() + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + blocks = my_aocd.to_blocks(input_data) + order = defaultdict[int, list[int]](list) + for line in blocks[0]: + a, b = map(int, line.split("|")) + order[a].append(b) + updates = [list(map(int, line.split(","))) for line in blocks[1]] + return order, updates + + def solve(self, input: Input, mode: Mode) -> int: + order, updates = input + + def cmp(a: int, b: int) -> int: + return -1 if b in order[a] else 1 + + ans = 0 + for update in updates: + correct = update[:] + correct.sort(key=cmp_to_key(cmp)) + if not ((mode == Mode.USE_CORRECT) ^ (update == correct)): + ans += correct[len(correct) // 2] + return ans + + def part_1(self, input: Input) -> Output1: + return self.solve(input, Mode.USE_CORRECT) + + def part_2(self, input: Input) -> Output2: + return self.solve(input, Mode.USE_INCORRECT) + + @aoc_samples( + ( + ("part_1", TEST, 143), + ("part_2", TEST, 123), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 5) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_06.py b/src/main/python/AoC2024_06.py new file mode 100644 index 00000000..327f28c1 --- /dev/null +++ b/src/main/python/AoC2024_06.py @@ -0,0 +1,193 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 6 +# + +from __future__ import annotations + +import sys +from collections import OrderedDict +from typing import Iterator +from typing import NamedTuple + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.grid import CharGrid + +Cell = tuple[int, int] +Direction = tuple[int, int] +Input = CharGrid +Output1 = int +Output2 = int + +UP, RIGHT, DOWN, LEFT = (0, 1), (1, 0), (0, -1), (-1, 0) +DIRS = [UP, RIGHT, DOWN, LEFT] + + +TEST = """\ +....#..... +.........# +.......... +..#....... +.......#.. +.......... +.#..^..... +........#. +#......... +......#... +""" + + +class Obstacles(NamedTuple): + obs: dict[Direction, list[list[Cell | None]]] + + @classmethod + def _obstacles( + cls, grid: CharGrid, starts: Iterator[Cell], dir: Direction + ) -> list[list[Cell | None]]: + h, w = grid.get_height(), grid.get_width() + obs: list[list[Cell | None]] = [ + [None for _ in range(w)] for _ in range(h) + ] + for start in starts: + last = start if grid.values[start[0]][start[1]] == "#" else None + cell = start + while True: + r, c = cell + obs[r][c] = last + if grid.values[r][c] == "#": + last = cell + nr, nc = r - dir[1], c + dir[0] + if not (0 <= nr < h and 0 <= nc < w): + break + cell = (nr, nc) + return obs + + @classmethod + def from_grid(cls, grid: CharGrid) -> Obstacles: + h, w = grid.get_height(), grid.get_width() + obs = dict[Direction, list[list[Cell | None]]]() + mci, mri = grid.get_max_col_index(), grid.get_max_row_index() + obs[RIGHT] = Obstacles._obstacles( + grid, ((r, mci) for r in range(h)), LEFT + ) + obs[LEFT] = Obstacles._obstacles( + grid, ((r, 0) for r in range(h)), RIGHT + ) + obs[UP] = Obstacles._obstacles(grid, ((0, c) for c in range(w)), DOWN) + obs[DOWN] = Obstacles._obstacles( + grid, ((mri, c) for c in range(w)), UP + ) + return Obstacles(obs) + + def get_next( + self, start: Cell, dir: Direction, extra: Cell + ) -> Cell | None: + obs = self.obs[dir][start[0]][start[1]] + if obs is None: + return ( + extra + if ( + dir[1] == 0 + and start[0] == extra[0] + and dir == (RIGHT if start[1] < extra[1] else LEFT) + or dir[0] == 0 + and start[1] == extra[1] + and dir == (DOWN if start[0] < extra[0] else UP) + ) + else None + ) + else: + if ( + dir[1] == 0 + and obs[0] == extra[0] + and dir == (RIGHT if start[1] < extra[1] else LEFT) + ): + dextra = abs(extra[1] - start[1]) + dobs = abs(obs[1] - start[1]) + return obs if dobs < dextra else extra + elif ( + dir[0] == 0 + and obs[1] == extra[1] + and dir == (DOWN if start[0] < extra[0] else UP) + ): + dextra = abs(extra[0] - start[0]) + dobs = abs(obs[0] - start[0]) + return obs if dobs < dextra else extra + return obs + + +class Solution(SolutionBase[Input, Output1, Output2]): + + def parse_input(self, input_data: InputData) -> Input: + return CharGrid.from_strings(list(input_data)) + + def visited( + self, + grid: CharGrid, + dir: int, + ) -> OrderedDict[Cell, list[int]]: + start = next(grid.get_all_equal_to("^")) + pos = (start.row, start.col) + h, w = grid.get_height(), grid.get_width() + seen = OrderedDict[Cell, list[int]]() + seen.setdefault(pos, list()).append(dir) + while True: + nxt = (pos[0] - DIRS[dir][1], pos[1] + DIRS[dir][0]) + if not (0 <= nxt[0] < h and 0 <= nxt[1] < w): + return seen + if grid.values[nxt[0]][nxt[1]] == "#": + dir = (dir + 1) % len(DIRS) + else: + pos = nxt + seen.setdefault(pos, list()).append(dir) + + def is_loop( + self, pos: Cell, dir: int, obs: Obstacles, extra: Cell + ) -> bool: + seen = dict[Cell, int]() + seen[pos] = 1 << dir + while True: + nxt_obs = obs.get_next(pos, DIRS[dir], extra) + if nxt_obs is None: + return False + pos = (nxt_obs[0] + DIRS[dir][1], nxt_obs[1] - DIRS[dir][0]) + dir = (dir + 1) % len(DIRS) + if pos in seen and seen[pos] & (1 << dir) != 0: + return True + seen[pos] = seen.get(pos, 0) | (1 << dir) + + def part_1(self, grid: Input) -> Output1: + return len(self.visited(grid, 0)) + + def part_2(self, grid: Input) -> Output2: + obs = Obstacles.from_grid(grid) + visited = self.visited(grid, 0) + prev_pos, prev_dirs = visited.popitem(last=False) + ans = 0 + while len(visited) > 0: + pos, dirs = visited.popitem(last=False) + ans += self.is_loop(prev_pos, prev_dirs.pop(0), obs, pos) + prev_pos, prev_dirs = pos, dirs + return ans + + @aoc_samples( + ( + ("part_1", TEST, 41), + ("part_2", TEST, 6), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 6) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_07.py b/src/main/python/AoC2024_07.py new file mode 100644 index 00000000..bf66d59b --- /dev/null +++ b/src/main/python/AoC2024_07.py @@ -0,0 +1,98 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 7 +# + +from __future__ import annotations + +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = list[tuple[int, list[int]]] +Output1 = int +Output2 = int + +ADD = 1 +MULTIPLY = 2 +CONCATENATE = 4 + + +TEST = """\ +190: 10 19 +3267: 81 40 27 +83: 17 5 +156: 15 6 +7290: 6 8 6 15 +161011: 16 10 13 +192: 17 8 14 +21037: 9 7 18 13 +292: 11 6 16 20 +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + ans = [] + for line in input_data: + left, right = line.split(": ") + sol = int(left) + terms = list(map(int, right.split())) + ans.append((sol, terms)) + return ans + + def solve(self, input: Input, ops: int) -> int: + def can_obtain(sol: int, terms: list[int], ops: int) -> bool: + if len(terms) == 1: + return sol == terms[0] + if ( + ops & MULTIPLY + and sol % terms[-1] == 0 + and can_obtain(sol // terms[-1], terms[:-1], ops) + ): + return True + if ( + ops & ADD + and sol > terms[-1] + and can_obtain(sol - terms[-1], terms[:-1], ops) + ): + return True + if ops & CONCATENATE: + s_sol, s_last = str(sol), str(terms[-1]) + if ( + len(s_sol) > len(s_last) + and s_sol.endswith(s_last) + and can_obtain(int(s_sol[: -len(s_last)]), terms[:-1], ops) + ): + return True + return False + + return sum(sol for sol, terms in input if can_obtain(sol, terms, ops)) + + def part_1(self, input: Input) -> Output1: + return self.solve(input, ADD | MULTIPLY) + + def part_2(self, input: Input) -> Output2: + return self.solve(input, ADD | MULTIPLY | CONCATENATE) + + @aoc_samples( + ( + ("part_1", TEST, 3749), + ("part_2", TEST, 11387), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 7) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_08.py b/src/main/python/AoC2024_08.py new file mode 100644 index 00000000..9aae786f --- /dev/null +++ b/src/main/python/AoC2024_08.py @@ -0,0 +1,121 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 8 +# + +import itertools +import sys +from collections import defaultdict +from enum import Enum +from enum import auto +from enum import unique +from functools import reduce +from operator import ior +from typing import Iterator + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry import Position +from aoc.geometry import Vector + +Antenna = Position +AntennaPair = tuple[Antenna, Antenna] +Input = tuple[int, int, list[set[Antenna]]] +Output1 = int +Output2 = int + + +TEST = """\ +............ +........0... +.....0...... +.......0.... +....0....... +......A..... +............ +............ +........A... +.........A.. +............ +............ +""" + + +@unique +class Mode(Enum): + MODE_1 = auto() + MODE_2 = auto() + + def collect_antinodes( + self, h: int, w: int, pair: AntennaPair + ) -> set[Position]: + def get_antinodes(max_count: int) -> Iterator[Antenna]: + vec = Vector.of(pair[0].x - pair[1].x, pair[0].y - pair[1].y) + for pos, d in itertools.product(pair, {-1, 1}): + for a in range(1, max_count + 1): + antinode = pos.translate(vec, d * a) + if 0 <= antinode.x < w and 0 <= antinode.y < h: + yield antinode + else: + break + + match self: + case Mode.MODE_1: + return set(get_antinodes(max_count=1)) - { + pair[0], + pair[1], + } + case Mode.MODE_2: + return set(get_antinodes(max_count=sys.maxsize)) + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + lines = list(input_data) + h, w = len(lines), len(lines[0]) + antennae = defaultdict[str, set[Antenna]](set) + for r, c in itertools.product(range(h), range(w)): + if (freq := lines[r][c]) != ".": + antennae[freq].add(Antenna(c, h - r - 1)) + return h, w, list(antennae.values()) + + def solve( + self, h: int, w: int, antennae: list[set[Antenna]], mode: Mode + ) -> int: + return len( + reduce( + ior, + ( + mode.collect_antinodes(h, w, pair) + for same_frequency in antennae + for pair in itertools.combinations(same_frequency, 2) + ), + ) + ) + + def part_1(self, input: Input) -> Output1: + return self.solve(*input, Mode.MODE_1) + + def part_2(self, input: Input) -> Output2: + return self.solve(*input, Mode.MODE_2) + + @aoc_samples( + ( + ("part_1", TEST, 14), + ("part_2", TEST, 34), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 8) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_09.py b/src/main/python/AoC2024_09.py new file mode 100644 index 00000000..36f2d6f0 --- /dev/null +++ b/src/main/python/AoC2024_09.py @@ -0,0 +1,111 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 9 +# + +import heapq +import sys +from enum import Enum +from enum import auto +from enum import unique + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = list[int] +Output1 = int +Output2 = int +File = tuple[int, int, int] + +TRIANGLE = [0, 0, 1, 3, 6, 10, 15, 21, 28, 36] + +TEST = """\ +2333133121414131402 +""" + + +@unique +class Mode(Enum): + MODE_1 = (auto(),) + MODE_2 = (auto(),) + + def create_files(self, id: int, pos: int, sz: int) -> list[File]: + match self: + case Mode.MODE_1: + return [(id, pos + i, 1) for i in range(sz)] + case Mode.MODE_2: + return [(id, pos, sz)] + + def checksum(self, f: File) -> int: + id, pos, sz = f + match self: + case Mode.MODE_1: + return id * pos + case Mode.MODE_2: + return id * (pos * sz + TRIANGLE[sz]) + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(map(int, list(input_data)[0])) + + def solve(self, disk: Input, mode: Mode) -> int: + files = list[File]() + free_by_sz: list[list[int]] = [[] for _ in range(10)] + is_free, id, pos = False, 0, 0 + for n in disk: + if is_free: + heapq.heappush(free_by_sz[n], pos) + else: + files.extend(mode.create_files(id, pos, n)) + id += 1 + pos += n + is_free = not is_free + ans = 0 + for id, pos, sz in reversed(files): + earliest = min( + ( + (i, free) + for i, free in enumerate(free_by_sz[sz:], sz) + if len(free) > 0 + ), + key=lambda e: e[1][0], + default=None, + ) + if earliest is not None: + free_sz, free = earliest + free_pos = free[0] + if free_pos < pos: + heapq.heappop(free_by_sz[free_sz]) + pos = free_pos + if sz < free_sz: + heapq.heappush(free_by_sz[free_sz - sz], pos + sz) + ans += mode.checksum((id, pos, sz)) + return ans + + def part_1(self, disk: Input) -> Output1: + return self.solve(disk, Mode.MODE_1) + + def part_2(self, disk: Input) -> Output2: + return self.solve(disk, Mode.MODE_2) + + @aoc_samples( + ( + ("part_1", TEST, 1928), + ("part_2", TEST, 2858), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 9) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_10.py b/src/main/python/AoC2024_10.py new file mode 100644 index 00000000..12c4dff8 --- /dev/null +++ b/src/main/python/AoC2024_10.py @@ -0,0 +1,96 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 10 +# + +import sys +from collections import deque +from enum import Enum +from enum import auto +from enum import unique + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.grid import Cell +from aoc.grid import IntGrid + +Input = IntGrid +Output1 = int +Output2 = int + + +TEST = """\ +89010123 +78121874 +87430965 +96549874 +45678903 +32019012 +01329801 +10456732 +""" + + +@unique +class Grading(Enum): + SCORE = auto() + RATING = auto() + + def get(self, trails: list[list[Cell]]) -> int: + match self: + case Grading.SCORE: + return len({trail[-1] for trail in trails}) + case Grading.RATING: + return len(trails) + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return IntGrid.from_strings(list(input_data)) + + def solve(self, grid: IntGrid, grading: Grading) -> int: + def bfs(trail_head: Cell) -> list[list[Cell]]: + trails = list[list[Cell]]() + q = deque([[trail_head]]) + while q: + trail = q.pop() + nxt = len(trail) + if nxt == 10: + trails.append(trail) + continue + for n in grid.get_capital_neighbours(trail[-1]): + if grid.get_value(n) == nxt: + q.append(trail + [n]) + return trails + + return sum( + grading.get(bfs(trail_head)) + for trail_head in grid.get_all_equal_to(0) + ) + + def part_1(self, grid: Input) -> Output1: + return self.solve(grid, Grading.SCORE) + + def part_2(self, grid: Input) -> Output2: + return self.solve(grid, Grading.RATING) + + @aoc_samples( + ( + ("part_1", TEST, 36), + ("part_2", TEST, 81), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 10) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_11.py b/src/main/python/AoC2024_11.py new file mode 100644 index 00000000..01148cc5 --- /dev/null +++ b/src/main/python/AoC2024_11.py @@ -0,0 +1,63 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 11 +# + +import sys +from functools import cache + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = list[int] +Output1 = int +Output2 = int + + +TEST = """\ +125 17 +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(map(int, list(input_data)[0].split())) + + def solve(self, stones: list[int], blinks: int) -> int: + @cache + def count(s: int, cnt: int) -> int: + if cnt == 0: + return 1 + if s == 0: + return count(1, cnt - 1) + ss = str(s) + size = len(ss) + if size % 2 == 0: + s1 = int(ss[: size // 2]) + s2 = int(ss[size // 2 :]) # noqa E203 + return count(s1, cnt - 1) + count(s2, cnt - 1) + return count(s * 2024, cnt - 1) + + return sum(count(int(s), blinks) for s in stones) + + def part_1(self, stones: Input) -> Output1: + return self.solve(stones, blinks=25) + + def part_2(self, stones: Input) -> Output2: + return self.solve(stones, blinks=75) + + @aoc_samples((("part_1", TEST, 55312),)) + def samples(self) -> None: + pass + + +solution = Solution(2024, 11) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_12.py b/src/main/python/AoC2024_12.py new file mode 100644 index 00000000..7471bbc1 --- /dev/null +++ b/src/main/python/AoC2024_12.py @@ -0,0 +1,153 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 12 +# + +import sys +from collections import defaultdict +from enum import Enum +from enum import auto +from enum import unique +from typing import Iterator + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry import Direction +from aoc.graph import flood_fill +from aoc.grid import Cell + +Input = InputData +Output1 = int +Output2 = int + +CORNER_DIRS = [ + [Direction.LEFT_AND_UP, Direction.LEFT, Direction.UP], + [Direction.RIGHT_AND_UP, Direction.RIGHT, Direction.UP], + [Direction.RIGHT_AND_DOWN, Direction.RIGHT, Direction.DOWN], + [Direction.LEFT_AND_DOWN, Direction.LEFT, Direction.DOWN], +] + +TEST1 = """\ +AAAA +BBCD +BBCC +EEEC +""" +TEST2 = """\ +OOOOO +OXOXO +OOOOO +OXOXO +OOOOO +""" +TEST3 = """\ +RRRRIICCFF +RRRRIICCCF +VVRRRCCFFF +VVRCCCJFFF +VVVVCJJCFE +VVIVCCJJEE +VVIIICJJEE +MIIIIIJJEE +MIIISIJEEE +MMMISSJEEE +""" +TEST4 = """\ +EEEEE +EXXXX +EEEEE +EXXXX +EEEEE +""" +TEST5 = """\ +AAAAAA +AAABBA +AAABBA +ABBAAA +ABBAAA +AAAAAA +""" + + +@unique +class Pricing(Enum): + PERIMETER = auto() + NUMBER_OF_SIDES = auto() + + def calculate(self, plot: Cell, region: set[Cell]) -> int: + match self: + case Pricing.PERIMETER: + return 4 - sum( + n in region for n in plot.get_capital_neighbours() + ) + case Pricing.NUMBER_OF_SIDES: + return sum( + tuple( + 1 if plot.at(d[i]) in region else 0 for i in range(3) + ) + in ((0, 0, 0), (1, 0, 0), (0, 1, 1)) + for d in CORNER_DIRS + ) + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return input_data + + def solve(self, input: Input, pricing: Pricing) -> int: + def get_regions(input: Input) -> Iterator[set[Cell]]: + plots_by_plant = defaultdict[str, set[Cell]](set) + for r, row in enumerate(input): + for c, plant in enumerate(row): + plots_by_plant[plant].add(Cell(r, c)) + for all_plots_with_plant in plots_by_plant.values(): + while all_plots_with_plant: + region = flood_fill( + next(iter(all_plots_with_plant)), + lambda cell: ( + n + for n in cell.get_capital_neighbours() + if n in all_plots_with_plant + ), + ) + yield region + all_plots_with_plant -= region + + return sum( + sum(pricing.calculate(plot, region) for plot in region) + * len(region) + for region in get_regions(input) + ) + + def part_1(self, input: Input) -> Output1: + return self.solve(input, Pricing.PERIMETER) + + def part_2(self, input: Input) -> Output2: + return self.solve(input, Pricing.NUMBER_OF_SIDES) + + @aoc_samples( + ( + ("part_1", TEST1, 140), + ("part_1", TEST2, 772), + ("part_1", TEST3, 1930), + ("part_2", TEST1, 80), + ("part_2", TEST2, 436), + ("part_2", TEST3, 1206), + ("part_2", TEST4, 236), + ("part_2", TEST5, 368), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 12) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_13.py b/src/main/python/AoC2024_13.py new file mode 100644 index 00000000..c4e8a13a --- /dev/null +++ b/src/main/python/AoC2024_13.py @@ -0,0 +1,99 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 13 +# + +from __future__ import annotations + +import sys +from typing import NamedTuple + +from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +TEST = """\ +Button A: X+94, Y+34 +Button B: X+22, Y+67 +Prize: X=8400, Y=5400 + +Button A: X+26, Y+66 +Button B: X+67, Y+21 +Prize: X=12748, Y=12176 + +Button A: X+17, Y+86 +Button B: X+84, Y+37 +Prize: X=7870, Y=6450 + +Button A: X+69, Y+23 +Button B: X+27, Y+71 +Prize: X=18641, Y=10279 +""" + + +class Machine(NamedTuple): + ax: int + bx: int + ay: int + by: int + px: int + py: int + + @classmethod + def from_input(cls, block: list[str]) -> Machine: + a, b = ((int(block[i][12:14]), int(block[i][18:20])) for i in range(2)) + sp = block[2].split(", ") + px, py = int(sp[0].split("=")[1]), int(sp[1][2:]) + return Machine(a[0], b[0], a[1], b[1], px, py) + + +Input = list[Machine] +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [ + Machine.from_input(block) + for block in my_aocd.to_blocks(input_data) + ] + + def solve(self, machines: list[Machine], offset: int = 0) -> int: + def calc_tokens(machine: Machine, offset: int) -> int | None: + px, py = machine.px + offset, machine.py + offset + div = machine.bx * machine.ay - machine.ax * machine.by + ans_a = (py * machine.bx - px * machine.by) / div + ans_b = (px * machine.ay - py * machine.ax) / div + if int(ans_a) == ans_a and int(ans_b) == ans_b: + return int(ans_a) * 3 + int(ans_b) + else: + return None + + return sum( + tokens + for tokens in (calc_tokens(m, offset) for m in machines) + if tokens is not None + ) + + def part_1(self, machines: Input) -> Output1: + return self.solve(machines) + + def part_2(self, machines: Input) -> Output2: + return self.solve(machines, offset=10_000_000_000_000) + + @aoc_samples((("part_1", TEST, 480),)) + def samples(self) -> None: + pass + + +solution = Solution(2024, 13) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_14.py b/src/main/python/AoC2024_14.py new file mode 100644 index 00000000..4a7362e0 --- /dev/null +++ b/src/main/python/AoC2024_14.py @@ -0,0 +1,132 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 14 +# + +import math +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase + +Position = tuple[int, int] +Vector = tuple[int, int] +Input = list[tuple[Position, Vector]] +Output1 = int +Output2 = int + +W, H = 101, 103 + +TEST = """\ +p=0,4 v=3,-3 +p=6,3 v=-1,-3 +p=10,3 v=-1,2 +p=2,0 v=2,-1 +p=0,0 v=1,3 +p=3,0 v=-2,-2 +p=7,6 v=-1,-3 +p=3,0 v=-1,-2 +p=9,3 v=2,3 +p=7,3 v=-1,2 +p=2,4 v=2,-3 +p=9,5 v=-3,-3 +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + robots = list[tuple[Position, Vector]]() + for line in input_data: + p, v = line.split() + px, py = map(int, p.split("=")[1].split(",")) + vx, vy = map(int, v.split("=")[1].split(",")) + robots.append(((px, py), (vx, vy))) + return robots + + def safety_factor( + self, + robots: list[tuple[Position, Vector]], + w: int, + h: int, + rounds: int, + ) -> int: + mid_w, mid_h = w // 2, h // 2 + q1, q2, q3, q4 = 0, 0, 0, 0 + for pos, vec in robots: + px, py = pos[0] + rounds * vec[0], pos[1] + rounds * vec[1] + px, py = px % w, py % h + if px < mid_w: + if py < mid_h: + q1 += 1 + elif mid_h < py: + q2 += 1 + elif mid_w < px: + if py < mid_h: + q3 += 1 + elif mid_h < py: + q4 += 1 + return math.prod((q1, q2, q3, q4)) + + def solve_1( + self, robots: list[tuple[Position, Vector]], w: int, h: int + ) -> int: + return self.safety_factor(robots, w, h, 100) + + def part_1(self, robots: Input) -> Output1: + return self.solve_1(robots, W, H) + + def part_2(self, robots: Input) -> Output2: + def print_robots( + robots: list[tuple[Position, Vector]], w: int, h: int, round: int + ) -> None: + if not __debug__: + return + new_robots = [ + ((pos[0] + round * vec[0]) % w, (pos[1] + round * vec[1]) % h) + for pos, vec in robots + ] + lines = [ + "".join("*" if (x, y) in new_robots else "." for x in range(w)) + for y in range(h) + ] + for line in lines: + print(line) + + ans, best, round = 0, sys.maxsize, 1 + sfs = list[int]() + while round < W + H: + safety_factor = self.safety_factor(robots, W, H, round) + sfs.append(safety_factor) + if safety_factor < best: + best = safety_factor + ans = round + round += 1 + mins = list[int]() + for i in range(2, len(sfs) - 2): + avg = sum(sfs[i + j] for j in (-2, -1, 1, 2)) // 4 + if sfs[i] < avg * 9 // 10: + mins.append(i + 1) + period = mins[2] - mins[0] + round = mins[2] + period + while round <= W * H: + safety_factor = self.safety_factor(robots, W, H, round) + if safety_factor < best: + best = safety_factor + ans = round + round += period + print_robots(robots, W, H, ans) + return ans + + def samples(self) -> None: + assert self.solve_1(self.parse_input(TEST.splitlines()), 11, 7) == 12 + + +solution = Solution(2024, 14) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_15.py b/src/main/python/AoC2024_15.py new file mode 100644 index 00000000..659f1879 --- /dev/null +++ b/src/main/python/AoC2024_15.py @@ -0,0 +1,179 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 15 +# + +from __future__ import annotations + +import sys +from enum import Enum +from enum import auto +from enum import unique +from typing import Iterator +from typing import NamedTuple + +from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry import Direction +from aoc.grid import Cell +from aoc.grid import CharGrid + +FLOOR, WALL, ROBOT = ".", "#", "@" +BOX, BIG_BOX_LEFT, BIG_BOX_RIGHT = "O", "[", "]" +SCALE_UP = { + WALL: WALL + WALL, + BOX: BIG_BOX_LEFT + BIG_BOX_RIGHT, + FLOOR: FLOOR + FLOOR, + ROBOT: ROBOT + FLOOR, +} + +TEST1 = """\ +######## +#..O.O.# +##@.O..# +#...O..# +#.#.O..# +#...O..# +#......# +######## + +<^^>>>vv>v<< +""" +TEST2 = """\ +########## +#..O..O.O# +#......O.# +#.OO..O.O# +#..O@..O.# +#O#..O...# +#O..O..O.# +#.OO.O.OO# +#....O...# +########## + +^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ +vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< +<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ +^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< +^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ +<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> +^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< +v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ +""" + + +@unique +class WarehouseType(Enum): + WAREHOUSE_1 = auto() + WAREHOUSE_2 = auto() + + +class Warehouse(NamedTuple): + type: WarehouseType + grid: CharGrid + + @classmethod + def create(cls, type: WarehouseType, grid_in: list[str]) -> Warehouse: + match type: + case WarehouseType.WAREHOUSE_1: + grid = CharGrid.from_strings(grid_in) + case WarehouseType.WAREHOUSE_2: + strings = [ + "".join(SCALE_UP[ch] for ch in line) for line in grid_in + ] + grid = CharGrid.from_strings(strings) + return Warehouse(type, grid) + + def get_to_move(self, robot: Cell, dir: Direction) -> list[Cell]: + to_move = [robot] + for cell in to_move: + nxt = cell.at(dir) + if nxt in to_move: + continue + nxt_val = self.grid.get_value(nxt) + if nxt_val == WALL: + return [] + match self.type: + case WarehouseType.WAREHOUSE_1: + if nxt_val != BOX: + continue + case WarehouseType.WAREHOUSE_2: + if nxt_val == BIG_BOX_LEFT: + to_move.append(nxt.at(Direction.RIGHT)) + elif nxt_val == BIG_BOX_RIGHT: + to_move.append(nxt.at(Direction.LEFT)) + else: + continue + to_move.append(nxt) + return to_move + + def get_boxes(self) -> Iterator[Cell]: + match self.type: + case WarehouseType.WAREHOUSE_1: + ch = BOX + case WarehouseType.WAREHOUSE_2: + ch = BIG_BOX_LEFT + return self.grid.get_all_equal_to(ch) + + +Input = tuple[list[str], list[Direction]] +Output1 = int +Output2 = int + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + blocks = my_aocd.to_blocks(input_data) + dirs = [Direction.from_str(ch) for ch in "".join(blocks[1])] + return blocks[0], dirs + + def solve(self, warehouse: Warehouse, dirs: list[Direction]) -> int: + robot = next(warehouse.grid.get_all_equal_to(ROBOT)) + for dir in dirs: + to_move = warehouse.get_to_move(robot, dir) + if len(to_move) == 0: + continue + vals = {tm: warehouse.grid.get_value(tm) for tm in to_move} + robot = robot.at(dir) + for cell in to_move: + warehouse.grid.set_value(cell, FLOOR) + for cell in to_move: + warehouse.grid.set_value(cell.at(dir), vals[cell]) + return sum(cell.row * 100 + cell.col for cell in warehouse.get_boxes()) + + def part_1(self, input: Input) -> Output1: + grid, dirs = input + return self.solve( + Warehouse.create(WarehouseType.WAREHOUSE_1, grid), dirs + ) + + def part_2(self, input: Input) -> Output2: + grid, dirs = input + return self.solve( + Warehouse.create(WarehouseType.WAREHOUSE_2, grid), dirs + ) + + @aoc_samples( + ( + ("part_1", TEST1, 2028), + ("part_1", TEST2, 10092), + ("part_2", TEST2, 9021), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 15) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_16.py b/src/main/python/AoC2024_16.py new file mode 100644 index 00000000..556c52ca --- /dev/null +++ b/src/main/python/AoC2024_16.py @@ -0,0 +1,162 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 16 +# + +from __future__ import annotations + +import itertools +import sys +from typing import Iterator +from typing import NamedTuple +from typing import Self + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.geometry import Direction +from aoc.graph import Dijkstra +from aoc.grid import Cell + +IDX_TO_DIR = { + 0: Direction.UP, + 1: Direction.RIGHT, + 2: Direction.DOWN, + 3: Direction.LEFT, +} +DIR_TO_IDX = { + Direction.UP: 0, + Direction.RIGHT: 1, + Direction.DOWN: 2, + Direction.LEFT: 3, +} +TURNS = { + Direction.UP: [Direction.LEFT, Direction.RIGHT], + Direction.RIGHT: [Direction.UP, Direction.DOWN], + Direction.DOWN: [Direction.LEFT, Direction.RIGHT], + Direction.LEFT: [Direction.UP, Direction.DOWN], +} + + +TEST1 = """\ +############### +#.......#....E# +#.#.###.#.###.# +#.....#.#...#.# +#.###.#####.#.# +#.#.#.......#.# +#.#.#####.###.# +#...........#.# +###.#.#####.#.# +#...#.....#.#.# +#.#.#.###.#.#.# +#.....#...#.#.# +#.###.#.#.#.#.# +#S..#.....#...# +############### +""" +TEST2 = """\ +################# +#...#...#...#..E# +#.#.#.#.#.#.#.#.# +#.#.#.#...#...#.# +#.#.#.#.###.#.#.# +#...#.#.#.....#.# +#.#.#.#.#.#####.# +#.#...#.#.#.....# +#.#.#####.#.###.# +#.#.#.......#...# +#.#.###.#####.### +#.#.#...#.....#.# +#.#.#.#####.###.# +#.#.#.........#.# +#.#.#.#########.# +#S#.............# +################# +""" + + +class ReindeerMaze(NamedTuple): + grid: list[list[str]] + start: Cell + end: Cell + + @classmethod + def from_grid(cls, strings: list[str]) -> Self: + grid = [[ch for ch in line] for line in strings] + start = Cell(len(grid) - 2, 1) + end = Cell(1, len(grid[0]) - 2) + return cls(grid, start, end) + + def adjacent(self, state: State) -> Iterator[State]: + r, c, dir = state + direction = IDX_TO_DIR[dir] + states = ( + (r - d.y, c + d.x, DIR_TO_IDX[d]) + for d in [direction] + TURNS[direction] + ) + for state in states: + if self.grid[state[0]][state[1]] != "#": + yield state + + +Input = ReindeerMaze +Output1 = int +Output2 = int +State = tuple[int, int, int] + + +class Solution(SolutionBase[Input, Output1, Output2]): + + def parse_input(self, input_data: InputData) -> Input: + return ReindeerMaze.from_grid(list(input_data)) + + def part_1(self, maze: Input) -> Output1: + return Dijkstra.distance( + (maze.start.row, maze.start.col, DIR_TO_IDX[Direction.RIGHT]), + lambda state: (state[0], state[1]) == (maze.end.row, maze.end.col), + lambda state: maze.adjacent(state), + lambda curr, nxt: 1 if curr[2] == nxt[2] else 1001, + ) + + def part_2(self, maze: Input) -> Output2: + result = Dijkstra.all( + (maze.start.row, maze.start.col, DIR_TO_IDX[Direction.RIGHT]), + lambda state: (state[0], state[1]) == (maze.end.row, maze.end.col), + lambda state: maze.adjacent(state), + lambda curr, nxt: 1 if curr[2] == nxt[2] else 1001, + ) + return len( + { + (state[0], state[1]) + for end, dir in itertools.product( + [maze.end], Direction.capitals() + ) + for paths in result.get_paths( + (end.row, end.col, DIR_TO_IDX[dir]) + ) + for state in paths + } + ) + + @aoc_samples( + ( + ("part_1", TEST1, 7036), + ("part_1", TEST2, 11048), + ("part_2", TEST1, 45), + ("part_2", TEST2, 64), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 16) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_17.py b/src/main/python/AoC2024_17.py new file mode 100644 index 00000000..720c9b32 --- /dev/null +++ b/src/main/python/AoC2024_17.py @@ -0,0 +1,151 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 17 +# + +import sys +from collections import deque + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples +from aoc.common import log +from aoc.vm import Instruction +from aoc.vm import Program +from aoc.vm import VirtualMachine + +Input = tuple[int, int, int, list[int]] +Output1 = str +Output2 = int + + +TEST1 = """\ +Register A: 729 +Register B: 0 +Register C: 0 + +Program: 0,1,5,4,3,0 +""" +TEST2 = """\ +Register A: 2024 +Register B: 0 +Register C: 0 + +Program: 0,3,5,4,3,0 +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + lines = list(input_data) + a, b, c = map(int, (lines[i][12:] for i in range(3))) + ops = list(map(int, lines[4][9:].split(","))) + return a, b, c, ops + + def create_instructions(self, ops: list[int]) -> list[Instruction]: + def combo(operand: int) -> str: + match operand: + case 0 | 1 | 2 | 3: + return str(operand) + case 4 | 5 | 6: + return ["*A", "*B", "*C"][operand - 4] + case _: + raise ValueError + + ins = [] + ip_map = dict[int, int]() + ip = 0 + for i in range(0, len(ops), 2): + ip_map[i] = ip + opcode, operand = ops[i], ops[i + 1] + match opcode: + case 0: + ins.append(Instruction.RSH("A", combo(operand))) + ip += 1 + case 1: + ins.append(Instruction.XOR("B", str(operand))) + ip += 1 + case 2: + ins.append(Instruction.SET("B", str(combo(operand)))) + ins.append(Instruction.AND("B", "7")) + ip += 2 + case 3: + ins.append( + Instruction.JN0("*A", "!" + str(ip_map[operand])) + ) + ip += 1 + case 4: + ins.append(Instruction.XOR("B", "*C")) + ip += 1 + case 5: + ins.append(Instruction.SET("X", str(combo(operand)))) + ins.append(Instruction.AND("X", "7")) + ins.append(Instruction.OUT("*X")) + ip += 3 + case 6: + ins.append(Instruction.SET("C", "*B")) + ins.append(Instruction.RSH("C", combo(operand))) + ip += 2 + case 7: + ins.append(Instruction.SET("C", "*A")) + ins.append(Instruction.RSH("C", combo(operand))) + ip += 2 + case _: + raise ValueError + return ins + + def run_program( + self, ins: list[Instruction], a: int, b: int, c: int + ) -> list[str]: + output = [] + program = Program(ins, output_consumer=lambda s: output.append(s)) + program.registers["A"] = int(a) + program.registers["B"] = int(b) + program.registers["C"] = int(c) + VirtualMachine().run_program(program) + return output + + def part_1(self, input: Input) -> Output1: + a, b, c, ops = input + ins = self.create_instructions(ops) + return ",".join(self.run_program(ins, a, b, c)) + + def part_2(self, input: Input) -> Output2: + _, b, c, ops = input + ins = self.create_instructions(ops) + wanted = list(str(_) for _ in ops) + log(f"{wanted=}") + seen = set([0]) + q = deque([0]) + while q: + cand_a = q.popleft() * 8 + for i in range(8): + na = cand_a + i + res = self.run_program(ins, na, b, c) + if res == wanted: + return na + if res == wanted[-len(res) :] and na not in seen: # noqa E203 + seen.add(na) + log(na) + q.append(na) + raise RuntimeError("unsolvable") + + @aoc_samples( + ( + ("part_1", TEST1, "4,6,3,5,6,3,5,2,1,0"), + ("part_2", TEST2, 117440), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 17) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_18.py b/src/main/python/AoC2024_18.py new file mode 100644 index 00000000..501b7b23 --- /dev/null +++ b/src/main/python/AoC2024_18.py @@ -0,0 +1,117 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 18 +# + +import sys +from typing import Iterator + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.graph import bfs +from aoc.graph import flood_fill +from aoc.grid import Cell + +Input = list[Cell] +Output1 = int +Output2 = str + +SIZE = 71 +TIME = 1024 +START = Cell(0, 0) + +TEST = """\ +5,4 +4,2 +4,5 +3,0 +2,1 +6,3 +2,4 +1,5 +0,6 +3,3 +2,6 +5,1 +1,2 +5,5 +2,5 +6,5 +1,4 +0,4 +6,4 +1,1 +6,1 +1,0 +0,5 +1,6 +2,0 +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return [ + Cell(int(r), int(c)) + for c, r in (line.split(",") for line in input_data) + ] + + def adjacent( + self, cell: Cell, size: int, occupied: set[Cell] + ) -> Iterator[Cell]: + return ( + n + for n in cell.get_capital_neighbours() + if 0 <= n.row < size and 0 <= n.col < size and n not in occupied + ) + + def solve_1(self, cells: Input, size: int = SIZE, time: int = TIME) -> int: + end = Cell(size - 1, size - 1) + occupied = set(cells[:time]) + distance, _ = bfs( + START, + lambda cell: cell == end, + lambda cell: self.adjacent(cell, size, occupied), + ) + return distance + + def solve_2(self, cells: Input, size: int = SIZE, time: int = TIME) -> str: + end = Cell(size - 1, size - 1) + + def free(time: int) -> set[Cell]: + occupied = set(cells[:time]) + return flood_fill( + START, + lambda cell: self.adjacent(cell, size, occupied), + ) + + lo, hi = time, len(cells) + while lo < hi: + mid = (lo + hi) // 2 + if end in free(mid): + lo = mid + 1 + else: + hi = mid + return f"{cells[lo - 1].col},{cells[lo - 1].row}" + + def part_1(self, cells: Input) -> Output1: + return self.solve_1(cells) + + def part_2(self, cells: Input) -> Output2: + return self.solve_2(cells) + + def samples(self) -> None: + input = self.parse_input(TEST.splitlines()) + assert self.solve_1(input, 7, 12) == 22 + assert self.solve_2(input, 7, 12) == "6,1" + + +solution = Solution(2024, 18) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_19.py b/src/main/python/AoC2024_19.py new file mode 100644 index 00000000..d610c65c --- /dev/null +++ b/src/main/python/AoC2024_19.py @@ -0,0 +1,73 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 19 +# + +import sys +from functools import cache + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = tuple[tuple[str, ...], list[str]] +Output1 = int +Output2 = int + + +TEST = """\ +r, wr, b, g, bwu, rb, gb, br + +brwrr +bggr +gbbr +rrbgbr +ubwu +bwurrg +brgr +bbrgwb +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + lines = list(input_data) + return tuple(lines[0].split(", ")), lines[2:] + + @cache + def count(self, design: str, towels: tuple[str]) -> int: + if len(design) == 0: + return 1 + return sum( + self.count(design[len(towel) :], towels) # noqa E203 + for towel in towels + if design.startswith(towel) + ) + + def part_1(self, input: Input) -> Output1: + towels, designs = input + return sum(self.count(design, towels) > 0 for design in designs) + + def part_2(self, input: Input) -> Output2: + towels, designs = input + return sum(self.count(design, towels) for design in designs) + + @aoc_samples( + ( + ("part_1", TEST, 6), + ("part_2", TEST, 16), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 19) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_20.py b/src/main/python/AoC2024_20.py new file mode 100644 index 00000000..c0a8b959 --- /dev/null +++ b/src/main/python/AoC2024_20.py @@ -0,0 +1,143 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 20 +# + +import multiprocessing +import os +import sys + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.geometry import Direction +from aoc.geometry import Turn +from aoc.grid import CharGrid + +Cell = tuple[int, int] +Input = CharGrid +Output1 = int +Output2 = int + + +TEST = """\ +############### +#...#...#.....# +#.#.#.#.#.###.# +#S#...#.#.#...# +#######.#.#.### +#######.#.#...# +#######.#.###.# +###..E#...#...# +###.#######.### +#...###...#...# +#.#####.#.###.# +#.#...#.#.#...# +#.#.#.#.#.#.### +#...#...#...### +############### +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return CharGrid.from_strings(list(input_data)) + + def solve(self, grid: CharGrid, cheat_len: int, target: int) -> int: + ans = multiprocessing.Manager().dict() + h, w = grid.get_height(), grid.get_width() + + def count_shortcuts( + id: str, + cells: list[Cell], + dist: list[int], + ) -> None: + count = 0 + for r, c in cells: + d_cell = dist[r * h + c] + for md in range(2, cheat_len + 1): + min_req = target + md + for dr in range(md + 1): + dc = md - dr + for rr, cc in { + (r + dr, c + dc), + (r + dr, c - dc), + (r - dr, c + dc), + (r - dr, c - dc), + }: + if not (0 <= rr < h and 0 <= cc < w): + continue + d_n = dist[rr * h + cc] + if d_n == sys.maxsize: + continue + if d_n - d_cell >= min_req: + count += 1 + ans[id] = count + + start = next( + cell for cell in grid.get_cells() if grid.get_value(cell) == "S" + ) + end = next( + cell for cell in grid.get_cells() if grid.get_value(cell) == "E" + ) + dir = next( + d + for d in Direction.capitals() + if grid.get_value(start.at(d)) != "#" + ) + pos = start + dist = 0 + track = list[Cell]() + distances = [sys.maxsize for r in range(h) for c in range(w)] + while True: + distances[pos.row * h + pos.col] = dist + track.append((pos.row, pos.col)) + if pos == end: + break + dir = next( + d + for d in (dir, dir.turn(Turn.RIGHT), dir.turn(Turn.LEFT)) + if grid.get_value(pos.at(d)) != "#" + ) + pos = pos.at(dir) + dist += 1 + if sys.platform.startswith("win"): + count_shortcuts("main", track, distances) + else: + n = os.process_cpu_count() + cells: list[list[Cell]] = [[] for _ in range(n)] + cnt = 0 + for cell in track: + cells[cnt % n].append(cell) + cnt += 1 + jobs = [] + for i in range(n): + p = multiprocessing.Process( + target=count_shortcuts, args=(str(i), cells[i], distances) + ) + jobs.append(p) + p.start() + for p in jobs: + p.join() + return sum(ans.values()) + + def part_1(self, grid: Input) -> Output1: + return self.solve(grid, 2, 100) + + def part_2(self, grid: Input) -> Output2: + return self.solve(grid, 20, 100) + + def samples(self) -> None: + input = self.parse_input(TEST.splitlines()) + assert self.solve(input, 2, 2) == 44 + assert self.solve(input, 20, 50) == 285 + + +solution = Solution(2024, 20) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_21.py b/src/main/python/AoC2024_21.py new file mode 100644 index 00000000..c49230b0 --- /dev/null +++ b/src/main/python/AoC2024_21.py @@ -0,0 +1,108 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 21 +# + +import sys +from dataclasses import dataclass +from functools import cache +from typing import Iterator + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = InputData +Output1 = int +Output2 = int + + +TEST = """\ +029A +980A +179A +456A +379A +""" + + +@dataclass(frozen=True) +class Keypad: + layout: list[str] + + def get_position(self, k: str) -> tuple[int, int]: + return next( + (x, y) + for y, r in enumerate(self.layout) + for x, c in enumerate(r) + if c == k + ) + + def __getitem__(self, idx: int) -> str: + return self.layout[idx] + + +NUMERIC = Keypad(layout=["789", "456", "123", " 0A"]) +DIRECTIONAL = Keypad(layout=[" ^A", ""]) + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return input_data + + def solve(self, input: Input, levels: int) -> int: + def path(keypad: Keypad, from_: str, to: str) -> str: + from_x, from_y = keypad.get_position(from_) + to_x, to_y = keypad.get_position(to) + + def paths(x: int, y: int, s: str) -> Iterator[str]: + if (x, y) == (to_x, to_y): + yield s + "A" + if to_x < x and keypad[y][x - 1] != " ": + yield from paths(x - 1, y, s + "<") + if to_y < y and keypad[y - 1][x] != " ": + yield from paths(x, y - 1, s + "^") + if to_y > y and keypad[y + 1][x] != " ": + yield from paths(x, y + 1, s + "v") + if to_x > x and keypad[y][x + 1] != " ": + yield from paths(x + 1, y, s + ">") + + return min( + paths(from_x, from_y, ""), + key=lambda p: sum(a != b for a, b in zip(p, p[1:])), + ) + + @cache + def count(s: str, level: int, max_level: int) -> int: + if level > max_level: + return len(s) + keypad = DIRECTIONAL if level else NUMERIC + return sum( + count(path(keypad, from_, to), level + 1, max_level) + for from_, to in zip("A" + s, s) + ) + + return sum( + int(combo[:-1]) * count(combo, 0, levels) for combo in input + ) + + def part_1(self, input: Input) -> Output1: + return self.solve(input, 2) + + def part_2(self, input: Input) -> Output2: + return self.solve(input, 25) + + @aoc_samples((("part_1", TEST, 126384),)) + def samples(self) -> None: + pass + + +solution = Solution(2024, 21) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_22.py b/src/main/python/AoC2024_22.py new file mode 100644 index 00000000..a6a02408 --- /dev/null +++ b/src/main/python/AoC2024_22.py @@ -0,0 +1,133 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 22 +# + +import multiprocessing +import os +import sys +from collections import defaultdict +from typing import Callable + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = list[int] +Output1 = int +Output2 = int + + +TEST1 = """\ +1 +10 +100 +2024 +""" +TEST2 = """\ +1 +2 +3 +2024 +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + return list(map(int, input_data)) + + def secret(self, num: int) -> int: + num = (num ^ (num * 64)) % 16777216 + num = (num ^ (num // 32)) % 16777216 + num = (num ^ (num * 2048)) % 16777216 + return num + + def solve( + self, seeds: Input, worker: Callable[[str, list[int]], None] + ) -> None: + if sys.platform.startswith("win"): + worker("main", seeds) + else: + n = os.process_cpu_count() + batch = [list[int]() for _ in range(n)] + cnt = 0 + for seed in seeds: + batch[cnt % n].append(seed) + cnt += 1 + jobs = [] + for i in range(n): + p = multiprocessing.Process( + target=worker, args=(str(i), batch[i]) + ) + jobs.append(p) + p.start() + for p in jobs: + p.join() + + def part_1(self, seeds: Input) -> Output1: + m_ans = multiprocessing.Manager().dict() + + def worker(id: str, seeds: list[int]) -> None: + ans = 0 + for num in seeds: + for _ in range(2000): + num = self.secret(num) + ans += num + m_ans[id] = ans + + self.solve(seeds, worker) + return sum(m_ans.values()) + + def part_2(self, seeds: Input) -> Output2: + m_ans = multiprocessing.Array("i", [0 for _ in range(19**4)]) + + def worker(id: str, seeds: list[int]) -> None: + p = defaultdict[int, int](int) + seen = [-1 for _ in range(19**4)] + for i, num in enumerate(seeds): + na = num + nb = self.secret(na) + nc = self.secret(nb) + nd = self.secret(nc) + b, c, d = ( + na % 10 - nb % 10 + 9, + nb % 10 - nc % 10 + 9, + nc % 10 - nd % 10 + 9, + ) + num = nd + prev_price = num % 10 + for _ in range(3, 2000): + num = self.secret(num) + price = num % 10 + a, b, c, d = b, c, d, prev_price - price + 9 + prev_price = price + key = (a * 19**3) + (b * 19**2) + (c * 19) + d + if seen[key] == i: + continue + seen[key] = i + p[key] += price + for k, v in p.items(): + m_ans[k] += v + + self.solve(seeds, worker) + return max(m_ans) # type:ignore + + @aoc_samples( + ( + ("part_1", TEST1, 37327623), + ("part_2", TEST2, 23), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 22) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_23.py b/src/main/python/AoC2024_23.py new file mode 100644 index 00000000..3af633b6 --- /dev/null +++ b/src/main/python/AoC2024_23.py @@ -0,0 +1,100 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 23 +# + +import sys +from collections import defaultdict + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Input = dict[str, set[str]] +Output1 = int +Output2 = str + + +TEST = """\ +kh-tc +qp-kh +de-cg +ka-co +yn-aq +qp-ub +cg-tb +vc-aq +tb-ka +wh-tc +yn-cg +kh-ub +ta-co +de-co +tc-td +tb-wq +wh-td +ta-ka +td-qp +aq-cg +wq-ub +ub-vc +de-ta +wq-aq +wq-vc +wh-yn +ka-de +kh-ta +co-tc +wh-qp +tb-vc +td-yn +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + edges = defaultdict[str, set[str]](set) + for line in input_data: + node_1, node_2 = line.split("-") + edges[node_1].add(node_2) + edges[node_2].add(node_1) + return edges + + def part_1(self, edges: Input) -> Output1: + return len( + { + tuple(sorted((t, neighbour_1, neighbour_2))) + for t in (comp for comp in edges if comp.startswith("t")) + for neighbour_1 in edges[t] + for neighbour_2 in edges[neighbour_1] + if t in edges[neighbour_2] + } + ) + + def part_2(self, edges: Input) -> Output2: + cliques = [{comp} for comp in edges] + for clique in cliques: + for comp in edges: + if clique & edges[comp] == clique: + clique.add(comp) + return ",".join(sorted(max(cliques, key=len))) # type:ignore + + @aoc_samples( + ( + ("part_1", TEST, 7), + ("part_2", TEST, "co,de,ka,ta"), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 23) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_24.py b/src/main/python/AoC2024_24.py new file mode 100644 index 00000000..b78ec07a --- /dev/null +++ b/src/main/python/AoC2024_24.py @@ -0,0 +1,181 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 24 +# + +import sys +from collections import deque +from operator import and_ +from operator import or_ +from operator import xor +from typing import cast + +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Gate = tuple[str, str, str, str] +Input = tuple[dict[str, int], list[Gate]] +Output1 = int +Output2 = str + +OPS = { + "OR": or_, + "AND": and_, + "XOR": xor, +} + + +TEST1 = """\ +x00: 1 +x01: 1 +x02: 1 +y00: 0 +y01: 1 +y02: 0 + +x00 AND y00 -> z00 +x01 XOR y01 -> z01 +x02 OR y02 -> z02 +""" +TEST2 = """\ +x00: 1 +x01: 0 +x02: 1 +x03: 1 +x04: 0 +y00: 1 +y01: 1 +y02: 1 +y03: 1 +y04: 1 + +ntg XOR fgs -> mjb +y02 OR x01 -> tnw +kwq OR kpj -> z05 +x00 OR x03 -> fst +tgd XOR rvg -> z01 +vdt OR tnw -> bfw +bfw AND frj -> z10 +ffh OR nrd -> bqk +y00 AND y03 -> djm +y03 OR y00 -> psh +bqk OR frj -> z08 +tnw OR fst -> frj +gnj AND tgd -> z11 +bfw XOR mjb -> z00 +x03 OR x00 -> vdt +gnj AND wpb -> z02 +x04 AND y00 -> kjc +djm OR pbm -> qhw +nrd AND vdt -> hwm +kjc AND fst -> rvg +y04 OR y02 -> fgs +y01 AND x02 -> pbm +ntg OR kjc -> kwq +psh XOR fgs -> tgd +qhw XOR tgd -> z09 +pbm OR djm -> kpj +x03 XOR y03 -> ffh +x00 XOR y04 -> ntg +bfw OR bqk -> z06 +nrd XOR fgs -> wpb +frj XOR qhw -> z04 +bqk OR frj -> z07 +y03 OR x01 -> nrd +hwm AND bqk -> z03 +tgd XOR rvg -> z12 +tnw OR pbm -> gnj +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + wires = dict[str, int]() + gates = list[Gate]() + for line in input_data: + if ": " in line: + name, value = line.split(": ") + wires[name] = int(value) + elif " -> " in line: + in1, op, in2, _, out = line.split() + gates.append((in1, op, in2, out)) + return wires, gates + + def part_1(self, input: Input) -> Output1: + wires, gates = input + q = deque(gates) + while q: + in1, op, in2, out = q.popleft() + if in1 in wires and in2 in wires: + wires[out] = cast(int, OPS[op](wires[in1], wires[in2])) + else: + q.append((in1, op, in2, out)) + max_z = max( + int(out[1:]) for _, _, _, out in gates if out.startswith("z") + ) + return sum((1 << i) * wires[f"z{i:0>2}"] for i in range(max_z, -1, -1)) + + def part_2(self, input: Input) -> Output2: + def is_swapped(gate: Gate) -> bool: + def outputs_to_z_except_first_one_and_not_xor(gate: Gate) -> bool: + _, op, _, out = gate + return out[0] == "z" and op != "XOR" and out != "z45" + + def is_xor_not_connected_to_x_or_y_or_z(gate: Gate) -> bool: + in1, op, in2, out = gate + return op == "XOR" and not any( + name[0] in ("x", "y", "z") for name in (in1, in2, out) + ) + + def is_and_except_last_with_output_not_to_or(gate: Gate) -> bool: + in1, op, in2, out = gate + return ( + op == "AND" + and not (in1 == "x00" or in2 == "x00") + and any( + out in (other_in1, other_in2) and other_op != "OR" + for other_in1, other_op, other_in2, _ in gates + ) + ) + + def is_xor_with_output_to_or(gate: Gate) -> bool: + in1, op, in2, out = gate + return ( + op == "XOR" + and not (in1 == "x00" or in2 == "x00") + and any( + out in (other_in1, other_in2) and other_op == "OR" + for other_in1, other_op, other_in2, _ in gates + ) + ) + + return ( + outputs_to_z_except_first_one_and_not_xor(gate) + or is_xor_not_connected_to_x_or_y_or_z(gate) + or is_and_except_last_with_output_not_to_or(gate) + or is_xor_with_output_to_or(gate) + ) + + _, gates = input + return ",".join(sorted(gate[3] for gate in gates if is_swapped(gate))) + + @aoc_samples( + ( + ("part_1", TEST1, 4), + ("part_1", TEST2, 2024), + ) + ) + def samples(self) -> None: + pass + + +solution = Solution(2024, 24) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/AoC2024_25.py b/src/main/python/AoC2024_25.py new file mode 100644 index 00000000..9662d6ee --- /dev/null +++ b/src/main/python/AoC2024_25.py @@ -0,0 +1,101 @@ +#! /usr/bin/env python3 +# +# Advent of Code 2024 Day 25 +# + +import sys + +from aoc import my_aocd +from aoc.common import InputData +from aoc.common import SolutionBase +from aoc.common import aoc_samples + +Heights = tuple[int, ...] +Input = tuple[set[Heights], set[Heights]] +Output1 = int +Output2 = str + + +TEST = """\ +##### +.#### +.#### +.#### +.#.#. +.#... +..... + +##### +##.## +.#.## +...## +...#. +...#. +..... + +..... +#.... +#.... +#...# +#.#.# +#.### +##### + +..... +..... +#.#.. +###.. +###.# +###.# +##### + +..... +..... +..... +#.... +#.#.. +#.#.# +##### +""" + + +class Solution(SolutionBase[Input, Output1, Output2]): + def parse_input(self, input_data: InputData) -> Input: + keys, locks = set(), set() + blocks = my_aocd.to_blocks(input_data) + for block in blocks: + rows = ["".join(x) for x in zip(*[line for line in block])] + nums: Heights = tuple( + sum(1 for ch in row if ch == "#") - 1 for row in rows + ) + if rows[0][0] == "#": + locks.add(nums) + else: + keys.add(nums) + return keys, locks + + def part_1(self, input: Input) -> Output1: + keys, locks = input + return sum( + all(a + b <= 5 for a, b in zip(lock, key)) + for lock in locks + for key in keys + ) + + def part_2(self, input: Input) -> Output2: + return "🎄" + + @aoc_samples((("part_1", TEST, 3),)) + def samples(self) -> None: + pass + + +solution = Solution(2024, 25) + + +def main() -> None: + solution.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/main/python/aoc/collections.py b/src/main/python/aoc/collections.py new file mode 100644 index 00000000..f625e140 --- /dev/null +++ b/src/main/python/aoc/collections.py @@ -0,0 +1,35 @@ +from typing import Any + + +def index_of_sublist(lst: list[Any], sub: list[Any]) -> int: + size = len(sub) + if len(lst) == 0 or size == 0 or len(lst) < size: + return -1 + for idx in (i for i, e in enumerate(lst) if e == sub[0]): + if lst[idx : idx + size] == sub: # noqa E203 + return idx + return -1 + + +def indexes_of_sublist(lst: list[Any], sub: list[Any]) -> list[int]: + idxs: list[int] = [] + i = 0 + while i + len(sub) <= len(lst): + idx = index_of_sublist(lst[i:], sub) + if idx == -1: + break + idxs.append(i + idx) + i += idx + len(sub) + return idxs + + +def subtract_all(lst: list[Any], sub: list[Any]) -> list[Any]: + ans = list[Any]() + i = 0 + for idx in indexes_of_sublist(lst, sub): + while i < idx: + ans.append(lst[i]) + i += 1 + i += len(sub) + ans += lst[i:][:] + return ans diff --git a/src/main/python/aoc/common.py b/src/main/python/aoc/common.py index 453b6942..d04ce1a2 100644 --- a/src/main/python/aoc/common.py +++ b/src/main/python/aoc/common.py @@ -16,6 +16,7 @@ import aocd from aoc import my_aocd from prettyprinter import cpprint +from termcolor import colored def clog(c: Callable[[], object]) -> None: @@ -106,10 +107,14 @@ def duration_as_ms(self) -> float: return self.duration / 1_000_000 def __repr__(self) -> str: - return ( - f"Part {self.part}:" - f" {self.answer}, took {self.duration_as_ms:.3f} ms" - ) + answer = colored(self.answer, "white", attrs=["bold"]) + if self.duration_as_ms <= 1_000: + duration = f"{self.duration_as_ms:.3f}" + elif self.duration_as_ms <= 5_000: + duration = colored(f"{self.duration_as_ms:.0f}", "yellow") + else: + duration = colored(f"{self.duration_as_ms:.0f}", "red") + return f"Part {self.part}: {answer}, took {duration} ms" def to_json(self) -> str: return ( diff --git a/src/main/python/aoc/game_of_life.py b/src/main/python/aoc/game_of_life.py index f4ff8d75..c2a1f8b0 100644 --- a/src/main/python/aoc/game_of_life.py +++ b/src/main/python/aoc/game_of_life.py @@ -1,74 +1,58 @@ from __future__ import annotations + +from abc import ABC +from abc import abstractmethod +from collections import Counter from itertools import product -from abc import ABC, abstractmethod -from typing import Iterable, TypeVar +from typing import Generic +from typing import Iterable +from typing import TypeVar T = TypeVar("T") class GameOfLife: def __init__( - self, alive: Iterable[T], universe: Universe, rules: Rules + self, alive: Iterable[T], universe: Universe[T], rules: Rules[T] ) -> None: self.alive = alive self.universe = universe self.rules = rules def next_generation(self) -> None: - def _is_alive(cell: T) -> bool: - count = self.universe.neigbour_count(cell, self.alive) - return self.rules.alive(cell, count, self.alive) - self.alive = { - cell for cell in self.universe.cells(self.alive) if _is_alive(cell) + cell + for cell, count in self.universe.neighbour_count( + self.alive + ).items() + if self.rules.alive(cell, count, self.alive) } - class Universe(ABC): - @abstractmethod - def cells(self, alive: Iterable[T]) -> Iterable[T]: - pass - + class Universe(ABC, Generic[T]): @abstractmethod - def neigbour_count(self, cell: T, alive: Iterable[T]) -> int: + def neighbour_count(self, alive: Iterable[T]) -> dict[T, int]: pass - class Rules(ABC): + class Rules(ABC, Generic[T]): @abstractmethod def alive(self, cell: T, count: int, alive: Iterable[T]) -> bool: pass -class ClassicRules(GameOfLife.Rules): +class ClassicRules(GameOfLife.Rules[tuple[int, ...]]): def alive(self, cell: T, count: int, alive: Iterable[T]) -> bool: return count == 3 or (count == 2 and cell in alive) -class InfiniteGrid(GameOfLife.Universe): - Cell = tuple[int, int, int, int] - - def __init__(self, dim: int) -> None: - self.dim = dim - self.ds = list(product((-1, 0, 1), repeat=4)) - self.ds.remove((0, 0, 0, 0)) +class InfiniteGrid(GameOfLife.Universe[tuple[int, ...]]): - def cells(self, alive: Iterable[Cell]) -> set[Cell]: # type:ignore[override] # noqa E501 - def _expand(index: int) -> Iterable[int]: - values = [a[index] for a in alive] - return range(min(values) - 1, max(values) + 2) - - return { - _ - for _ in product( - _expand(0), - _expand(1), - _expand(2) if self.dim >= 3 else [0], - _expand(3) if self.dim >= 4 else [0], - ) - } + def _neighbours(self, cell: tuple[int, ...]) -> set[tuple[int, ...]]: + tmp = {tuple(_) for _ in product([-1, 0, 1], repeat=len(cell))} + tmp.remove(tuple(0 for i in range(len(cell)))) + return {tuple(cell[i] + a[i] for i in range(len(cell))) for a in tmp} - def neigbour_count(self, cell: Cell, alive: Iterable[Cell]) -> int: # type:ignore[override] # noqa E501 - x, y, z, w = cell - return sum( - (x + dx, y + dy, z + dz, w + dw) in alive - for dx, dy, dz, dw in self.ds - ) + def neighbour_count( + self, + alive: Iterable[tuple[int, ...]], + ) -> dict[tuple[int, ...], int]: + return Counter(n for cell in alive for n in self._neighbours(cell)) diff --git a/src/main/python/aoc/geometry.py b/src/main/python/aoc/geometry.py index 30a6ac4a..1809b359 100644 --- a/src/main/python/aoc/geometry.py +++ b/src/main/python/aoc/geometry.py @@ -1,5 +1,7 @@ from __future__ import annotations -from enum import Enum, unique + +from enum import Enum +from enum import unique from typing import NamedTuple @@ -84,6 +86,13 @@ def from_degrees(cls, degrees: int) -> Turn: return v raise ValueError + @classmethod + def from_directions(cls, dir1: Direction, dir2: Direction) -> Turn: + for v in Turn: + if dir1.turn(v) == dir2: + return v + raise ValueError + @unique class Direction(Enum): @@ -137,6 +146,10 @@ def octants(cls) -> set[Direction]: Direction.LEFT_AND_UP, } + @classmethod + def capital_arrows(cls) -> set[str]: + return {d.arrow for d in Direction.capitals() if d.arrow is not None} + @classmethod def from_str(cls, s: str) -> Direction: for v in Direction: @@ -163,6 +176,14 @@ def x(self) -> int: def y(self) -> int: return self.vector.y + @property + def is_horizontal(self) -> bool: + return self == Direction.LEFT or self == Direction.RIGHT + + @property + def is_vertical(self) -> bool: + return self == Direction.UP or self == Direction.DOWN + def turn(self, turn: Turn) -> Direction: if self == Direction.UP: return ( @@ -220,3 +241,28 @@ def draw( ] ) ) + + +class Line(NamedTuple): + position: Position + vector: Vector + + @classmethod + def of(cls, position: Position, vector: Vector) -> Line: + return Line(position, vector) + + def intersection(self, other: Line) -> tuple[float, float] | None: + x1, y1 = self.position.x, self.position.y + x2, y2 = x1 + 100 * self.vector.x, y1 + 100 * self.vector.y + x3, y3 = other.position.x, other.position.y + x4, y4 = x3 + 100 * other.vector.x, y3 + 100 * other.vector.y + d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) + if d == 0: + return None + x = ( + (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4) + ) / d + y = ( + (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4) + ) / d + return (x, y) diff --git a/src/main/python/aoc/geometry3d.py b/src/main/python/aoc/geometry3d.py index 4bada630..2b8e6c42 100644 --- a/src/main/python/aoc/geometry3d.py +++ b/src/main/python/aoc/geometry3d.py @@ -1,6 +1,11 @@ from __future__ import annotations + +import itertools +from typing import Iterator from typing import NamedTuple +from aoc.range import RangeInclusive + class Point3D(NamedTuple): x: int @@ -45,3 +50,42 @@ def to_from( cls, to: Position3D, from_: Position3D = Position3D.of(0, 0, 0) ) -> Vector3D: return Vector3D(to.x - from_.x, to.y - from_.y, to.z - from_.z) + + +class Cuboid(NamedTuple): + x1: int + x2: int + y1: int + y2: int + z1: int + z2: int + + @classmethod + def of( + cls, x1: int, x2: int, y1: int, y2: int, z1: int, z2: int + ) -> Cuboid: + return Cuboid(x1, x2, y1, y2, z1, z2) + + def positions(self) -> Iterator[Position3D]: + for (x, y), z in itertools.product( + itertools.product( + range(self.x1, self.x2 + 1), range(self.y1, self.y2 + 1) + ), + range(self.z1, self.z2 + 1), + ): + yield Position3D(x, y, z) + + def overlap_x(self, other: Cuboid) -> bool: + return RangeInclusive.between(self.x1, self.x2).is_overlapped_by( + RangeInclusive.between(other.x1, other.x2) + ) + + def overlap_y(self, other: Cuboid) -> bool: + return RangeInclusive.between(self.y1, self.y2).is_overlapped_by( + RangeInclusive.between(other.y1, other.y2) + ) + + def overlap_z(self, other: Cuboid) -> bool: + return RangeInclusive.between(self.z1, self.z2).is_overlapped_by( + RangeInclusive.between(other.z1, other.z2) + ) diff --git a/src/main/python/aoc/graph.py b/src/main/python/aoc/graph.py index 491f01da..8ddf9e9a 100644 --- a/src/main/python/aoc/graph.py +++ b/src/main/python/aoc/graph.py @@ -1,71 +1,67 @@ #! /usr/bin/env python3 -from collections import defaultdict, deque +import sys +from collections import defaultdict +from collections import deque from collections.abc import Iterator from queue import PriorityQueue from typing import Callable +from typing import NamedTuple from typing import TypeVar T = TypeVar("T") -def a_star( +def bfs( start: T, is_end: Callable[[T], bool], adjacent: Callable[[T], Iterator[T]], - get_cost: Callable[[T], int], -) -> tuple[int, dict[T, int], list[T]]: - q: PriorityQueue[tuple[int, T]] = PriorityQueue() - q.put((0, start)) - best: defaultdict[T, int] = defaultdict(lambda: 1_000_000_000) - best[start] = 0 +) -> tuple[int, list[T]]: + q: deque[tuple[int, T]] = deque() + q.append((0, start)) + seen: set[T] = set() + seen.add(start) parent: dict[T, T] = {} - path = [] - while not q.empty(): - cost, node = q.get() + while not len(q) == 0: + distance, node = q.popleft() if is_end(node): path = [node] curr = node while curr in parent: curr = parent[curr] path.append(curr) - break - c_total = best[node] + return distance, path for n in adjacent(node): - new_risk = c_total + get_cost(n) - if new_risk < best[n]: - best[n] = new_risk - parent[n] = node - q.put((new_risk, n)) - return cost, best, path + if n in seen: + continue + seen.add(n) + parent[n] = node + q.append((distance + 1, n)) + raise RuntimeError("unsolvable") -def bfs( +def bfs_full( start: T, is_end: Callable[[T], bool], adjacent: Callable[[T], Iterator[T]], -) -> tuple[int, list[T]]: +) -> tuple[dict[T, int], dict[T, T]]: q: deque[tuple[int, T]] = deque() q.append((0, start)) seen: set[T] = set() seen.add(start) parent: dict[T, T] = {} + dists = defaultdict[T, int](int) while not len(q) == 0: distance, node = q.popleft() if is_end(node): - path = [node] - curr = node - while curr in parent: - curr = parent[curr] - path.append(curr) - return distance, path + dists[node] = distance for n in adjacent(node): if n in seen: continue seen.add(n) parent[n] = node q.append((distance + 1, n)) - raise RuntimeError("unsolvable") + return dists, parent def flood_fill( @@ -84,3 +80,113 @@ def flood_fill( seen.add(n) q.append(n) return seen + + +class AllResults[T](NamedTuple): + start: T + distances: dict[T, int] + prececessors: dict[T, list[T]] + + def get_paths(self, t: T) -> list[list[T]]: + if t == self.start: + return [[self.start]] + paths = list[list[T]]() + for predecessor in self.prececessors.get(t, list()): + for path in self.get_paths(predecessor): + paths.append(path + [t]) + return paths + + def get_distance(self, t: T) -> int: + return self.distances[t] + + +class Dijkstra: + + @classmethod + def dijkstra( + cls, + start: T, + is_end: Callable[[T], bool], + adjacent: Callable[[T], Iterator[T]], + get_cost: Callable[[T, T], int], + ) -> tuple[int, dict[T, int], list[T]]: + q: PriorityQueue[tuple[int, T]] = PriorityQueue() + q.put((0, start)) + best: defaultdict[T, int] = defaultdict(lambda: sys.maxsize) + best[start] = 0 + parent: dict[T, T] = {} + path = [] + while not q.empty(): + cost, node = q.get() + if is_end(node): + path = [node] + curr = node + while curr in parent: + curr = parent[curr] + path.append(curr) + break + c_total = best[node] + for n in adjacent(node): + new_risk = c_total + get_cost(node, n) + if new_risk < best[n]: + best[n] = new_risk + parent[n] = node + q.put((new_risk, n)) + return cost, best, path + + @classmethod + def distance( + cls, + start: T, + is_end: Callable[[T], bool], + adjacent: Callable[[T], Iterator[T]], + get_cost: Callable[[T, T], int], + ) -> int: + q = PriorityQueue[tuple[int, T]]() + q.put((0, start)) + distances = defaultdict[T, int](lambda: sys.maxsize) + distances[start] = 0 + while not q.empty(): + distance, node = q.get() + if is_end(node): + return distance + total = distances[node] + if distance > total: + continue + for n in adjacent(node): + new_distance = total + get_cost(node, n) + if new_distance < distances[n]: + distances[n] = new_distance + q.put((new_distance, n)) + raise RuntimeError("unreachable") + + @classmethod + def all( + cls, + start: T, + is_end: Callable[[T], bool], + adjacent: Callable[[T], Iterator[T]], + get_cost: Callable[[T, T], int], + ) -> AllResults[T]: + q = PriorityQueue[tuple[int, T]]() + q.put((0, start)) + distances = defaultdict[T, int](lambda: sys.maxsize) + distances[start] = 0 + predecessors = dict[T, list[T]]() + while not q.empty(): + distance, node = q.get() + if is_end(node): + break + total = distances[node] + if distance > total: + continue + for n in adjacent(node): + new_distance = total + get_cost(node, n) + dist_n = distances[n] + if new_distance < dist_n: + distances[n] = new_distance + predecessors[n] = [node] + q.put((new_distance, n)) + elif new_distance == dist_n: + predecessors.setdefault(n, []).append(node) + return AllResults(start, distances, predecessors) diff --git a/src/main/python/aoc/grid.py b/src/main/python/aoc/grid.py index f874410a..4d5ef1b7 100644 --- a/src/main/python/aoc/grid.py +++ b/src/main/python/aoc/grid.py @@ -29,6 +29,27 @@ def get_capital_neighbours(self) -> Iterator[Cell]: def get_all_neighbours(self) -> Iterator[Cell]: return (self.at(d) for d in Direction.octants()) + def to(self, other: Cell) -> Direction: + if self.row == other.row: + if self.col == other.col: + return Direction.NONE + return Direction.RIGHT if self.col < other.col else Direction.LEFT + elif self.col == other.col: + return Direction.DOWN if self.row < other.row else Direction.UP + raise ValueError("not supported") + + def get_all_at_manhattan_distance(self, distance: int) -> Iterator[Cell]: + r, c = self.row, self.col + for dr in range(distance + 1): + dc = distance - dr + for rr, cc in { + (r + dr, c + dc), + (r + dr, c - dc), + (r - dr, c + dc), + (r - dr, c - dc), + }: + yield Cell(rr, cc) + @unique class IterDir(Enum): @@ -144,6 +165,34 @@ def get_cells(self) -> Iterator[Cell]: for c in range(self.get_width()) ) + def get_cells_without_border(self) -> Iterator[Cell]: + return ( + Cell(r, c) + for r in range(1, self.get_height() - 1) + for c in range(1, self.get_width() - 1) + ) + + def get_cells_dir(self, cell: Cell, dir: Direction) -> Iterator[Cell]: + if dir == Direction.UP: + iter_dir = IterDir.UP + elif dir == Direction.RIGHT: + iter_dir = IterDir.RIGHT + elif dir == Direction.DOWN: + iter_dir = IterDir.DOWN + elif dir == Direction.LEFT: + iter_dir = IterDir.LEFT + elif dir == Direction.RIGHT_AND_UP: + iter_dir = IterDir.RIGHT_AND_UP + elif dir == Direction.RIGHT_AND_DOWN: + iter_dir = IterDir.RIGHT_AND_DOWN + elif dir == Direction.LEFT_AND_UP: + iter_dir = IterDir.LEFT_AND_UP + elif dir == Direction.LEFT_AND_DOWN: + iter_dir = IterDir.LEFT_AND_DOWN + else: + raise ValueError(f"Not supported: {dir}") + return (c for c in GridIterator(self, cell, iter_dir)) + def get_cells_n(self, cell: Cell) -> Iterator[Cell]: return (c for c in GridIterator(self, cell, IterDir.UP)) @@ -250,6 +299,9 @@ def get_value_at(self, row: int, col: int) -> str: return self.values[row][col] def set_value(self, c: Cell, value: str) -> None: + assert self.is_valid_row_index(c.row) and self.is_valid_col_index( + c.col + ) self.values[c.row][c.col] = value def get_row_as_string(self, row: int) -> str: @@ -262,3 +314,17 @@ def get_col_as_string(self, col: int) -> str: def get_cols_as_strings(self) -> Iterator[str]: return (self.get_col_as_string(col) for col in range(self.get_width())) + + def roll_row(self, row_idx: int, amount: int) -> None: + assert self.is_valid_row_index(row_idx) + new_row = ( + self.values[row_idx][-amount:] + self.values[row_idx][:-amount] + ) + self.values[row_idx] = new_row + + def roll_column(self, col_idx: int, amount: int) -> None: + assert self.is_valid_col_index(col_idx) + old_col = self.get_col_as_string(col_idx) + new_col = old_col[-amount:] + old_col[:-amount] + for r, ch in enumerate(new_col): + self.set_value(Cell(r, col_idx), ch) diff --git a/src/main/python/aoc/implementation_tables/config.py b/src/main/python/aoc/implementation_tables/config.py index 65754d69..76a09bd0 100644 --- a/src/main/python/aoc/implementation_tables/config.py +++ b/src/main/python/aoc/implementation_tables/config.py @@ -1,10 +1,11 @@ #! /usr/bin/env python3 -import os -import yaml import logging +import os from typing import NamedTuple +import yaml + log = logging.getLogger(__name__) @@ -18,19 +19,26 @@ class Row(NamedTuple): class Config: def get_rows(self) -> list[Row]: rows = list[Row]() - for row in self.implementation_tables['rows']: - rows.append(Row(row['language'], - row['base_dir'], - row['pattern'], - row['ext'])) + for row in self.implementation_tables[ # type:ignore[attr-defined] + "rows" + ]: + rows.append( + Row( + row["language"], + row["base_dir"], + row["pattern"], + row["ext"], + ) + ) return rows -with open(os.path.join('.', 'setup.yml'), 'r') as f: +with open(os.path.join(".", "setup.yml"), "r") as f: setup_yaml = f.read() config = yaml.load( # nosec "!!python/object:" + __name__ + ".Config\n" + setup_yaml, - Loader=yaml.Loader) + Loader=yaml.Loader, +) log.debug(config.__dict__) diff --git a/src/main/python/aoc/implementation_tables/implementation_tables.py b/src/main/python/aoc/implementation_tables/implementation_tables.py index fde3dc30..20cd4646 100644 --- a/src/main/python/aoc/implementation_tables/implementation_tables.py +++ b/src/main/python/aoc/implementation_tables/implementation_tables.py @@ -1,53 +1,60 @@ #! /usr/bin/env python3 -import sys -import os import logging +import os +import sys from datetime import datetime from typing import IO + if __name__ == "__main__": - from config import Row, config + from config import Row # type:ignore[import-not-found] + from config import config else: - from aoc.implementation_tables.config import Row, config + from aoc.implementation_tables.config import Row + from aoc.implementation_tables.config import config log = logging.getLogger(__name__) def _build_link(base_dir: str, pattern: str, year: int, day: int) -> str: - path = os.path.join(base_dir, pattern.format(year=year, day=day)) + path = base_dir + "/" + pattern.format(year=year, day=day) return f"[✓]({path})" if os.path.exists(path) else "" -def _build_row(days) -> str: +def _build_row(days: list[str]) -> str: return "| " + " | ".join(days) + " |" def _print_year(year: int, rows: list[Row], f: IO[str]) -> None: log.debug(f"Adding {year}") - print("| " + _build_row(str(day) for day in range(1, 26)), file=f) - print("| ---" + _build_row("---" for day in range(1, 26)), file=f) + print("| " + _build_row([str(day) for day in range(1, 26)]), file=f) + print("| ---" + _build_row(["---" for day in range(1, 26)]), file=f) for row in rows: log.debug(f"Adding {row.language}") + days = [ + _build_link(row.base_dir, row.pattern + row.ext, year, day) + for day in range(1, 26) + ] + if len("".join(days)) == 0: + continue print( - "| " + row.language + " " - + _build_row( - _build_link(row.base_dir, row.pattern + row.ext, year, day) - for day in range(1, 26)), - file=f) + "| " + row.language + " " + _build_row(days), + file=f, + ) def _get_rows() -> list[Row]: rows = config.get_rows() log.debug(rows) - return rows + return rows # type:ignore[no-any-return] def main(file_name: str) -> None: log.debug(f"file: {file_name}") rows = _get_rows() - with open(file_name, 'r') as f: + with open(file_name, "r", encoding="utf-8") as f: tmp = f.read() - with open(file_name, 'w') as f: + with open(file_name, "w", encoding="utf-8") as f: in_table = False for line in tmp.splitlines(): if line.startswith("

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy