From 6d8fb621d1e93b83786890d6d9cb6f0d8defa8ae Mon Sep 17 00:00:00 2001 From: DJ O'Leary Date: Fri, 26 Jun 2026 18:04:09 +0200 Subject: refactor!: invert repo directory structure now is language first --- .githooks/commit-msg | 15 + .githooks/pre-push | 18 + .gitignore | 2 +- 2023/go/.envrc | 1 - 2023/go/.gitignore | 21 - 2023/go/README.md | 21 - 2023/go/Taskfile.yml | 23 - 2023/go/flake.lock | 27 - 2023/go/flake.nix | 28 - 2023/go/go.mod | 11 - 2023/go/go.sum | 8 - 2023/go/internal/day_01/day_01.go | 114 ---- 2023/go/internal/day_01/day_01_test.go | 31 - 2023/go/internal/day_02/part_01/part_01.go | 138 ---- 2023/go/internal/day_02/part_01/part_01_test.go | 102 --- 2023/go/internal/day_02/part_02/part_02.go | 114 ---- 2023/go/internal/day_02/part_02/part_02_test.go | 84 --- 2023/go/internal/day_03/part_01/part_01.go | 123 ---- 2023/go/internal/day_03/part_01/part_01_test.go | 78 --- 2023/go/internal/day_04/part_01/part_01.go | 130 ---- 2023/go/internal/day_04/part_01/part_01_test.go | 48 -- 2023/go/internal/day_04/part_02/part_02.go | 142 ---- 2023/go/internal/day_04/part_02/part_02_test.go | 143 ---- 2023/go/internal/day_06/part_01/part_01.go | 85 --- 2023/go/internal/day_06/part_01/part_01_test.go | 96 --- 2023/go/internal/day_06/part_02/part_02.go | 81 --- 2023/go/internal/day_06/part_02/part_02_test.go | 84 --- 2023/go/internal/day_07/part_01/part_01.go | 273 -------- 2023/go/internal/day_07/part_01/part_01_test.go | 432 ------------ 2023/go/main.go | 86 --- 2024/php/.envrc | 1 - 2024/php/README.md | 11 - 2024/php/day_04/Grid.php | 69 -- 2024/php/day_04/Input.php | 25 - 2024/php/day_04/Position.php | 79 --- 2024/php/day_04/part_01.php | 94 --- 2024/php/flake.lock | 27 - 2024/php/flake.nix | 43 -- 2025/ocaml/.editorconfig | 27 - 2025/ocaml/.env.template | 1 - 2025/ocaml/.envrc | 3 - 2025/ocaml/.gitattributes | 7 - 2025/ocaml/.githooks/commit-msg | 15 - 2025/ocaml/.githooks/pre-push | 18 - 2025/ocaml/.gitignore | 45 -- 2025/ocaml/.ocamlformat | 3 - 2025/ocaml/CODEOWNERS | 1 - 2025/ocaml/CONTRIBUTING.md | 5 - 2025/ocaml/LICENSE | 25 - 2025/ocaml/README.md | 54 -- 2025/ocaml/Taskfile.yaml | 107 --- 2025/ocaml/advent_of_code.opam | 42 -- 2025/ocaml/bin/dune | 11 - 2025/ocaml/bin/main.ml | 16 - 2025/ocaml/docs/Main.html | 18 - 2025/ocaml/docs/index.html | 22 - 2025/ocaml/docs/index_attributes.html | 17 - 2025/ocaml/docs/index_class_types.html | 17 - 2025/ocaml/docs/index_classes.html | 17 - 2025/ocaml/docs/index_exceptions.html | 17 - 2025/ocaml/docs/index_extensions.html | 17 - 2025/ocaml/docs/index_methods.html | 17 - 2025/ocaml/docs/index_module_types.html | 17 - 2025/ocaml/docs/index_modules.html | 20 - 2025/ocaml/docs/index_types.html | 17 - 2025/ocaml/docs/index_values.html | 17 - 2025/ocaml/docs/style.css | 43 -- 2025/ocaml/docs/type_Main.html | 10 - 2025/ocaml/dune-project | 40 -- 2025/ocaml/flake.lock | 27 - 2025/ocaml/flake.nix | 52 -- 2025/ocaml/lib/application/d01p01.ml | 53 -- 2025/ocaml/lib/application/d01p01.mli | 4 - 2025/ocaml/lib/application/d01p02.ml | 55 -- 2025/ocaml/lib/application/d01p02.mli | 4 - 2025/ocaml/lib/application/d02p01.ml | 54 -- 2025/ocaml/lib/application/d02p01.mli | 4 - 2025/ocaml/lib/application/d02p02.ml | 86 --- 2025/ocaml/lib/application/d02p02.mli | 4 - 2025/ocaml/lib/application/d03p01.ml | 23 - 2025/ocaml/lib/application/d03p01.mli | 4 - 2025/ocaml/lib/application/d03p02.ml | 23 - 2025/ocaml/lib/application/d03p02.mli | 4 - 2025/ocaml/lib/application/d04p01.ml | 48 -- 2025/ocaml/lib/application/d04p01.mli | 4 - 2025/ocaml/lib/application/d04p02.ml | 64 -- 2025/ocaml/lib/application/d04p02.mli | 4 - 2025/ocaml/lib/application/d05p01.ml | 3 - 2025/ocaml/lib/application/d05p01.mli | 4 - 2025/ocaml/lib/application/d06p01.ml | 50 -- 2025/ocaml/lib/application/d06p01.mli | 4 - 2025/ocaml/lib/application/d06p02.ml | 50 -- 2025/ocaml/lib/application/d06p02.mli | 4 - 2025/ocaml/lib/application/dune | 14 - 2025/ocaml/lib/application/parser.ml | 36 - 2025/ocaml/lib/domain/d01p01.ml | 76 --- 2025/ocaml/lib/domain/d01p02.ml | 95 --- 2025/ocaml/lib/domain/d02p01.ml | 33 - 2025/ocaml/lib/domain/d02p01.mli | 36 - 2025/ocaml/lib/domain/d02p02.ml | 29 - 2025/ocaml/lib/domain/d02p02.mli | 30 - 2025/ocaml/lib/domain/d03p01.ml | 114 ---- 2025/ocaml/lib/domain/d03p02.ml | 116 ---- 2025/ocaml/lib/domain/dune | 13 - 2025/ocaml/lib/domain/matrix.ml | 196 ------ 2025/ocaml/lib/domain/range.ml | 67 -- 2025/ocaml/lib/infrastructure/dune | 15 - 2025/ocaml/lib/infrastructure/file.ml | 4 - 2025/ocaml/lib/infrastructure/file.mli | 3 - 2025/ocaml/lib/presentation/cli.ml | 62 -- 2025/ocaml/lib/presentation/dune | 16 - 2025/ocaml/test/application/dune | 9 - 2025/ocaml/test/application/test_d01p01.ml | 23 - 2025/ocaml/test/application/test_d01p02.ml | 23 - 2025/ocaml/test/application/test_d04p01.ml | 24 - 2025/ocaml/test/domain/dune | 11 - 2025/ocaml/test/domain/test_d01p01.ml | 29 - 2025/ocaml/test/domain/test_d01p02.ml | 41 -- 2025/ocaml/test/domain/test_d03p01.ml | 857 ------------------------ 2025/ocaml/test/infrastructure/dune | 8 - 2025/ocaml/test/infrastructure/test_example.ml | 0 2025/ocaml/test/presentation/dune | 11 - 2025/ocaml/test/presentation/test_example.ml | 0 Taskfile.yml | 10 +- go/2023/.envrc | 1 + go/2023/.gitignore | 21 + go/2023/README.md | 21 + go/2023/Taskfile.yml | 23 + go/2023/flake.lock | 27 + go/2023/flake.nix | 28 + go/2023/go.mod | 11 + go/2023/go.sum | 8 + go/2023/internal/day_01/day_01.go | 114 ++++ go/2023/internal/day_01/day_01_test.go | 31 + go/2023/internal/day_02/part_01/part_01.go | 138 ++++ go/2023/internal/day_02/part_01/part_01_test.go | 102 +++ go/2023/internal/day_02/part_02/part_02.go | 114 ++++ go/2023/internal/day_02/part_02/part_02_test.go | 84 +++ go/2023/internal/day_03/part_01/part_01.go | 123 ++++ go/2023/internal/day_03/part_01/part_01_test.go | 78 +++ go/2023/internal/day_04/part_01/part_01.go | 130 ++++ go/2023/internal/day_04/part_01/part_01_test.go | 48 ++ go/2023/internal/day_04/part_02/part_02.go | 142 ++++ go/2023/internal/day_04/part_02/part_02_test.go | 143 ++++ go/2023/internal/day_06/part_01/part_01.go | 85 +++ go/2023/internal/day_06/part_01/part_01_test.go | 96 +++ go/2023/internal/day_06/part_02/part_02.go | 81 +++ go/2023/internal/day_06/part_02/part_02_test.go | 84 +++ go/2023/internal/day_07/part_01/part_01.go | 273 ++++++++ go/2023/internal/day_07/part_01/part_01_test.go | 432 ++++++++++++ go/2023/main.go | 86 +++ ocaml/.editorconfig | 27 + ocaml/.env.template | 1 + ocaml/.envrc | 3 + ocaml/.gitattributes | 7 + ocaml/.gitignore | 45 ++ ocaml/.ocamlformat | 3 + ocaml/CODEOWNERS | 1 + ocaml/CONTRIBUTING.md | 5 + ocaml/LICENSE | 25 + ocaml/README.md | 54 ++ ocaml/Taskfile.yaml | 103 +++ ocaml/advent_of_code.opam | 42 ++ ocaml/bin/dune | 11 + ocaml/bin/main.ml | 16 + ocaml/docs/Main.html | 18 + ocaml/docs/index.html | 22 + ocaml/docs/index_attributes.html | 17 + ocaml/docs/index_class_types.html | 17 + ocaml/docs/index_classes.html | 17 + ocaml/docs/index_exceptions.html | 17 + ocaml/docs/index_extensions.html | 17 + ocaml/docs/index_methods.html | 17 + ocaml/docs/index_module_types.html | 17 + ocaml/docs/index_modules.html | 20 + ocaml/docs/index_types.html | 17 + ocaml/docs/index_values.html | 17 + ocaml/docs/style.css | 43 ++ ocaml/docs/type_Main.html | 10 + ocaml/dune-project | 40 ++ ocaml/flake.lock | 27 + ocaml/flake.nix | 52 ++ ocaml/lib/application/d01p01.ml | 53 ++ ocaml/lib/application/d01p01.mli | 4 + ocaml/lib/application/d01p02.ml | 55 ++ ocaml/lib/application/d01p02.mli | 4 + ocaml/lib/application/d02p01.ml | 54 ++ ocaml/lib/application/d02p01.mli | 4 + ocaml/lib/application/d02p02.ml | 86 +++ ocaml/lib/application/d02p02.mli | 4 + ocaml/lib/application/d03p01.ml | 23 + ocaml/lib/application/d03p01.mli | 4 + ocaml/lib/application/d03p02.ml | 23 + ocaml/lib/application/d03p02.mli | 4 + ocaml/lib/application/d04p01.ml | 48 ++ ocaml/lib/application/d04p01.mli | 4 + ocaml/lib/application/d04p02.ml | 64 ++ ocaml/lib/application/d04p02.mli | 4 + ocaml/lib/application/d05p01.ml | 3 + ocaml/lib/application/d05p01.mli | 4 + ocaml/lib/application/d06p01.ml | 50 ++ ocaml/lib/application/d06p01.mli | 4 + ocaml/lib/application/d06p02.ml | 50 ++ ocaml/lib/application/d06p02.mli | 4 + ocaml/lib/application/dune | 14 + ocaml/lib/application/parser.ml | 36 + ocaml/lib/domain/d01p01.ml | 76 +++ ocaml/lib/domain/d01p02.ml | 95 +++ ocaml/lib/domain/d02p01.ml | 33 + ocaml/lib/domain/d02p01.mli | 36 + ocaml/lib/domain/d02p02.ml | 29 + ocaml/lib/domain/d02p02.mli | 30 + ocaml/lib/domain/d03p01.ml | 114 ++++ ocaml/lib/domain/d03p02.ml | 116 ++++ ocaml/lib/domain/dune | 13 + ocaml/lib/domain/matrix.ml | 196 ++++++ ocaml/lib/domain/range.ml | 67 ++ ocaml/lib/infrastructure/dune | 15 + ocaml/lib/infrastructure/file.ml | 4 + ocaml/lib/infrastructure/file.mli | 3 + ocaml/lib/presentation/cli.ml | 62 ++ ocaml/lib/presentation/dune | 16 + ocaml/test/application/dune | 9 + ocaml/test/application/test_d01p01.ml | 23 + ocaml/test/application/test_d01p02.ml | 23 + ocaml/test/application/test_d04p01.ml | 24 + ocaml/test/domain/dune | 11 + ocaml/test/domain/test_d01p01.ml | 29 + ocaml/test/domain/test_d01p02.ml | 41 ++ ocaml/test/domain/test_d03p01.ml | 857 ++++++++++++++++++++++++ ocaml/test/infrastructure/dune | 8 + ocaml/test/infrastructure/test_example.ml | 0 ocaml/test/presentation/dune | 11 + ocaml/test/presentation/test_example.ml | 0 php/2024/.envrc | 1 + php/2024/README.md | 11 + php/2024/day_04/Grid.php | 69 ++ php/2024/day_04/Input.php | 25 + php/2024/day_04/Position.php | 79 +++ php/2024/day_04/part_01.php | 94 +++ php/2024/flake.lock | 27 + php/2024/flake.nix | 43 ++ 242 files changed, 6186 insertions(+), 6186 deletions(-) create mode 100755 .githooks/commit-msg create mode 100755 .githooks/pre-push delete mode 100644 2023/go/.envrc delete mode 100644 2023/go/.gitignore delete mode 100644 2023/go/README.md delete mode 100644 2023/go/Taskfile.yml delete mode 100644 2023/go/flake.lock delete mode 100644 2023/go/flake.nix delete mode 100644 2023/go/go.mod delete mode 100644 2023/go/go.sum delete mode 100644 2023/go/internal/day_01/day_01.go delete mode 100644 2023/go/internal/day_01/day_01_test.go delete mode 100644 2023/go/internal/day_02/part_01/part_01.go delete mode 100644 2023/go/internal/day_02/part_01/part_01_test.go delete mode 100644 2023/go/internal/day_02/part_02/part_02.go delete mode 100644 2023/go/internal/day_02/part_02/part_02_test.go delete mode 100644 2023/go/internal/day_03/part_01/part_01.go delete mode 100644 2023/go/internal/day_03/part_01/part_01_test.go delete mode 100644 2023/go/internal/day_04/part_01/part_01.go delete mode 100644 2023/go/internal/day_04/part_01/part_01_test.go delete mode 100644 2023/go/internal/day_04/part_02/part_02.go delete mode 100644 2023/go/internal/day_04/part_02/part_02_test.go delete mode 100644 2023/go/internal/day_06/part_01/part_01.go delete mode 100644 2023/go/internal/day_06/part_01/part_01_test.go delete mode 100644 2023/go/internal/day_06/part_02/part_02.go delete mode 100644 2023/go/internal/day_06/part_02/part_02_test.go delete mode 100644 2023/go/internal/day_07/part_01/part_01.go delete mode 100644 2023/go/internal/day_07/part_01/part_01_test.go delete mode 100644 2023/go/main.go delete mode 100644 2024/php/.envrc delete mode 100644 2024/php/README.md delete mode 100644 2024/php/day_04/Grid.php delete mode 100644 2024/php/day_04/Input.php delete mode 100644 2024/php/day_04/Position.php delete mode 100644 2024/php/day_04/part_01.php delete mode 100644 2024/php/flake.lock delete mode 100644 2024/php/flake.nix delete mode 100644 2025/ocaml/.editorconfig delete mode 100644 2025/ocaml/.env.template delete mode 100644 2025/ocaml/.envrc delete mode 100644 2025/ocaml/.gitattributes delete mode 100755 2025/ocaml/.githooks/commit-msg delete mode 100755 2025/ocaml/.githooks/pre-push delete mode 100644 2025/ocaml/.gitignore delete mode 100644 2025/ocaml/.ocamlformat delete mode 100644 2025/ocaml/CODEOWNERS delete mode 100644 2025/ocaml/CONTRIBUTING.md delete mode 100644 2025/ocaml/LICENSE delete mode 100644 2025/ocaml/README.md delete mode 100644 2025/ocaml/Taskfile.yaml delete mode 100644 2025/ocaml/advent_of_code.opam delete mode 100644 2025/ocaml/bin/dune delete mode 100644 2025/ocaml/bin/main.ml delete mode 100644 2025/ocaml/docs/Main.html delete mode 100644 2025/ocaml/docs/index.html delete mode 100644 2025/ocaml/docs/index_attributes.html delete mode 100644 2025/ocaml/docs/index_class_types.html delete mode 100644 2025/ocaml/docs/index_classes.html delete mode 100644 2025/ocaml/docs/index_exceptions.html delete mode 100644 2025/ocaml/docs/index_extensions.html delete mode 100644 2025/ocaml/docs/index_methods.html delete mode 100644 2025/ocaml/docs/index_module_types.html delete mode 100644 2025/ocaml/docs/index_modules.html delete mode 100644 2025/ocaml/docs/index_types.html delete mode 100644 2025/ocaml/docs/index_values.html delete mode 100644 2025/ocaml/docs/style.css delete mode 100644 2025/ocaml/docs/type_Main.html delete mode 100644 2025/ocaml/dune-project delete mode 100644 2025/ocaml/flake.lock delete mode 100644 2025/ocaml/flake.nix delete mode 100644 2025/ocaml/lib/application/d01p01.ml delete mode 100644 2025/ocaml/lib/application/d01p01.mli delete mode 100644 2025/ocaml/lib/application/d01p02.ml delete mode 100644 2025/ocaml/lib/application/d01p02.mli delete mode 100644 2025/ocaml/lib/application/d02p01.ml delete mode 100644 2025/ocaml/lib/application/d02p01.mli delete mode 100644 2025/ocaml/lib/application/d02p02.ml delete mode 100644 2025/ocaml/lib/application/d02p02.mli delete mode 100644 2025/ocaml/lib/application/d03p01.ml delete mode 100644 2025/ocaml/lib/application/d03p01.mli delete mode 100644 2025/ocaml/lib/application/d03p02.ml delete mode 100644 2025/ocaml/lib/application/d03p02.mli delete mode 100644 2025/ocaml/lib/application/d04p01.ml delete mode 100644 2025/ocaml/lib/application/d04p01.mli delete mode 100644 2025/ocaml/lib/application/d04p02.ml delete mode 100644 2025/ocaml/lib/application/d04p02.mli delete mode 100644 2025/ocaml/lib/application/d05p01.ml delete mode 100644 2025/ocaml/lib/application/d05p01.mli delete mode 100644 2025/ocaml/lib/application/d06p01.ml delete mode 100644 2025/ocaml/lib/application/d06p01.mli delete mode 100644 2025/ocaml/lib/application/d06p02.ml delete mode 100644 2025/ocaml/lib/application/d06p02.mli delete mode 100644 2025/ocaml/lib/application/dune delete mode 100644 2025/ocaml/lib/application/parser.ml delete mode 100644 2025/ocaml/lib/domain/d01p01.ml delete mode 100644 2025/ocaml/lib/domain/d01p02.ml delete mode 100644 2025/ocaml/lib/domain/d02p01.ml delete mode 100644 2025/ocaml/lib/domain/d02p01.mli delete mode 100644 2025/ocaml/lib/domain/d02p02.ml delete mode 100644 2025/ocaml/lib/domain/d02p02.mli delete mode 100644 2025/ocaml/lib/domain/d03p01.ml delete mode 100644 2025/ocaml/lib/domain/d03p02.ml delete mode 100644 2025/ocaml/lib/domain/dune delete mode 100644 2025/ocaml/lib/domain/matrix.ml delete mode 100644 2025/ocaml/lib/domain/range.ml delete mode 100644 2025/ocaml/lib/infrastructure/dune delete mode 100644 2025/ocaml/lib/infrastructure/file.ml delete mode 100644 2025/ocaml/lib/infrastructure/file.mli delete mode 100644 2025/ocaml/lib/presentation/cli.ml delete mode 100644 2025/ocaml/lib/presentation/dune delete mode 100644 2025/ocaml/test/application/dune delete mode 100644 2025/ocaml/test/application/test_d01p01.ml delete mode 100644 2025/ocaml/test/application/test_d01p02.ml delete mode 100644 2025/ocaml/test/application/test_d04p01.ml delete mode 100644 2025/ocaml/test/domain/dune delete mode 100644 2025/ocaml/test/domain/test_d01p01.ml delete mode 100644 2025/ocaml/test/domain/test_d01p02.ml delete mode 100644 2025/ocaml/test/domain/test_d03p01.ml delete mode 100644 2025/ocaml/test/infrastructure/dune delete mode 100644 2025/ocaml/test/infrastructure/test_example.ml delete mode 100644 2025/ocaml/test/presentation/dune delete mode 100644 2025/ocaml/test/presentation/test_example.ml create mode 100644 go/2023/.envrc create mode 100644 go/2023/.gitignore create mode 100644 go/2023/README.md create mode 100644 go/2023/Taskfile.yml create mode 100644 go/2023/flake.lock create mode 100644 go/2023/flake.nix create mode 100644 go/2023/go.mod create mode 100644 go/2023/go.sum create mode 100644 go/2023/internal/day_01/day_01.go create mode 100644 go/2023/internal/day_01/day_01_test.go create mode 100644 go/2023/internal/day_02/part_01/part_01.go create mode 100644 go/2023/internal/day_02/part_01/part_01_test.go create mode 100644 go/2023/internal/day_02/part_02/part_02.go create mode 100644 go/2023/internal/day_02/part_02/part_02_test.go create mode 100644 go/2023/internal/day_03/part_01/part_01.go create mode 100644 go/2023/internal/day_03/part_01/part_01_test.go create mode 100644 go/2023/internal/day_04/part_01/part_01.go create mode 100644 go/2023/internal/day_04/part_01/part_01_test.go create mode 100644 go/2023/internal/day_04/part_02/part_02.go create mode 100644 go/2023/internal/day_04/part_02/part_02_test.go create mode 100644 go/2023/internal/day_06/part_01/part_01.go create mode 100644 go/2023/internal/day_06/part_01/part_01_test.go create mode 100644 go/2023/internal/day_06/part_02/part_02.go create mode 100644 go/2023/internal/day_06/part_02/part_02_test.go create mode 100644 go/2023/internal/day_07/part_01/part_01.go create mode 100644 go/2023/internal/day_07/part_01/part_01_test.go create mode 100644 go/2023/main.go create mode 100644 ocaml/.editorconfig create mode 100644 ocaml/.env.template create mode 100644 ocaml/.envrc create mode 100644 ocaml/.gitattributes create mode 100644 ocaml/.gitignore create mode 100644 ocaml/.ocamlformat create mode 100644 ocaml/CODEOWNERS create mode 100644 ocaml/CONTRIBUTING.md create mode 100644 ocaml/LICENSE create mode 100644 ocaml/README.md create mode 100644 ocaml/Taskfile.yaml create mode 100644 ocaml/advent_of_code.opam create mode 100644 ocaml/bin/dune create mode 100644 ocaml/bin/main.ml create mode 100644 ocaml/docs/Main.html create mode 100644 ocaml/docs/index.html create mode 100644 ocaml/docs/index_attributes.html create mode 100644 ocaml/docs/index_class_types.html create mode 100644 ocaml/docs/index_classes.html create mode 100644 ocaml/docs/index_exceptions.html create mode 100644 ocaml/docs/index_extensions.html create mode 100644 ocaml/docs/index_methods.html create mode 100644 ocaml/docs/index_module_types.html create mode 100644 ocaml/docs/index_modules.html create mode 100644 ocaml/docs/index_types.html create mode 100644 ocaml/docs/index_values.html create mode 100644 ocaml/docs/style.css create mode 100644 ocaml/docs/type_Main.html create mode 100644 ocaml/dune-project create mode 100644 ocaml/flake.lock create mode 100644 ocaml/flake.nix create mode 100644 ocaml/lib/application/d01p01.ml create mode 100644 ocaml/lib/application/d01p01.mli create mode 100644 ocaml/lib/application/d01p02.ml create mode 100644 ocaml/lib/application/d01p02.mli create mode 100644 ocaml/lib/application/d02p01.ml create mode 100644 ocaml/lib/application/d02p01.mli create mode 100644 ocaml/lib/application/d02p02.ml create mode 100644 ocaml/lib/application/d02p02.mli create mode 100644 ocaml/lib/application/d03p01.ml create mode 100644 ocaml/lib/application/d03p01.mli create mode 100644 ocaml/lib/application/d03p02.ml create mode 100644 ocaml/lib/application/d03p02.mli create mode 100644 ocaml/lib/application/d04p01.ml create mode 100644 ocaml/lib/application/d04p01.mli create mode 100644 ocaml/lib/application/d04p02.ml create mode 100644 ocaml/lib/application/d04p02.mli create mode 100644 ocaml/lib/application/d05p01.ml create mode 100644 ocaml/lib/application/d05p01.mli create mode 100644 ocaml/lib/application/d06p01.ml create mode 100644 ocaml/lib/application/d06p01.mli create mode 100644 ocaml/lib/application/d06p02.ml create mode 100644 ocaml/lib/application/d06p02.mli create mode 100644 ocaml/lib/application/dune create mode 100644 ocaml/lib/application/parser.ml create mode 100644 ocaml/lib/domain/d01p01.ml create mode 100644 ocaml/lib/domain/d01p02.ml create mode 100644 ocaml/lib/domain/d02p01.ml create mode 100644 ocaml/lib/domain/d02p01.mli create mode 100644 ocaml/lib/domain/d02p02.ml create mode 100644 ocaml/lib/domain/d02p02.mli create mode 100644 ocaml/lib/domain/d03p01.ml create mode 100644 ocaml/lib/domain/d03p02.ml create mode 100644 ocaml/lib/domain/dune create mode 100644 ocaml/lib/domain/matrix.ml create mode 100644 ocaml/lib/domain/range.ml create mode 100644 ocaml/lib/infrastructure/dune create mode 100644 ocaml/lib/infrastructure/file.ml create mode 100644 ocaml/lib/infrastructure/file.mli create mode 100644 ocaml/lib/presentation/cli.ml create mode 100644 ocaml/lib/presentation/dune create mode 100644 ocaml/test/application/dune create mode 100644 ocaml/test/application/test_d01p01.ml create mode 100644 ocaml/test/application/test_d01p02.ml create mode 100644 ocaml/test/application/test_d04p01.ml create mode 100644 ocaml/test/domain/dune create mode 100644 ocaml/test/domain/test_d01p01.ml create mode 100644 ocaml/test/domain/test_d01p02.ml create mode 100644 ocaml/test/domain/test_d03p01.ml create mode 100644 ocaml/test/infrastructure/dune create mode 100644 ocaml/test/infrastructure/test_example.ml create mode 100644 ocaml/test/presentation/dune create mode 100644 ocaml/test/presentation/test_example.ml create mode 100644 php/2024/.envrc create mode 100644 php/2024/README.md create mode 100644 php/2024/day_04/Grid.php create mode 100644 php/2024/day_04/Input.php create mode 100644 php/2024/day_04/Position.php create mode 100644 php/2024/day_04/part_01.php create mode 100644 php/2024/flake.lock create mode 100644 php/2024/flake.nix diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 0000000..1c1c287 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Commit message hook - enforce conventional commit format + +commit_regex='^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}|^fixup!' + +if ! grep -qE "$commit_regex" "$1"; then + echo "โŒ Invalid commit message format!" + echo "" + echo "Format: type(scope): description" + echo "Types: feat, fix, docs, style, refactor, test, chore" + echo "Example: feat(auth): add user login functionality" + echo "" + echo "Your message: $(cat $1)" + exit 1 +fi diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..51a8761 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Pre-push hook - run tests before pushing + +set -e + +echo "๐Ÿงช Running tests before push..." + +# Run the full test suite +task test + +if [ $? -eq 0 ]; then + echo "โœ… All tests passed! Push allowed." + exit 0 +else + echo "โŒ Tests failed! Push blocked." + echo "Fix the failing tests before pushing." + exit 1 +fi diff --git a/.gitignore b/.gitignore index 896581e..658b06c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .env .direnv -**/_input/ +.input/* diff --git a/2023/go/.envrc b/2023/go/.envrc deleted file mode 100644 index 3550a30..0000000 --- a/2023/go/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/2023/go/.gitignore b/2023/go/.gitignore deleted file mode 100644 index 9f4cb5b..0000000 --- a/2023/go/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -out/ - -# Go workspace file -go.work -go.work.sum diff --git a/2023/go/README.md b/2023/go/README.md deleted file mode 100644 index 9cda93b..0000000 --- a/2023/go/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Go 2023 - -## Getting Started - -### How to Build - -```sh -task build -``` - -### How to Run - -```sh -task run -``` - -### How to Test - -```sh -task test -``` diff --git a/2023/go/Taskfile.yml b/2023/go/Taskfile.yml deleted file mode 100644 index a07c24c..0000000 --- a/2023/go/Taskfile.yml +++ /dev/null @@ -1,23 +0,0 @@ -# https://taskfile.dev - -version: "3" -tasks: - default: - desc: Prints this message - cmds: - - cmd: task --list-all - build: - cmds: - - cmd: go build -o=out/Advent_of_Code_2023 main.go - run: - cmds: - - cmd: go run main.go {{.CLI_ARGS}} - test: - cmds: - - cmd: go test ./... - test:coverage: - desc: Runs tests and produce coverage report - cmds: - - cmd: mkdir --parents out - - cmd: go test -coverprofile=out/coverage.out ./... - - cmd: go tool cover -html=out/coverage.out diff --git a/2023/go/flake.lock b/2023/go/flake.lock deleted file mode 100644 index b7695d9..0000000 --- a/2023/go/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1741010256, - "narHash": "sha256-WZNlK/KX7Sni0RyqLSqLPbK8k08Kq7H7RijPJbq9KHM=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "ba487dbc9d04e0634c64e3b1f0d25839a0a68246", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/2023/go/flake.nix b/2023/go/flake.nix deleted file mode 100644 index 39dda89..0000000 --- a/2023/go/flake.nix +++ /dev/null @@ -1,28 +0,0 @@ -{ - description = "Advent of Code 2023: Go"; - - inputs = { - nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; - }; - - outputs = - { nixpkgs, ... }: - let - pkgs = nixpkgs.legacyPackages.x86_64-linux; - in - { - devShells.x86_64-linux.default = pkgs.mkShell { - # build tools - nativeBuildInputs = with pkgs; [ - go - ]; - # dependencies - buildInputs = with pkgs; [ - delve - gopls - gotools - golangci-lint - ]; - }; - }; -} diff --git a/2023/go/go.mod b/2023/go/go.mod deleted file mode 100644 index 9fba23c..0000000 --- a/2023/go/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/DJOLEARY/Advent_of_Code/2023/Go - -go 1.22.3 - -require github.com/outrigdev/outrig v0.8.2 - -require ( - github.com/google/uuid v1.6.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 // indirect -) diff --git a/2023/go/go.sum b/2023/go/go.sum deleted file mode 100644 index 3c60b26..0000000 --- a/2023/go/go.sum +++ /dev/null @@ -1,8 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/outrig v0.8.2 h1:+qR+N6BXvJmFXsHmksaAJk4sgItxHLbWXN+9K8W2UVM= -github.com/outrigdev/outrig v0.8.2/go.mod h1:8ULH17Fc55s9zkf6S2ndQ43UoiJ0j28t9A0lwjs7rQc= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= diff --git a/2023/go/internal/day_01/day_01.go b/2023/go/internal/day_01/day_01.go deleted file mode 100644 index 9cd0a49..0000000 --- a/2023/go/internal/day_01/day_01.go +++ /dev/null @@ -1,114 +0,0 @@ -package day_01 - -import ( - "errors" - "fmt" - "os" - "strconv" - "strings" -) - -func Solve() { - filename := "../_input/day_01.txt" - content := readFile(filename) - - numbers := []int{} - - lines := strings.Split(content, "\n") - for _, line := range lines { - first_num, last_num, err := findNumbers(line) - if err != nil { - continue - } - - num_str := first_num + last_num - num := strToInt(num_str) - numbers = append(numbers, num) - } - - acc := accumulate(numbers) - - fmt.Println(acc) -} - -func readFile(filename string) string { - content, err := os.ReadFile(filename) - if err != nil { - panic(err) - } - - return string(content) -} - -var num_map = map[string]string{ - "zero": "0", - "0": "0", - "one": "1", - "1": "1", - "two": "2", - "2": "2", - "three": "3", - "3": "3", - "four": "4", - "4": "4", - "five": "5", - "5": "5", - "six": "6", - "6": "6", - "seven": "7", - "7": "7", - "eight": "8", - "8": "8", - "nine": "9", - "9": "9", -} - -func findNumbers(line string) (string, string, error) { - if line == "" || line == "\n" { - return "", "", errors.New("Empty line") - } - - found_numbers := []string{} - window_size := 5 - for i := range line { - window_end := i + window_size - if window_end > len(line) { - window_end = len(line) - } - sub_str := line[i:window_end] - - for key, value := range num_map { - if strings.HasPrefix(sub_str, key) { - found_numbers = append(found_numbers, value) - } - } - } - - if len(found_numbers) == 0 { - return "", "", errors.New("No numbers found") - } - - first := found_numbers[0] - last := found_numbers[len(found_numbers)-1] - - return first, last, nil -} - -func accumulate(numbers []int) int { - acc := 0 - - for _, num := range numbers { - acc += num - } - - return acc -} - -func strToInt(str string) int { - num, err := strconv.ParseInt(str, 0, 0) - if err != nil { - panic(err) - } - - return int(num) -} diff --git a/2023/go/internal/day_01/day_01_test.go b/2023/go/internal/day_01/day_01_test.go deleted file mode 100644 index 8063619..0000000 --- a/2023/go/internal/day_01/day_01_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package day_01 - -import ( - "testing" -) - -type input string -type answer string - -func TestFindNumbers(t *testing.T) { - data := map[input]answer{ - "1abc2": "12", - "pqr3stu8vwx": "38", - "a1b2c3d4e5f": "15", - "treb7uchet": "77", - } - - for input, expected := range data { - actual_first, actual_last, err := findNumbers(string(input)) - - if err != nil { - t.Error("find_numbers returned an error") - } - - actual := actual_first + actual_last - - if actual != string(expected) { - t.Errorf("Expected %s, got %s", expected, actual) - } - } -} diff --git a/2023/go/internal/day_02/part_01/part_01.go b/2023/go/internal/day_02/part_01/part_01.go deleted file mode 100644 index 9efe20f..0000000 --- a/2023/go/internal/day_02/part_01/part_01.go +++ /dev/null @@ -1,138 +0,0 @@ -package part_01 - -import ( - "os" - "regexp" - "strconv" - "strings" -) - -type cubes struct { - red int - blue int - green int -} - -func Solve() { - contents, err := os.ReadFile("../_input/day_02.txt") - if err != nil { - panic(err) - } - - lines := strings.Split(string(contents), "\n") - possibleGameIds := processLines(lines) - - println(accumulate(possibleGameIds)) -} - -func processLines(lines []string) []int { - var possibleGames []int - - maxCubes := cubes{red: 12, blue: 14, green: 13} - - for _, line := range lines { - if line == "" || line == "\n" { - continue - } - - shownCubes := convertLineToCubes(line) - - isPossible := isPossibleGame(shownCubes, maxCubes) - if isPossible { - gameId := getGameId(line) - possibleGames = append(possibleGames, gameId) - } - } - - return possibleGames -} - -func convertLineToCubes(line string) []cubes { - var shownCubes []cubes - - redRegex := regexp.MustCompile(`(\d+) red`) - blueRegex := regexp.MustCompile(`(\d+) blue`) - greenRegex := regexp.MustCompile(`(\d+) green`) - - for _, round := range strings.Split(line, ";") { - var cubes cubes - - redMatches := redRegex.FindStringSubmatch(round) - if redMatches != nil { - red, err := strconv.Atoi(redMatches[1]) - if err != nil { - panic("Could not convert red to int: " + redMatches[1]) - } else { - cubes.red = red - } - } - - blueMatches := blueRegex.FindStringSubmatch(round) - if blueMatches != nil { - blue, err := strconv.Atoi(blueMatches[1]) - if err != nil { - panic("Could not convert green to int: " + blueMatches[1]) - } else { - cubes.blue = blue - } - } - - greenMatches := greenRegex.FindStringSubmatch(round) - if greenMatches != nil { - green, err := strconv.Atoi(greenMatches[1]) - if err != nil { - panic("Could not convert green to int: " + greenMatches[1]) - } else { - cubes.green = green - } - } - - shownCubes = append(shownCubes, cubes) - } - - return shownCubes -} - -func isPossibleGame(shownCubes []cubes, maxCubes cubes) bool { - for _, cubes := range shownCubes { - if cubes.red > maxCubes.red { - return false - } - - if cubes.blue > maxCubes.blue { - return false - } - - if cubes.green > maxCubes.green { - return false - } - } - - return true -} - -func getGameId(line string) int { - regex := regexp.MustCompile(`^Game (\d+):`) - matches := regex.FindStringSubmatch(line) - if matches == nil || len(matches) < 2 { - println(matches) - panic("No game id found in line: '" + line + "'") - } - - gameId, err := strconv.Atoi(matches[1]) - if err != nil { - panic("Could not convert game id to int: " + matches[1]) - } - - return gameId -} - -func accumulate(gameIds []int) int { - var acc int - - for _, gameId := range gameIds { - acc += gameId - } - - return acc -} diff --git a/2023/go/internal/day_02/part_01/part_01_test.go b/2023/go/internal/day_02/part_01/part_01_test.go deleted file mode 100644 index 33e5fbf..0000000 --- a/2023/go/internal/day_02/part_01/part_01_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package part_01 - -import ( - "testing" -) - -type data struct { - gameId int - isPossible bool - shownCubes []cubes - line string -} - -var testInput = []data{ - { - gameId: 1, - isPossible: true, - shownCubes: []cubes{ - {red: 4, blue: 3}, - {red: 1, blue: 6, green: 2}, - {green: 2}, - }, - line: "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green", - }, - { - gameId: 2, - isPossible: true, - shownCubes: []cubes{ - {blue: 1, green: 2}, - {red: 1, blue: 4, green: 3}, - {blue: 1, green: 1}, - }, - line: "Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue", - }, - { - gameId: 3, - isPossible: false, - shownCubes: []cubes{ - {red: 20, blue: 6, green: 8}, - {red: 4, blue: 5, green: 13}, - {red: 1, green: 5}, - }, - line: "Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red", - }, - { - gameId: 4, - isPossible: false, - shownCubes: []cubes{ - {red: 3, blue: 6, green: 1}, - {red: 6, green: 3}, - {red: 14, blue: 15, green: 3}, - }, - line: "Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red", - }, - { - gameId: 5, - isPossible: true, - shownCubes: []cubes{ - {red: 6, blue: 1, green: 3}, - {red: 1, blue: 2, green: 2}, - }, - line: "Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green", - }, -} - -func TestConvertLineToCubes(t *testing.T) { - for _, data := range testInput { - actualCubes := convertLineToCubes(data.line) - - if len(data.shownCubes) != len(actualCubes) { - t.Errorf("Expected length of shownCubes to be %d, got %d", len(data.shownCubes), len(actualCubes)) - } - - for i, cubes := range data.shownCubes { - if cubes != actualCubes[i] { - t.Errorf("Expected shownCubes to be %v, got %v", cubes, actualCubes[i]) - } - } - } -} - -func TestIsPossibleGame(t *testing.T) { - maxCubes := cubes{red: 12, blue: 13, green: 14} - - for _, data := range testInput { - actualIsPossible := isPossibleGame(data.shownCubes, maxCubes) - - if data.isPossible != actualIsPossible { - t.Errorf("Expected isPossibleGame to be %t, got %t for game id %d", data.isPossible, actualIsPossible, data.gameId) - } - } -} - -func TestGetGameId(t *testing.T) { - for _, data := range testInput { - actualGameId := getGameId(data.line) - - if data.gameId != actualGameId { - t.Errorf("Expected gameId to be %d, got %d", data.gameId, actualGameId) - } - } -} diff --git a/2023/go/internal/day_02/part_02/part_02.go b/2023/go/internal/day_02/part_02/part_02.go deleted file mode 100644 index ffb9691..0000000 --- a/2023/go/internal/day_02/part_02/part_02.go +++ /dev/null @@ -1,114 +0,0 @@ -package part_02 - -import ( - "os" - "regexp" - "strconv" - "strings" -) - -type cubes struct { - red int - blue int - green int -} - -func Solve() { - contents, err := os.ReadFile("../_input/day_02.txt") - if err != nil { - panic(err) - } - - lines := strings.Split(string(contents), "\n") - answer := processLines(lines) - - println(answer) -} - -func processLines(lines []string) int { - var answer int - - for _, line := range lines { - if line == "" || line == "\n" { - continue - } - - shownCubes := convertLineToCubes(line) - - maxCubes := getMaximumCubes(shownCubes) - power := getPower(maxCubes) - answer += power - } - - return answer -} - -func convertLineToCubes(line string) []cubes { - var shownCubes []cubes - - redRegex := regexp.MustCompile(`(\d+) red`) - blueRegex := regexp.MustCompile(`(\d+) blue`) - greenRegex := regexp.MustCompile(`(\d+) green`) - - for _, round := range strings.Split(line, ";") { - var cubes cubes - - redMatches := redRegex.FindStringSubmatch(round) - if redMatches != nil { - red, err := strconv.Atoi(redMatches[1]) - if err != nil { - panic("Could not convert red to int: " + redMatches[1]) - } else { - cubes.red = red - } - } - - blueMatches := blueRegex.FindStringSubmatch(round) - if blueMatches != nil { - blue, err := strconv.Atoi(blueMatches[1]) - if err != nil { - panic("Could not convert green to int: " + blueMatches[1]) - } else { - cubes.blue = blue - } - } - - greenMatches := greenRegex.FindStringSubmatch(round) - if greenMatches != nil { - green, err := strconv.Atoi(greenMatches[1]) - if err != nil { - panic("Could not convert green to int: " + greenMatches[1]) - } else { - cubes.green = green - } - } - - shownCubes = append(shownCubes, cubes) - } - - return shownCubes -} - -func getMaximumCubes(shownCubes []cubes) cubes { - var maxCubes cubes - - for _, cubes := range shownCubes { - if cubes.red > maxCubes.red { - maxCubes.red = cubes.red - } - - if cubes.blue > maxCubes.blue { - maxCubes.blue = cubes.blue - } - - if cubes.green > maxCubes.green { - maxCubes.green = cubes.green - } - } - - return maxCubes -} - -func getPower(maxCubes cubes) int { - return maxCubes.red * maxCubes.blue * maxCubes.green -} diff --git a/2023/go/internal/day_02/part_02/part_02_test.go b/2023/go/internal/day_02/part_02/part_02_test.go deleted file mode 100644 index 6952899..0000000 --- a/2023/go/internal/day_02/part_02/part_02_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package part_02 - -import ( - "testing" -) - -type data struct { - shownCubes []cubes - maxCubes cubes - line string -} - -var testInput = []data{ - { - shownCubes: []cubes{ - {red: 4, blue: 3}, - {red: 1, blue: 6, green: 2}, - {green: 2}, - }, - maxCubes: cubes{red: 4, blue: 6, green: 2}, - line: "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green", - }, - { - shownCubes: []cubes{ - {blue: 1, green: 2}, - {red: 1, blue: 4, green: 3}, - {blue: 1, green: 1}, - }, - maxCubes: cubes{red: 1, blue: 4, green: 3}, - line: "Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue", - }, - { - shownCubes: []cubes{ - {red: 20, blue: 6, green: 8}, - {red: 4, blue: 5, green: 13}, - {red: 1, green: 5}, - }, - maxCubes: cubes{red: 20, blue: 6, green: 13}, - line: "Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red", - }, - { - shownCubes: []cubes{ - {red: 3, blue: 6, green: 1}, - {red: 6, green: 3}, - {red: 14, blue: 15, green: 3}, - }, - maxCubes: cubes{red: 14, blue: 15, green: 3}, - line: "Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red", - }, - { - shownCubes: []cubes{ - {red: 6, blue: 1, green: 3}, - {red: 1, blue: 2, green: 2}, - }, - maxCubes: cubes{red: 6, blue: 2, green: 3}, - line: "Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green", - }, -} - -func TestConvertLineToCubes(t *testing.T) { - for _, data := range testInput { - actualCubes := convertLineToCubes(data.line) - - if len(data.shownCubes) != len(actualCubes) { - t.Errorf("Expected length of shownCubes to be %d, got %d", len(data.shownCubes), len(actualCubes)) - } - - for i, cubes := range data.shownCubes { - if cubes != actualCubes[i] { - t.Errorf("Expected shownCubes to be %v, got %v", cubes, actualCubes[i]) - } - } - } -} - -func TestGetMaximumCubes(t *testing.T) { - for _, data := range testInput { - actualMaxCubes := getMaximumCubes(data.shownCubes) - - if actualMaxCubes != data.maxCubes { - t.Errorf("Expected maxCubes to be %v, got %v", data.maxCubes, actualMaxCubes) - } - } -} diff --git a/2023/go/internal/day_03/part_01/part_01.go b/2023/go/internal/day_03/part_01/part_01.go deleted file mode 100644 index 49837b3..0000000 --- a/2023/go/internal/day_03/part_01/part_01.go +++ /dev/null @@ -1,123 +0,0 @@ -package part_01 - -/** - * TRIED: - * 530923 - */ - -import ( - "os" - "strconv" - "strings" -) - -const USE_SAMPLE = true - -func Solve() { - var content []byte - if USE_SAMPLE { - content, _ = os.ReadFile("../_input/day_03_part_01_sample.txt") - } else { - content, _ = os.ReadFile("../_input/day_03.txt") - } - lines := strings.Split(string(content), "\n") - - validNumbers := walkLines(lines) - - answer := 0 - for _, number := range validNumbers { - answer += number - } - - println(answer) -} - -func walkLines(lines []string) []int { - var validNumbers []int - - for y := 0; y < len(lines); y++ { - line := lines[y] - - if line == "" || line == "\n" { - continue - } - - // Counts as a symbol... - filteredLine := strings.ReplaceAll(line, "\r", "") - - previousWasNumber := false - number := "" - for x := 0; x < len(filteredLine); x++ { - char := filteredLine[x] - - _, err := strconv.Atoi(string(char)) - isNumeric := err == nil - - if isNumeric { - previousWasNumber = true - number += string(char) - continue - } - - if !previousWasNumber { - continue - } - - isValid := checkValidity(lines, x, y, len(number)) - if isValid { - num, _ := strconv.Atoi(number) - validNumbers = append(validNumbers, num) - } - - println(number) - - previousWasNumber = false - number = "" - } - } - - return validNumbers -} - -// N = number -// k = numOfDigits + 1 -// (x-k,y-1) (x-1,y-1) ( x ,y-1) -// (x-k, y ) N ( x , y ) -// (x-k,y+1) (x-1,y+1) ( x ,y+1) -func checkValidity(lines []string, x int, y int, numOfDigits int) bool { - - for i := max(y-1, 0); i <= y+1; i++ { - - var debugLine string - - for j := max(x-(numOfDigits+1), 0); j <= x; j++ { - - if lineExists := len(lines) > i; !lineExists { - continue - } - - if charExists := len(lines[i]) > j; !charExists { - continue - } - - char := rune(lines[i][j]) - debugLine += string(char) - - if char == '.' { - continue - } - - _, err := strconv.Atoi(string(char)) - isNumeric := err == nil - if isNumeric { - continue - } - - debugLine = "" //nolint:ineffassign // Only used for debugging and needs to be reset - - return true - } - } - - return false -} diff --git a/2023/go/internal/day_03/part_01/part_01_test.go b/2023/go/internal/day_03/part_01/part_01_test.go deleted file mode 100644 index a077fbd..0000000 --- a/2023/go/internal/day_03/part_01/part_01_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package part_01 - -import ( - "os" - "strings" - "testing" -) - -func TestWalkLines(t *testing.T) { - lines := getSampleLines() - expected := 4361 - - validNumbers := walkLines(lines) - - actual := 0 - for _, number := range validNumbers { - actual += number - } - - if actual != expected { - t.Errorf("Expected answer to be %d, got %d", expected, actual) - } -} - -func TestCheckValidity(t *testing.T) { - lines := getSampleLines() - - tests := []struct { - x int - y int - length int - expected bool - }{ - {3, 0, 3, true}, // 467 - {8, 0, 3, false}, // 114 - {9, 0, 1, false}, // 3 - Added to check for '\r' counting as a symbol - {4, 2, 2, true}, // 35 - {9, 2, 3, true}, // 633 - {9, 3, 1, false}, // 3 - Added to check if number continues to next line - {3, 4, 3, true}, // 617 - {9, 5, 2, false}, // 58 - {5, 6, 3, true}, // 592 - {9, 7, 3, true}, // 775 - {4, 9, 3, true}, // 664 - {8, 9, 3, true}, // 598 - } - - for _, test := range tests { - actual := checkValidity(lines, test.x, test.y, test.length) - - if actual != test.expected { - t.Errorf("Expected %d, %d, %d to be %t, got %t", test.x, test.y, test.length, test.expected, actual) - } - } -} - -func getSampleLines() []string { - contents, err := os.ReadFile("../../../../_input/day_03_part_01_sample.txt") - if err != nil { - panic(err) - } - - lines := strings.Split(string(contents), "\n") - - var filteredLines []string - for _, line := range lines { - if line == "" || line == "\n" { - continue - } - - // Counts as a symbol... - filteredLine := strings.ReplaceAll(line, "\r", "") - - filteredLines = append(filteredLines, filteredLine) - } - - return filteredLines -} diff --git a/2023/go/internal/day_04/part_01/part_01.go b/2023/go/internal/day_04/part_01/part_01.go deleted file mode 100644 index 1a215b1..0000000 --- a/2023/go/internal/day_04/part_01/part_01.go +++ /dev/null @@ -1,130 +0,0 @@ -package part_01 - -import ( - "os" - "slices" - "strconv" - "strings" -) - -const USE_SAMPLE = false - -type UnparsedCard struct { - id string - winningNumbers []string - haveNumbers []string -} - -type Card struct { - id int - winningNumbers []int - haveNumbers []int -} - -func (c UnparsedCard) parse() Card { - var card Card - - id, err := strconv.Atoi(c.id) - if err != nil { - panic(err) - } - card.id = id - - for _, number := range c.winningNumbers { - num, err := strconv.Atoi(number) - if err != nil { - panic(err) - } - card.winningNumbers = append(card.winningNumbers, num) - } - - for _, number := range c.haveNumbers { - num, err := strconv.Atoi(number) - if err != nil { - panic(err) - } - card.haveNumbers = append(card.haveNumbers, num) - } - - return card -} - -func Solve() { - var contents []byte - if USE_SAMPLE { - contents, _ = os.ReadFile("../_input/day_04_part_01_sample.txt") - } else { - contents, _ = os.ReadFile("../_input/day_04.txt") - } - - lines := strings.Split(string(contents), "\n") - - answer := 0 - for _, line := range lines { - if line == "" || line == "\n" { - continue - } - - card := parseCardLine(line) - score := tallyScore(card) - - answer += score - } - - println(answer) -} - -// "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53" -func parseCardLine(line string) Card { - var unparsedCard UnparsedCard - - filteredLine := strings.ReplaceAll(line, "\r", "") - - // "Card 1" - card_parts := strings.Split(filteredLine, ":")[0] - id_parts := strings.Split(card_parts, " ") - id := id_parts[len(id_parts)-1] - unparsedCard.id = strings.ReplaceAll(id, " ", "") - - // "41 48 83 86 17 | 83 86 6 31 17 9 48 53" - numbers := strings.Split(filteredLine, ":")[1] - - // " 41 48 83 86 17" - winningNumberString := strings.Split(numbers, "|")[0] - // ["", "41", "48", "83", "86", "17"] - winningNumbers := strings.Split(winningNumberString, " ") - for _, num := range winningNumbers { - if num == "" || num == " " { - continue - } - unparsedCard.winningNumbers = append(unparsedCard.winningNumbers, num) - } - - // " 83 86 6 31 17 9 48 53" - haveNumberString := strings.Split(numbers, "|")[1] - // ["", "83", "86", "", "6", "31", "17", "", "9", "48", "53"] - haveNumbers := strings.Split(haveNumberString, " ") - for _, num := range haveNumbers { - if num == "" || num == " " { - continue - } - unparsedCard.haveNumbers = append(unparsedCard.haveNumbers, num) - } - - return unparsedCard.parse() -} - -func tallyScore(card Card) int { - score := 0 - for _, winningNumber := range card.winningNumbers { - if slices.Contains(card.haveNumbers, winningNumber) { - if score == 0 { - score = 1 - } else { - score *= 2 - } - } - } - - return score -} diff --git a/2023/go/internal/day_04/part_01/part_01_test.go b/2023/go/internal/day_04/part_01/part_01_test.go deleted file mode 100644 index f6f1a83..0000000 --- a/2023/go/internal/day_04/part_01/part_01_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package part_01 - -import ( - "testing" -) - -func TestParseCardLine(t *testing.T) { - input := "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53" - expected := Card{1, []int{41, 48, 83, 86, 17}, []int{83, 86, 6, 31, 17, 9, 48, 53}} - - actual := parseCardLine(input) - - if actual.id != expected.id { - t.Errorf("ERROR: ID %d does not match expected %d", actual.id, expected.id) - } - - if len(actual.winningNumbers) != len(expected.winningNumbers) { - t.Errorf("ERROR: len(WinningNumbers) %d does not match expected %d", len(actual.winningNumbers), len(expected.winningNumbers)) - } - - if len(actual.haveNumbers) != len(expected.haveNumbers) { - t.Errorf("ERROR: len(HaveNumbers) %d does not match expected %d", len(actual.haveNumbers), len(expected.haveNumbers)) - } - - for i, num := range actual.winningNumbers { - if num != expected.winningNumbers[i] { - t.Errorf("ERROR: WinningNumber %d does not match expected %d", num, expected.winningNumbers[i]) - } - } - - for i, num := range actual.haveNumbers { - if num != expected.haveNumbers[i] { - t.Errorf("ERROR: HaveNumber %d does not match expected %d", num, expected.haveNumbers[i]) - } - } - -} - -func TestTallyScore(t *testing.T) { - card := Card{1, []int{41, 48, 83, 86, 17}, []int{83, 86, 6, 31, 17, 9, 48, 53}} - expected := 8 - - actual := tallyScore(card) - - if actual != expected { - t.Errorf("ERROR: Score %d does not match expected %d", actual, expected) - } -} diff --git a/2023/go/internal/day_04/part_02/part_02.go b/2023/go/internal/day_04/part_02/part_02.go deleted file mode 100644 index a9e3152..0000000 --- a/2023/go/internal/day_04/part_02/part_02.go +++ /dev/null @@ -1,142 +0,0 @@ -package part_02 - -import ( - "os" - "slices" - "strconv" - "strings" -) - -const USE_SAMPLE = false - -type UnparsedCard struct { - id string - winningNumbers []string - haveNumbers []string -} - -type Card struct { - id int - winningNumbers []int - haveNumbers []int -} - -func (c UnparsedCard) parse() Card { - var card Card - - id, err := strconv.Atoi(c.id) - if err != nil { - panic(err) - } - card.id = id - - for _, number := range c.winningNumbers { - num, err := strconv.Atoi(number) - if err != nil { - panic(err) - } - card.winningNumbers = append(card.winningNumbers, num) - } - - for _, number := range c.haveNumbers { - num, err := strconv.Atoi(number) - if err != nil { - panic(err) - } - card.haveNumbers = append(card.haveNumbers, num) - } - - return card -} - -func Solve() { - var contents []byte - if USE_SAMPLE { - contents, _ = os.ReadFile("../_input/day_04_part_02_sample.txt") - } else { - contents, _ = os.ReadFile("../_input/day_04.txt") - } - - lines := strings.Split(string(contents), "\n") - - dict := map[int]Card{} - var cards []Card - for _, line := range lines { - if line == "" || line == "\n" { - continue - } - - card := parseCardLine(line) - - dict[card.id] = card - cards = append(cards, card) - } - - answer := processCards(dict, cards) - - println(answer) -} - -// "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53" -func parseCardLine(line string) Card { - var unparsedCard UnparsedCard - - filteredLine := strings.ReplaceAll(line, "\r", "") - - // "Card 1" - card_parts := strings.Split(filteredLine, ":")[0] - id_parts := strings.Split(card_parts, " ") - id := id_parts[len(id_parts)-1] - unparsedCard.id = strings.ReplaceAll(id, " ", "") - - // "41 48 83 86 17 | 83 86 6 31 17 9 48 53" - numbers := strings.Split(filteredLine, ":")[1] - - // " 41 48 83 86 17" - winningNumberString := strings.Split(numbers, "|")[0] - // ["", "41", "48", "83", "86", "17"] - winningNumbers := strings.Split(winningNumberString, " ") - for _, num := range winningNumbers { - if num == "" || num == " " { - continue - } - unparsedCard.winningNumbers = append(unparsedCard.winningNumbers, num) - } - - // " 83 86 6 31 17 9 48 53" - haveNumberString := strings.Split(numbers, "|")[1] - // ["", "83", "86", "", "6", "31", "17", "", "9", "48", "53"] - haveNumbers := strings.Split(haveNumberString, " ") - for _, num := range haveNumbers { - if num == "" || num == " " { - continue - } - unparsedCard.haveNumbers = append(unparsedCard.haveNumbers, num) - } - - return unparsedCard.parse() -} - -func processCards(dict map[int]Card, cards []Card) int { - for i := 0; i < len(cards); i++ { - card := cards[i] - matchCount := calculateNumberOfMatches(card) - for j := card.id + 1; j < (card.id + 1 + matchCount); j++ { - copyCard := dict[j] - cards = append(cards, copyCard) - } - } - - return len(cards) -} - -func calculateNumberOfMatches(card Card) int { - matchCount := 0 - for _, winningNumber := range card.winningNumbers { - if slices.Contains(card.haveNumbers, winningNumber) { - matchCount++ - } - } - - return matchCount -} diff --git a/2023/go/internal/day_04/part_02/part_02_test.go b/2023/go/internal/day_04/part_02/part_02_test.go deleted file mode 100644 index 3c66a2e..0000000 --- a/2023/go/internal/day_04/part_02/part_02_test.go +++ /dev/null @@ -1,143 +0,0 @@ -package part_02 - -import ( - "testing" -) - -func TestParseCardLine(t *testing.T) { - input := "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53" - expected := Card{1, []int{41, 48, 83, 86, 17}, []int{83, 86, 6, 31, 17, 9, 48, 53}} - - actual := parseCardLine(input) - - if actual.id != expected.id { - t.Errorf("ERROR: ID %d does not match expected %d", actual.id, expected.id) - } - - if len(actual.winningNumbers) != len(expected.winningNumbers) { - t.Errorf("ERROR: len(WinningNumbers) %d does not match expected %d", len(actual.winningNumbers), len(expected.winningNumbers)) - } - - if len(actual.haveNumbers) != len(expected.haveNumbers) { - t.Errorf("ERROR: len(HaveNumbers) %d does not match expected %d", len(actual.haveNumbers), len(expected.haveNumbers)) - } - - for i, num := range actual.winningNumbers { - if num != expected.winningNumbers[i] { - t.Errorf("ERROR: WinningNumber %d does not match expected %d", num, expected.winningNumbers[i]) - } - } - - for i, num := range actual.haveNumbers { - if num != expected.haveNumbers[i] { - t.Errorf("ERROR: HaveNumber %d does not match expected %d", num, expected.haveNumbers[i]) - } - } - -} - -func TestProcessCards(t *testing.T) { - cards := []Card{ - // Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 - { - 1, - []int{41, 48, 83, 86, 17}, - []int{83, 86, 6, 31, 17, 9, 48, 53}, - }, - // Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 - { - 2, - []int{13, 32, 20, 16, 61}, - []int{61, 30, 68, 82, 17, 32, 24, 19}, - }, - // Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 - { - 3, - []int{1, 21, 53, 59, 44}, - []int{69, 82, 63, 72, 16, 21, 14, 1}, - }, - // Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 - { - 4, - []int{41, 92, 73, 84, 69}, - []int{59, 84, 76, 51, 58, 5, 54, 83}, - }, - // Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 - { - 5, - []int{87, 83, 26, 28, 32}, - []int{88, 30, 70, 12, 93, 22, 82, 36}, - }, - // Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 - { - 6, - []int{31, 18, 13, 56, 72}, - []int{74, 77, 10, 23, 35, 67, 36, 11}, - }, - } - dict := map[int]Card{} - for _, card := range cards { - dict[card.id] = card - } - expected := 30 - - actual := processCards(dict, cards) - - if actual != expected { - t.Errorf("ERROR: Total number of cards %d does not match expected %d", actual, expected) - } -} - -func TestCalculateNumberOfMatches(t *testing.T) { - cards := []Card{ - // Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 - { - 1, - []int{41, 48, 83, 86, 17}, - []int{83, 86, 6, 31, 17, 9, 48, 53}, - }, - // Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 - { - 2, - []int{13, 32, 20, 16, 61}, - []int{61, 30, 68, 82, 17, 32, 24, 19}, - }, - // Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 - { - 3, - []int{1, 21, 53, 59, 44}, - []int{69, 82, 63, 72, 16, 21, 14, 1}, - }, - // Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 - { - 4, - []int{41, 92, 73, 84, 69}, - []int{59, 84, 76, 51, 58, 5, 54, 83}, - }, - // Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 - { - 5, - []int{87, 83, 26, 28, 32}, - []int{88, 30, 70, 12, 93, 22, 82, 36}, - }, - // Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 - { - 6, - []int{31, 18, 13, 56, 72}, - []int{74, 77, 10, 23, 35, 67, 36, 11}, - }, - } - expected := 9 - - total := 0 - for _, card := range cards { - matches := calculateNumberOfMatches(card) - total += matches - - cards = append(cards, card) - } - - if total != expected { - t.Errorf("ERROR: Score %d does not match expected %d", total, expected) - } -} diff --git a/2023/go/internal/day_06/part_01/part_01.go b/2023/go/internal/day_06/part_01/part_01.go deleted file mode 100644 index 5735924..0000000 --- a/2023/go/internal/day_06/part_01/part_01.go +++ /dev/null @@ -1,85 +0,0 @@ -package part_01 - -import ( - "os" - "strconv" - "strings" -) - -const USE_SAMPLE = false - -/** - * Tried: - * - 72128 (too low) - * - 3317888 (correct!) - */ -func Solve() { - var filePath string - if USE_SAMPLE { - filePath = "../_input/day_06_part_01_sample.txt" - } else { - filePath = "../_input/day_06.txt" - } - contents, _ := os.ReadFile(filePath) - - lines := strings.Split(string(contents), "\r\n") - - timeLine := lines[0] - times := parseTimeLine(timeLine) - - distLine := lines[1] - distances := parseDistanceLine(distLine) - - answer := calculateAnswer(times, distances) - - println(answer) -} - -func parseTimeLine(timeLine string) []int { - prefix := "Time:" - return parseLine(prefix, timeLine) -} - -func parseDistanceLine(distLine string) []int { - prefix := "Distance:" - return parseLine(prefix, distLine) -} - -func parseLine(prefix string, line string) []int { - withoutTitle := strings.TrimPrefix(line, prefix) - - numbers := []int{} - for _, number := range strings.Split(withoutTitle, " ") { - if number == "" { - continue - } - numberInt, _ := strconv.Atoi(number) - numbers = append(numbers, numberInt) - } - return numbers -} - -func calculateAnswer(times []int, distances []int) int { - answer := 1 - for i, maxTime := range times { - recordDistance := distances[i] - - var greaterThanRecord []int - for buttonPressedFor := 0; buttonPressedFor <= maxTime; buttonPressedFor++ { - distance := calculateDistance(buttonPressedFor, maxTime) - if distance > recordDistance { - greaterThanRecord = append(greaterThanRecord, distance) - } - } - - if len(greaterThanRecord) > 0 { - answer *= len(greaterThanRecord) - } - } - - return answer -} - -func calculateDistance(buttonPressedFor int, maxTime int) int { - return buttonPressedFor * (maxTime - buttonPressedFor) -} diff --git a/2023/go/internal/day_06/part_01/part_01_test.go b/2023/go/internal/day_06/part_01/part_01_test.go deleted file mode 100644 index 7af828c..0000000 --- a/2023/go/internal/day_06/part_01/part_01_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package part_01 - -import "testing" - -func TestParseTimeLine(t *testing.T) { - tests := map[string]struct { - input string - expected []int - }{ - "empty": {"", []int{}}, - "sample": {"Time: 7 15 30", []int{7, 15, 30}}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := parseTimeLine(test.input) - if len(actual) != len(test.expected) { - t.Errorf("Expected %d, got %d", len(test.expected), len(actual)) - } - for i := 0; i < len(actual); i++ { - if actual[i] != test.expected[i] { - t.Errorf("Expected %d, got %d", test.expected[i], actual[i]) - } - } - }) - } -} - -func TestParseDistLine(t *testing.T) { - tests := map[string]struct { - input string - expected []int - }{ - "empty": {"", []int{}}, - "sample": {"Distance: 9 40 200", []int{9, 40, 200}}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := parseDistanceLine(test.input) - if len(actual) != len(test.expected) { - t.Errorf("Expected %d, got %d", len(test.expected), len(actual)) - } - for i := 0; i < len(actual); i++ { - if actual[i] != test.expected[i] { - t.Errorf("Expected %d, got %d", test.expected[i], actual[i]) - } - } - }) - } -} - -func TestCalculateAnswer(t *testing.T) { - tests := map[string]struct { - times []int - distances []int - expected int - }{ - "sample": {[]int{7, 15, 30}, []int{9, 40, 200}, 288}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := calculateAnswer(test.times, test.distances) - if actual != test.expected { - t.Errorf("Expected %d, got %d", test.expected, actual) - } - }) - } -} - -func TestCalculateDistance(t *testing.T) { - tests := map[string]struct { - maxTime int - buttonPressedFor int - expected int - }{ - "7_7": {7, 7, 0}, - "7_6": {7, 6, 6}, - "7_5": {7, 5, 10}, - "7_4": {7, 4, 12}, - "7_3": {7, 3, 12}, - "7_2": {7, 2, 10}, - "7_1": {7, 1, 6}, - "7_0": {7, 0, 0}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := calculateDistance(test.buttonPressedFor, test.maxTime) - if actual != test.expected { - t.Errorf("Expected %d, got %d", test.expected, actual) - } - }) - } -} diff --git a/2023/go/internal/day_06/part_02/part_02.go b/2023/go/internal/day_06/part_02/part_02.go deleted file mode 100644 index 316b169..0000000 --- a/2023/go/internal/day_06/part_02/part_02.go +++ /dev/null @@ -1,81 +0,0 @@ -package part_02 - -import ( - "os" - "strconv" - "strings" -) - -const USE_SAMPLE = false - -/** - * Tried: - * - 24655068 (correct!) - */ -func Solve() { - var filePath string - if USE_SAMPLE { - filePath = "../_input/day_06_part_02_sample.txt" - } else { - filePath = "../_input/day_06.txt" - } - contents, _ := os.ReadFile(filePath) - - lines := strings.Split(string(contents), "\r\n") - - timeLine := lines[0] - time := parseTimeLine(timeLine) - - distLine := lines[1] - distance := parseDistanceLine(distLine) - - answer := calculateAnswer(time, distance) - - println(answer) -} - -func parseTimeLine(timeLine string) int { - prefix := "Time:" - return parseLine(prefix, timeLine) -} - -func parseDistanceLine(distLine string) int { - prefix := "Distance:" - return parseLine(prefix, distLine) -} - -func parseLine(prefix string, line string) int { - withoutTitle := strings.TrimPrefix(line, prefix) - - numbers := []string{} - for _, number := range strings.Split(withoutTitle, " ") { - if number == "" { - continue - } - numbers = append(numbers, number) - } - - number := strings.Join(numbers, "") - numberInt, err := strconv.Atoi(number) - if err != nil { - panic(err) - } - - return numberInt -} - -func calculateAnswer(maxTime int, recordDistance int) int { - var greaterThanRecord []int - for buttonPressedFor := 0; buttonPressedFor <= maxTime; buttonPressedFor++ { - distance := calculateDistance(buttonPressedFor, maxTime) - if distance > recordDistance { - greaterThanRecord = append(greaterThanRecord, distance) - } - } - - return len(greaterThanRecord) -} - -func calculateDistance(buttonPressedFor int, maxTime int) int { - return buttonPressedFor * (maxTime - buttonPressedFor) -} diff --git a/2023/go/internal/day_06/part_02/part_02_test.go b/2023/go/internal/day_06/part_02/part_02_test.go deleted file mode 100644 index 67924dc..0000000 --- a/2023/go/internal/day_06/part_02/part_02_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package part_02 - -import "testing" - -func TestParseTimeLine(t *testing.T) { - tests := map[string]struct { - input string - expected int - }{ - "sample": {"Time: 7 15 30", 71530}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := parseTimeLine(test.input) - if actual != test.expected { - t.Errorf("Expected %d, got %d", test.expected, actual) - } - }) - } -} - -func TestParseDistLine(t *testing.T) { - tests := map[string]struct { - input string - expected int - }{ - "sample": {"Distance: 9 40 200", 940200}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := parseDistanceLine(test.input) - if actual != test.expected { - t.Errorf("Expected %d, got %d", test.expected, actual) - } - }) - } -} - -func TestCalculateAnswer(t *testing.T) { - tests := map[string]struct { - maxTime int - recordDistance int - expected int - }{ - "sample": {71530, 940200, 71503}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := calculateAnswer(test.maxTime, test.recordDistance) - if actual != test.expected { - t.Errorf("Expected %d, got %d", test.expected, actual) - } - }) - } -} - -func TestCalculateDistance(t *testing.T) { - tests := map[string]struct { - maxTime int - buttonPressedFor int - expected int - }{ - "7_7": {7, 7, 0}, - "7_6": {7, 6, 6}, - "7_5": {7, 5, 10}, - "7_4": {7, 4, 12}, - "7_3": {7, 3, 12}, - "7_2": {7, 2, 10}, - "7_1": {7, 1, 6}, - "7_0": {7, 0, 0}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := calculateDistance(test.buttonPressedFor, test.maxTime) - if actual != test.expected { - t.Errorf("Expected %d, got %d", test.expected, actual) - } - }) - } -} diff --git a/2023/go/internal/day_07/part_01/part_01.go b/2023/go/internal/day_07/part_01/part_01.go deleted file mode 100644 index a485dfd..0000000 --- a/2023/go/internal/day_07/part_01/part_01.go +++ /dev/null @@ -1,273 +0,0 @@ -package part_01 - -import ( - "os" - "strconv" - "strings" -) - -const USE_SAMPLE = false - -type handType int - -const ( - HIGH_CARD handType = iota - PAIR - TWO_PAIR - THREE_OF_A_KIND - FULL_HOUSE - FOUR_OF_A_KIND - FIVE_OF_A_KIND -) - -func (h handType) String() string { - return [...]string{"HIGH_CARD", "PAIR", "TWO_PAIR", "THREE_OF_A_KIND", "FULL_HOUSE", "FOUR_OF_A_KIND", "FIVE_OF_A_KIND"}[h] -} - -type cardType int - -const ( - TWO cardType = iota - THREE - FOUR - FIVE - SIX - SEVEN - EIGHT - NINE - TEN - JACK - QUEEN - KING - ACE -) - -func (c cardType) String() string { - return [...]string{"TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "TEN", "JACK", "QUEEN", "KING", "ACE"}[c] -} - -type hand struct { - strength handType - cards []cardType - bet int -} - -/** - * Tried: - * - 252489894 (too high) - * - 229176554 (too low) - * - 200253108 (too low) - */ -func Solve() { - var filePath string - if USE_SAMPLE { - filePath = "../_input/day_07_part_01_sample.txt" - } else { - filePath = "../_input/day_07.txt" - } - contents, err := os.ReadFile(filePath) - if err != nil { - panic(err) - } - - lines := strings.Split(string(contents), "\r\n") - var hands []hand - for _, line := range lines { - if line == "" { - continue - } - - hands = append(hands, parseLine(line)) - } - - mappedByStrength := mapByStrength(hands) - - sortedByHighest := make(map[handType][]hand) - for _, strength := range []handType{HIGH_CARD, PAIR, TWO_PAIR, THREE_OF_A_KIND, FULL_HOUSE, FOUR_OF_A_KIND, FIVE_OF_A_KIND} { - if _, ok := mappedByStrength[strength]; !ok { - continue - } - - toBeSorted := mappedByStrength[strength] - sortedByHighest[strength] = sortByHighest(toBeSorted) - } - - flattened := flatten(sortedByHighest) - - answer := calculateWinnings(flattened) - - println("Answer: ", answer) -} - -func parseLine(line string) hand { - parts := strings.Split(line, " ") - - unparsedCards := strings.Split(parts[0], "") - cards := parseCards(unparsedCards) - strength := calculateStrength(cards) - - bet, err := strconv.Atoi(parts[1]) - if err != nil { - panic(err) - } - - return hand{ - strength, - cards, - bet, - } -} - -func parseCards(unparsedCards []string) []cardType { - var cards []cardType - for _, card := range unparsedCards { - switch card { - case "2": - cards = append(cards, TWO) - case "3": - cards = append(cards, THREE) - case "4": - cards = append(cards, FOUR) - case "5": - cards = append(cards, FIVE) - case "6": - cards = append(cards, SIX) - case "7": - cards = append(cards, SEVEN) - case "8": - cards = append(cards, EIGHT) - case "9": - cards = append(cards, NINE) - case "T": - cards = append(cards, TEN) - case "J": - cards = append(cards, JACK) - case "Q": - cards = append(cards, QUEEN) - case "K": - cards = append(cards, KING) - case "A": - cards = append(cards, ACE) - } - } - return cards -} - -func calculateStrength(cards []cardType) handType { - strength := map[cardType]int{} - for _, card := range cards { - if _, ok := strength[card]; !ok { - strength[card] = 0 - } - strength[card]++ - } - - var pairs int - var hasThreeOfAKind bool - for _, count := range strength { - if count == 5 { - return FIVE_OF_A_KIND - } - - if count == 4 { - return FOUR_OF_A_KIND - } - - if count == 3 { - hasThreeOfAKind = true - } - - if count == 2 { - pairs++ - } - } - - if hasThreeOfAKind && pairs == 1 { - return FULL_HOUSE - } - - if hasThreeOfAKind { - return THREE_OF_A_KIND - } - - if pairs == 2 { - return TWO_PAIR - } - - if pairs == 1 { - return PAIR - } - - return HIGH_CARD -} - -func mapByStrength(hands []hand) map[handType][]hand { - - handsByStrength := make(map[handType][]hand) - for _, hand := range hands { - handsByStrength[hand.strength] = append(handsByStrength[hand.strength], hand) - } - - return handsByStrength -} - -// low to high -func sortByHighest(hands []hand) []hand { - var partition = func(arr []hand, low, high int) ([]hand, int) { - pivot := arr[high] - index := low - - for i := low; i < high; i++ { - // Always 5 cards - for j := 0; j < 5; j++ { - if arr[i].cards[j] < pivot.cards[j] { - arr[i], arr[index] = arr[index], arr[i] - index++ - break - } - - if arr[i].cards[j] != pivot.cards[j] { - break - } - } - } - - arr[high], arr[index] = arr[index], arr[high] - - return arr, index - } - - var quickSort func(arr []hand, low, high int) []hand - quickSort = func(arr []hand, low, high int) []hand { - if low < high { - arr, pivot := partition(arr, low, high) - - quickSort(arr, low, pivot-1) - quickSort(arr, pivot+1, high) - } - - return arr - } - - hands = quickSort(hands, 0, len(hands)-1) - - return hands -} - -func flatten(hands map[handType][]hand) []hand { - var flat []hand - for _, handType := range []handType{HIGH_CARD, PAIR, TWO_PAIR, THREE_OF_A_KIND, FULL_HOUSE, FOUR_OF_A_KIND, FIVE_OF_A_KIND} { - flat = append(flat, hands[handType]...) - } - return flat -} - -func calculateWinnings(hands []hand) int { - var winnings int - for i, hand := range hands { - earned := hand.bet * (i + 1) - winnings += earned - println("hand ", i, ": { strength: ", hand.strength.String(), ", cards: [", hand.cards[0], hand.cards[1], hand.cards[2], hand.cards[3], hand.cards[4], "], earned: ", earned, " }") - } - return winnings -} diff --git a/2023/go/internal/day_07/part_01/part_01_test.go b/2023/go/internal/day_07/part_01/part_01_test.go deleted file mode 100644 index 6ceabe8..0000000 --- a/2023/go/internal/day_07/part_01/part_01_test.go +++ /dev/null @@ -1,432 +0,0 @@ -package part_01 - -import "testing" - -func TestParseLine(t *testing.T) { - tests := map[string]struct { - input string - expected hand - }{ - "sample_1": { - input: "32T3K 765", - expected: hand{ - strength: PAIR, - cards: []cardType{THREE, TWO, TEN, THREE, KING}, - bet: 765, - }, - }, - "sample_2": { - input: "T55J5 684", - expected: hand{ - strength: THREE_OF_A_KIND, - cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, - bet: 684, - }, - }, - "sample_3": { - input: "KK677 28", - expected: hand{ - strength: TWO_PAIR, - cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, - bet: 28, - }, - }, - "sample_4": { - input: "KTJJT 220", - expected: hand{ - strength: TWO_PAIR, - cards: []cardType{KING, TEN, JACK, JACK, TEN}, - bet: 220, - }, - }, - "sample_5": { - input: "QQQJA 483", - expected: hand{ - strength: THREE_OF_A_KIND, - cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, - bet: 483, - }, - }, - "full_house": { - input: "QQQAA 999", - expected: hand{ - strength: FULL_HOUSE, - cards: []cardType{QUEEN, QUEEN, QUEEN, ACE, ACE}, - bet: 999, - }, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := parseLine(test.input) - - if actual.strength != test.expected.strength { - t.Errorf("Strength: expected %v, got %v", test.expected.strength, actual.strength) - } - - if len(actual.cards) != len(test.expected.cards) { - t.Errorf("Cards: expected %v, got %v", test.expected.cards, actual.cards) - } - - for i, card := range actual.cards { - if card != test.expected.cards[i] { - t.Errorf("Card Order: expected %v, got %v", test.expected.cards, actual.cards) - } - } - - if actual.bet != test.expected.bet { - t.Errorf("Bet: expected %v, got %v", test.expected.bet, actual.bet) - } - }) - } -} - -func TestMapByStrength(t *testing.T) { - tests := map[string]struct { - input []hand - expected map[handType][]hand - }{ - "sample": { - input: []hand{ - { - strength: PAIR, - cards: []cardType{THREE, TWO, TEN, THREE, KING}, - bet: 765, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, - bet: 684, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, - bet: 28, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, TEN, JACK, JACK, TEN}, - bet: 220, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, - bet: 483, - }, - }, - expected: map[handType][]hand{ - PAIR: { - { - strength: PAIR, - cards: []cardType{THREE, TWO, TEN, THREE, KING}, - bet: 765, - }, - }, - TWO_PAIR: { - { - strength: TWO_PAIR, - cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, - bet: 28, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, TEN, JACK, JACK, TEN}, - bet: 220, - }, - }, - THREE_OF_A_KIND: { - { - strength: THREE_OF_A_KIND, - cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, - bet: 684, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, - bet: 483, - }, - }, - }, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := mapByStrength(test.input) - - for strength, hands := range actual { - expected := test.expected[strength] - - if len(hands) != len(expected) { - t.Errorf("Length: expected %v, got %v", len(expected), len(hands)) - } - - for i, hand := range hands { - if hand.strength != expected[i].strength { - t.Errorf("Strength: expected %v, got %v", expected[i].strength, hand.strength) - } - - if len(hand.cards) != len(expected[i].cards) { - t.Errorf("Cards: expected %v, got %v", expected[i].cards, hand.cards) - } - - for j, card := range hand.cards { - if card != expected[i].cards[j] { - t.Errorf("Card Order: expected %v, got %v", expected[i].cards, hand.cards) - } - } - - if hand.bet != expected[i].bet { - t.Errorf("Bet: expected %v, got %v", expected[i].bet, hand.bet) - } - } - } - }) - } -} - -func TestSortByHighest(t *testing.T) { - tests := map[string]struct { - input []hand - expected []hand - }{ - "sample": { - input: []hand{ - { - strength: PAIR, - cards: []cardType{THREE, TWO, TEN, THREE, KING}, - bet: 765, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, - bet: 684, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, - bet: 28, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, TEN, JACK, JACK, TEN}, - bet: 220, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, - bet: 483, - }, - }, - expected: []hand{ - { - strength: PAIR, - cards: []cardType{THREE, TWO, TEN, THREE, KING}, - bet: 765, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, - bet: 684, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, - bet: 483, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, TEN, JACK, JACK, TEN}, - bet: 220, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, - bet: 28, - }, - }, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := sortByHighest(test.input) - - if len(actual) != len(test.expected) { - t.Errorf("Length: expected %v, got %v", len(test.expected), len(actual)) - } - - for i, hand := range actual { - if hand.strength != test.expected[i].strength { - t.Errorf("Strength: expected %v, got %v", test.expected[i].strength, hand.strength) - } - - if len(hand.cards) != len(test.expected[i].cards) { - t.Errorf("Cards: expected %v, got %v", test.expected[i].cards, hand.cards) - } - - for j, card := range hand.cards { - if card != test.expected[i].cards[j] { - t.Errorf("Card Order: expected %v, got %v", test.expected[i].cards, hand.cards) - } - } - - if hand.bet != test.expected[i].bet { - t.Errorf("Bet: expected %v, got %v", test.expected[i].bet, hand.bet) - } - } - }) - } -} - -func TestFlatten(t *testing.T) { - tests := map[string]struct { - input map[handType][]hand - expected []hand - }{ - "sample": { - // Order jumbled to ensure sorting works - input: map[handType][]hand{ - TWO_PAIR: { - { - strength: TWO_PAIR, - cards: []cardType{KING, TEN, JACK, JACK, TEN}, - bet: 220, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, - bet: 28, - }, - }, - PAIR: { - { - strength: PAIR, - cards: []cardType{THREE, TWO, TEN, THREE, KING}, - bet: 765, - }, - }, - THREE_OF_A_KIND: { - { - strength: THREE_OF_A_KIND, - cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, - bet: 684, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, - bet: 483, - }, - }, - }, - expected: []hand{ - { - strength: PAIR, - cards: []cardType{THREE, TWO, TEN, THREE, KING}, - bet: 765, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, TEN, JACK, JACK, TEN}, - bet: 220, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, - bet: 28, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, - bet: 684, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, - bet: 483, - }, - }, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := flatten(test.input) - - if len(actual) != len(test.expected) { - t.Errorf("Length: expected %v, got %v", len(test.expected), len(actual)) - } - - for i, hand := range actual { - expected := test.expected[i] - - if hand.strength != expected.strength { - t.Errorf("Strength: expected %v, got %v", expected.strength, hand.strength) - } - - if len(hand.cards) != len(expected.cards) { - t.Errorf("Cards: expected %v, got %v", expected.cards, hand.cards) - } - - for i, card := range hand.cards { - if card != expected.cards[i] { - t.Errorf("Card Order: expected %v, got %v", expected.cards, hand.cards) - } - } - - if hand.bet != expected.bet { - t.Errorf("Bet: expected %v, got %v", expected.bet, hand.bet) - } - } - }) - } -} - -func TestCalculateWinnings(t *testing.T) { - tests := map[string]struct { - input []hand - expected int - }{ - "sample": { - input: []hand{ - { - strength: PAIR, - cards: []cardType{THREE, TWO, TEN, THREE, KING}, - bet: 765, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, TEN, JACK, JACK, TEN}, - bet: 220, - }, - { - strength: TWO_PAIR, - cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, - bet: 28, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, - bet: 684, - }, - { - strength: THREE_OF_A_KIND, - cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, - bet: 483, - }, - }, - expected: 6440, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actual := calculateWinnings(test.input) - - if actual != test.expected { - t.Errorf("Winnings: expected %v, got %v", test.expected, actual) - } - }) - } -} diff --git a/2023/go/main.go b/2023/go/main.go deleted file mode 100644 index 7d71b47..0000000 --- a/2023/go/main.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "flag" - "os" - - "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_01" - day_02_part_01 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_02/part_01" - day_02_part_02 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_02/part_02" - day_03_part_01 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_03/part_01" - day_04_part_01 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_04/part_01" - day_04_part_02 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_04/part_02" - day_06_part_01 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_06/part_01" - day_06_part_02 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_06/part_02" - day_07_part_01 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_07/part_01" - _ "github.com/outrigdev/outrig/autoinit" -) - -func main() { - day := flag.Int("day", 1, "Day to run") - part := flag.Int("part", 2, "Part to run, must be 1 or 2") - - flag.Parse() - - if *part < 1 || *part > 2 { - println("Invalid part number, must be 1 or 2") - os.Exit(1) - } - - switch *day { - case 1: - switch *part { - case 1: - println("Day 1 part 1 not implemented") - os.Exit(1) - case 2: - day_01.Solve() - } - - case 2: - switch *part { - case 1: - day_02_part_01.Solve() - case 2: - day_02_part_02.Solve() - } - - case 3: - switch *part { - case 1: - day_03_part_01.Solve() - case 2: - println("Day 3 part 2 not implemented") - os.Exit(1) - } - - case 4: - switch *part { - case 1: - day_04_part_01.Solve() - case 2: - day_04_part_02.Solve() - } - - case 6: - switch *part { - case 1: - day_06_part_01.Solve() - case 2: - day_06_part_02.Solve() - } - - case 7: - switch *part { - case 1: - day_07_part_01.Solve() - case 2: - println("Day 7 part 2 not implemented") - os.Exit(1) - } - - default: - println("Day not implemented") - os.Exit(1) - } -} diff --git a/2024/php/.envrc b/2024/php/.envrc deleted file mode 100644 index cffc922..0000000 --- a/2024/php/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake . --impure diff --git a/2024/php/README.md b/2024/php/README.md deleted file mode 100644 index 6aeb9d6..0000000 --- a/2024/php/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# PHP 2024 - -## Getting Started - -### How to Run - -Run the php script directly, e.g. - -```sh -php day_XX/part_YY.php -``` diff --git a/2024/php/day_04/Grid.php b/2024/php/day_04/Grid.php deleted file mode 100644 index 75d531b..0000000 --- a/2024/php/day_04/Grid.php +++ /dev/null @@ -1,69 +0,0 @@ - array_reduce( - array: $row, - callback: fn (string $acc, string $character): string => $acc . $character, - initial: "", - ); - - return array_reduce( - array: $this->data, - callback: fn (string $acc, array $row): string => $acc . $rowToLine($row) . PHP_EOL, - initial: "", - ); - } - - public function getCharacterAtPosition(Position $position): string - { - if ($this->isInBounds($position)) { - return $this->data[$position->y][$position->x]; - } - - return "."; - } - - /** - * @param Position[] $positions - */ - public function getStringFromPositions(array $positions): string - { - $characters = array_map( - callback: fn (Position $position): string => $this->getCharacterAtPosition($position), - array: $positions, - ); - - $string = array_reduce( - array: $characters, - callback: fn (string $acc, string $character): string => $acc . $character, - initial: "", - ); - - return $string; - } - - public function isInBounds(Position $position): bool - { - if (!array_key_exists($position->y, $this->data)) { - return false; - } - - if (!array_key_exists($position->x, $this->data[$position->y])) { - return false; - } - - return true; - } -} diff --git a/2024/php/day_04/Input.php b/2024/php/day_04/Input.php deleted file mode 100644 index 60a16a0..0000000 --- a/2024/php/day_04/Input.php +++ /dev/null @@ -1,25 +0,0 @@ -fileName, use_include_path: true); - $this->contents = $contents === false ? "" : $contents; - } - - public function toGrid(): Grid - { - $lines = explode("\n", $this->contents); - $gridArray = array_map( - callback: fn (string $line): array => str_split(trim($line)), - array: $lines - ); - - return new Grid($gridArray); - } -} diff --git a/2024/php/day_04/Position.php b/2024/php/day_04/Position.php deleted file mode 100644 index 0dcf96c..0000000 --- a/2024/php/day_04/Position.php +++ /dev/null @@ -1,79 +0,0 @@ -x}, {$this->y})"; - } - - /** - * Not validated - */ - public function getNorth(): self - { - return new self($this->x, $this->y - 1); - } - - /** - * Not validated - */ - public function getNorthEast(): self - { - return new self($this->x + 1, $this->y - 1); - } - - /** - * Not validated - */ - public function getEast(): self - { - return new self($this->x + 1, $this->y); - } - - /** - * Not validated - */ - public function getSouthEast(): self - { - return new self($this->x + 1, $this->y + 1); - } - - /** - * Not validated - */ - public function getSouth(): self - { - return new self($this->x, $this->y + 1); - } - - /** - * Not validated - */ - public function getSouthWest(): self - { - return new self($this->x - 1, $this->y + 1); - } - - /** - * Not validated - */ - public function getWest(): self - { - return new self($this->x - 1, $this->y); - } - - /** - * Not validated - */ - public function getNorthWest(): self - { - return new self($this->x - 1, $this->y - 1); - } -} diff --git a/2024/php/day_04/part_01.php b/2024/php/day_04/part_01.php deleted file mode 100644 index 0faa5b0..0000000 --- a/2024/php/day_04/part_01.php +++ /dev/null @@ -1,94 +0,0 @@ - require "{$className}.php"); - -// Assumes executed from workspace root -$input = new Input('../_input/day_04.txt'); -if ($input->contents === "") { - throw new RuntimeException("File not found"); -} - -$grid = $input->toGrid(); - -$matchCount = 0; -foreach ($grid->data as $y => $row) { - foreach ($row as $x => $char) { - if ($char !== 'X') { - continue; - } - - $start = new Position($x, $y); - - $possibleMatches = []; - - $possibleMatches["north"] = $grid->getStringFromPositions([ - $start, - $start->getNorth(), - $start->getNorth()->getNorth(), - $start->getNorth()->getNorth()->getNorth(), - ]); - - $possibleMatches["east"] = $grid->getStringFromPositions([ - $start, - $start->getEast(), - $start->getEast()->getEast(), - $start->getEast()->getEast()->getEast(), - ]); - - $possibleMatches["south"] = $grid->getStringFromPositions([ - $start, - $start->getSouth(), - $start->getSouth()->getSouth(), - $start->getSouth()->getSouth()->getSouth(), - ]); - - $possibleMatches["west"] = $grid->getStringFromPositions([ - $start, - $start->getWest(), - $start->getWest()->getWest(), - $start->getWest()->getWest()->getWest(), - ]); - - $possibleMatches["north-east"] = $grid->getStringFromPositions([ - $start, - $start->getNorthEast(), - $start->getNorthEast()->getNorthEast(), - $start->getNorthEast()->getNorthEast()->getNorthEast(), - ]); - - $possibleMatches["north-west"] = $grid->getStringFromPositions([ - $start, - $start->getNorthWest(), - $start->getNorthWest()->getNorthWest(), - $start->getNorthWest()->getNorthWest()->getNorthWest(), - ]); - - $possibleMatches["south-east"] = $grid->getStringFromPositions([ - $start, - $start->getSouthEast(), - $start->getSouthEast()->getSouthEast(), - $start->getSouthEast()->getSouthEast()->getSouthEast(), - ]); - - $possibleMatches["south-west"] = $grid->getStringFromPositions([ - $start, - $start->getSouthWest(), - $start->getSouthWest()->getSouthWest(), - $start->getSouthWest()->getSouthWest()->getSouthWest(), - ]); - - $matchCount += array_reduce( - array: $possibleMatches, - callback: fn (int $acc, string $possibleMatch): int => match ($possibleMatch) { - "XMAS" => $acc + 1, - default => $acc, - }, - initial: 0, - ); - } -} - -// ANSWER: 2517 -echo $matchCount; diff --git a/2024/php/flake.lock b/2024/php/flake.lock deleted file mode 100644 index b7695d9..0000000 --- a/2024/php/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1741010256, - "narHash": "sha256-WZNlK/KX7Sni0RyqLSqLPbK8k08Kq7H7RijPJbq9KHM=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "ba487dbc9d04e0634c64e3b1f0d25839a0a68246", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/2024/php/flake.nix b/2024/php/flake.nix deleted file mode 100644 index 2be5644..0000000 --- a/2024/php/flake.nix +++ /dev/null @@ -1,43 +0,0 @@ -{ - description = "Advent of Code 2024: PHP"; - - inputs = { - nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; - }; - - outputs = - { nixpkgs, ... }: - let - pkgs = import nixpkgs { - config.allowUnfree = true; - }; - in - { - devShells.x86_64-linux.default = - let - phpVersion = "php84"; - - phpPackage = pkgs.lib.attrByPath [ phpVersion ] null pkgs; - phpPackages = pkgs.lib.attrByPath [ "${phpVersion}Packages" ] null pkgs; - phpExtensions = pkgs.lib.attrByPath [ "${phpVersion}Extensions" ] null pkgs; - in - pkgs.mkShell { - # build tools - nativeBuildInputs = with pkgs; [ - phpPackage - nodejs_18 - ]; - # dependencies - buildInputs = with pkgs; [ - intelephense - phpunit - phpPackages.php-cs-fixer - phpPackages.phpstan - phpExtensions.xdebug - - gh # GitHub CLI - ]; - }; - - }; -} diff --git a/2025/ocaml/.editorconfig b/2025/ocaml/.editorconfig deleted file mode 100644 index 64b41ca..0000000 --- a/2025/ocaml/.editorconfig +++ /dev/null @@ -1,27 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_style = space - -[*.{ml,mli}] -indent_style = space -indent_size = 2 -tab_width = 2 - -[*.{opam,dune,install,sexp,conf,in}] -indent_style = space -indent_size = 4 - -[*.{md}] -indent_style = tab -trim_trailing_whitespace = true - -[*.{nix}] -indent_style = space -indent_size = 2 -insert_final_newline = false -trim_trailing_whitespace = true diff --git a/2025/ocaml/.env.template b/2025/ocaml/.env.template deleted file mode 100644 index 3adee3e..0000000 --- a/2025/ocaml/.env.template +++ /dev/null @@ -1 +0,0 @@ -LOG_LEVEL=debug diff --git a/2025/ocaml/.envrc b/2025/ocaml/.envrc deleted file mode 100644 index cf8df4b..0000000 --- a/2025/ocaml/.envrc +++ /dev/null @@ -1,3 +0,0 @@ -dotenv - -use flake diff --git a/2025/ocaml/.gitattributes b/2025/ocaml/.gitattributes deleted file mode 100644 index 1bc2e72..0000000 --- a/2025/ocaml/.gitattributes +++ /dev/null @@ -1,7 +0,0 @@ -# http://git-scm.com/docs/gitattributes#_end_of_line_conversion -* text=auto -.* text eol=lf - -flake.lock linguist-generated -ocaml_template.install linguist-generated -ocaml_template.opam linguist-generated diff --git a/2025/ocaml/.githooks/commit-msg b/2025/ocaml/.githooks/commit-msg deleted file mode 100755 index 1c1c287..0000000 --- a/2025/ocaml/.githooks/commit-msg +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -# Commit message hook - enforce conventional commit format - -commit_regex='^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}|^fixup!' - -if ! grep -qE "$commit_regex" "$1"; then - echo "โŒ Invalid commit message format!" - echo "" - echo "Format: type(scope): description" - echo "Types: feat, fix, docs, style, refactor, test, chore" - echo "Example: feat(auth): add user login functionality" - echo "" - echo "Your message: $(cat $1)" - exit 1 -fi diff --git a/2025/ocaml/.githooks/pre-push b/2025/ocaml/.githooks/pre-push deleted file mode 100755 index 51a8761..0000000 --- a/2025/ocaml/.githooks/pre-push +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -# Pre-push hook - run tests before pushing - -set -e - -echo "๐Ÿงช Running tests before push..." - -# Run the full test suite -task test - -if [ $? -eq 0 ]; then - echo "โœ… All tests passed! Push allowed." - exit 0 -else - echo "โŒ Tests failed! Push blocked." - echo "Fix the failing tests before pushing." - exit 1 -fi diff --git a/2025/ocaml/.gitignore b/2025/ocaml/.gitignore deleted file mode 100644 index 5456007..0000000 --- a/2025/ocaml/.gitignore +++ /dev/null @@ -1,45 +0,0 @@ -*.annot -*.cmo -*.cma -*.cmi -*.a -*.o -*.cmx -*.cmxs -*.cmxa - -# Files containing detailed information about the compilation (generated -# by `ocamlc`/`ocamlopt` when invoked using the option `-bin-annot`). -# These files are typically useful for code inspection tools -# (e.g. Merlin). -*.cmt -*.cmti - -# ocamlbuild and Dune default working directory -_build/ - -# ocamlbuild targets -*.byte -*.native - -# oasis generated files -setup.data -setup.log - -# Merlin configuring file for Vim and Emacs -.merlin - -# Dune generated files -*.install - -# Local OPAM switch -_opam/ - -# DirEnv cache -.direnv/ - -# Environment variables -*.env - -# Taskfile cache -.task/ diff --git a/2025/ocaml/.ocamlformat b/2025/ocaml/.ocamlformat deleted file mode 100644 index 6fdcd22..0000000 --- a/2025/ocaml/.ocamlformat +++ /dev/null @@ -1,3 +0,0 @@ -profile = ocamlformat -version = 0.28.1 - diff --git a/2025/ocaml/CODEOWNERS b/2025/ocaml/CODEOWNERS deleted file mode 100644 index 66c50a2..0000000 --- a/2025/ocaml/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @djoleary diff --git a/2025/ocaml/CONTRIBUTING.md b/2025/ocaml/CONTRIBUTING.md deleted file mode 100644 index 832b94b..0000000 --- a/2025/ocaml/CONTRIBUTING.md +++ /dev/null @@ -1,5 +0,0 @@ -# Contributing - -## Commit Messages - -We use conventional commits for all commit messages. See diff --git a/2025/ocaml/LICENSE b/2025/ocaml/LICENSE deleted file mode 100644 index 1e9858b..0000000 --- a/2025/ocaml/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to - diff --git a/2025/ocaml/README.md b/2025/ocaml/README.md deleted file mode 100644 index 772c76b..0000000 --- a/2025/ocaml/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# OCaml Template - -This is a template for OCaml projects. - -## Quick Start - -1. Fork this repository -2. Rename the repository to your project name -3. Replace all instances of `advent_of_code` with your project name, both in files and filenames themselves. - -## Project Structure - -```txt -. -โ”œโ”€โ”€ _build # build outputs -โ”œโ”€โ”€ bin # program entry points -โ”œโ”€โ”€ docs # documentation -โ”œโ”€โ”€ lib # public modules -โ”œโ”€โ”€ test # tests -โ”œโ”€โ”€ .editorconfig -โ”œโ”€โ”€ .envrc -โ”œโ”€โ”€ .gitignore -โ”œโ”€โ”€ .ocamlformat # ocamlformat configuration -โ”œโ”€โ”€ CODEOWNERS -โ”œโ”€โ”€ CONTRIBUTING.md -โ”œโ”€โ”€ LICENSE -โ”œโ”€โ”€ README.md -โ”œโ”€โ”€ Taskfile.yml # task runner configuration similar to make -โ”œโ”€โ”€ dune-project # dune project configuration -โ”œโ”€โ”€ flake.lock -โ”œโ”€โ”€ flake.nix # nix flake configuration -โ”œโ”€โ”€ advent_of_code.install # autogenerated install script -โ””โ”€โ”€ advent_of_code.opam # autogenerated project description -``` - -## Tools - -- [task](https://taskfile.dev/) -- [dune](https://dune.build/) -- [opam](https://opam.ocaml.org/) -- [ocamlformat](https://ocaml.org/p/ocamlformat/latest/doc/index.html) -- [odoc](https://ocaml.github.io/odoc/odoc/index.html) -- [alchotest](https://github.com/mirage/alcotest) -- [bisect_ppx](https://github.com/aantron/bisect_ppx) -- [ocaml-lsp](https://github.com/ocaml/ocaml-lsp) -- [earlybird](https://github.com/ocaml-lsp/earlybird) -- [findlib](https://github.com/ocaml/ocamlfind) -- [re2](https://github.com/janestreet/re2) -- [fmt](https://erratique.ch/software/fmt) -- [cmdliner](https://erratique.ch/software/cmdliner) - -## License - -This project is licensed under the [Unlicense](https://unlicense.org/). diff --git a/2025/ocaml/Taskfile.yaml b/2025/ocaml/Taskfile.yaml deleted file mode 100644 index e79cf9e..0000000 --- a/2025/ocaml/Taskfile.yaml +++ /dev/null @@ -1,107 +0,0 @@ -# https://taskfile.dev -version: "3" - -tasks: - - default: - desc: Prints this message - cmds: - - cmd: task --list-all - - docs: - desc: Builds documentation - preconditions: - - command -v ocamldoc - cmds: - - cmd: ocamldoc -html -d docs bin/main.ml - silent: true - - test: - desc: Runs the tests - preconditions: - - command -v dune - deps: - - task: build - cmds: - - cmd: dune runtest {{.CLI_ARGS}} - - test:watch: - desc: Continuously runs the tests - ignore_error: true - sources: - - lib/**/*.ml - - test/**/*.ml - preconditions: - - command -v dune - deps: - - task: build - cmds: - - cmd: dune runtest --watch {{.CLI_ARGS}} - - test:coverage: - desc: Runs the tests and reports coverage - preconditions: - - command -v dune - - command -v bisect-ppx-report - deps: - - task: build - cmds: - - cmd: rm --recursive --force _coverage - - cmd: rm --force _build/default/test/bisect*.coverage - - cmd: dune runtest --instrument-with bisect_ppx --force {{.CLI_ARGS}} - - cmd: bisect-ppx-report html - - test:suite: - desc: Run a singluar test suite, e.g. `task test:suite SUITE=test/domain/d01p01` - preconditions: - - command -v dune - deps: - - task: build - requires: - vars: - - SUITE - cmds: - - cmd: dune exec {{.SUITE}}.exe -- {{.CLI_ARGS}} - - build: - desc: Builds the project - preconditions: - - command -v dune - cmds: - - cmd: dune build - - build:watch: - desc: Builds the project when files change - preconditions: - - command -v dune - cmds: - - cmd: dune build --watch {{.CLI_ARGS}} - - build:release: - desc: Builds a release version the project - preconditions: - - command -v dune - cmds: - - cmd: dune build --release - - run: - desc: Runs the project - preconditions: - - command -v dune - deps: - - task: build - cmds: - - cmd: dune exec -- ./bin/main.exe {{.CLI_ARGS}} - - repl: - desc: Opens a REPL for the project - interactive: true - preconditions: - - command -v dune - - command -v utop - cmds: - - cmd: dune utop - - git:hooks: - desc: sets up the githooks for the project - cmd: git config core.hooksPath .githooks diff --git a/2025/ocaml/advent_of_code.opam b/2025/ocaml/advent_of_code.opam deleted file mode 100644 index 103f2ce..0000000 --- a/2025/ocaml/advent_of_code.opam +++ /dev/null @@ -1,42 +0,0 @@ -# This file is generated by dune, edit dune-project instead -opam-version: "2.0" -synopsis: "advent of code solutions for 2025" -description: - "project aiming to solve the advent of code puzzles released for 2025" -maintainer: ["DJ O'Leary "] -authors: ["DJ O'Leary "] -license: "Unlicense" -tags: ["Advent of Code"] -homepage: "https://github.com/djoleary/advent_of_code" -doc: "https://github.com/djoleary/advent_of_code/2025/ocaml/docs/index.html" -bug-reports: "https://github.com/djoleary/advent_of_code/issues" -depends: [ - "dune" {>= "3.20"} - "ocaml" {>= "5.2"} - "ppx_deriving" {build} - "alcotest" {with-test} - "qcheck" {with-test} - "bisect_ppx" {with-test} - "ppx_inline_test" {with-test} - "ppx_expect" {with-test} - "odoc" {with-doc} - "cmdliner" - "fmt" - "re2" -] -build: [ - ["dune" "subst"] {dev} - [ - "dune" - "build" - "-p" - name - "-j" - jobs - "@install" - "@runtest" {with-test} - "@doc" {with-doc} - ] -] -dev-repo: "git+https://github.com/djoleary/advent_of_code.git" -x-maintenance-intent: ["(latest)"] diff --git a/2025/ocaml/bin/dune b/2025/ocaml/bin/dune deleted file mode 100644 index 0f6768d..0000000 --- a/2025/ocaml/bin/dune +++ /dev/null @@ -1,11 +0,0 @@ -(executable - (public_name advent_of_code) - (name main) - (libraries - domain - application - infrastructure - presentation - logs - logs.fmt) - (modes byte exe)) diff --git a/2025/ocaml/bin/main.ml b/2025/ocaml/bin/main.ml deleted file mode 100644 index cbfe6d2..0000000 --- a/2025/ocaml/bin/main.ml +++ /dev/null @@ -1,16 +0,0 @@ -let env_log_level = - match Sys.getenv_opt "LOG_LEVEL" with - | None -> - Some Logs.Warning - | Some lvl -> ( - match Logs.level_of_string lvl with Error _ -> None | Ok v -> v ) - -let main () = - let log_level = env_log_level in - Logs.set_level log_level ; - Logs.set_reporter (Logs_fmt.reporter ()) ; - Logs.info (fun m -> - m "Started application with LOG_LEVEL=%s" (Logs.level_to_string log_level) ) ; - Presentation.Cli.run () - -let () = if !Sys.interactive then () else exit (main ()) diff --git a/2025/ocaml/docs/Main.html b/2025/ocaml/docs/Main.html deleted file mode 100644 index 11d7257..0000000 --- a/2025/ocaml/docs/Main.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - -Main - - - -

Module Main

- -
module Main: sig .. end

- diff --git a/2025/ocaml/docs/index.html b/2025/ocaml/docs/index.html deleted file mode 100644 index f536f1a..0000000 --- a/2025/ocaml/docs/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - -
Main
- - diff --git a/2025/ocaml/docs/index_attributes.html b/2025/ocaml/docs/index_attributes.html deleted file mode 100644 index 3450f40..0000000 --- a/2025/ocaml/docs/index_attributes.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -Index of class attributes - - - -

Index of class attributes

- -
- - diff --git a/2025/ocaml/docs/index_class_types.html b/2025/ocaml/docs/index_class_types.html deleted file mode 100644 index be3ecc7..0000000 --- a/2025/ocaml/docs/index_class_types.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -Index of class types - - - -

Index of class types

- -
- - diff --git a/2025/ocaml/docs/index_classes.html b/2025/ocaml/docs/index_classes.html deleted file mode 100644 index 08c083b..0000000 --- a/2025/ocaml/docs/index_classes.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -Index of classes - - - -

Index of classes

- -
- - diff --git a/2025/ocaml/docs/index_exceptions.html b/2025/ocaml/docs/index_exceptions.html deleted file mode 100644 index 0767720..0000000 --- a/2025/ocaml/docs/index_exceptions.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -Index of exceptions - - - -

Index of exceptions

- -
- - diff --git a/2025/ocaml/docs/index_extensions.html b/2025/ocaml/docs/index_extensions.html deleted file mode 100644 index 38625df..0000000 --- a/2025/ocaml/docs/index_extensions.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -Index of extensions - - - -

Index of extensions

- -
- - diff --git a/2025/ocaml/docs/index_methods.html b/2025/ocaml/docs/index_methods.html deleted file mode 100644 index 7debe46..0000000 --- a/2025/ocaml/docs/index_methods.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -Index of class methods - - - -

Index of class methods

- -
- - diff --git a/2025/ocaml/docs/index_module_types.html b/2025/ocaml/docs/index_module_types.html deleted file mode 100644 index 0b75a38..0000000 --- a/2025/ocaml/docs/index_module_types.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -Index of module types - - - -

Index of module types

- -
- - diff --git a/2025/ocaml/docs/index_modules.html b/2025/ocaml/docs/index_modules.html deleted file mode 100644 index b6588a3..0000000 --- a/2025/ocaml/docs/index_modules.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -Index of modules - - - -

Index of modules

- - - - -
M
Main
- - diff --git a/2025/ocaml/docs/index_types.html b/2025/ocaml/docs/index_types.html deleted file mode 100644 index 61efcda..0000000 --- a/2025/ocaml/docs/index_types.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -Index of types - - - -

Index of types

- -
- - diff --git a/2025/ocaml/docs/index_values.html b/2025/ocaml/docs/index_values.html deleted file mode 100644 index fc7294b..0000000 --- a/2025/ocaml/docs/index_values.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -Index of values - - - -

Index of values

- -
- - diff --git a/2025/ocaml/docs/style.css b/2025/ocaml/docs/style.css deleted file mode 100644 index 259bd49..0000000 --- a/2025/ocaml/docs/style.css +++ /dev/null @@ -1,43 +0,0 @@ -.keyword { font-weight : bold ; color : Red } -.keywordsign { color : #C04600 } -.comment { color : Green } -.constructor { color : Blue } -.type { color : #5C6585 } -.string { color : Maroon } -.warning { color : Red ; font-weight : bold } -.info { margin-left : 3em; margin-right: 3em } -.param_info { margin-top: 4px; margin-left : 3em; margin-right : 3em } -.code { color : #465F91 ; } -.typetable { border-style : hidden } -.paramstable { border-style : hidden ; padding: 5pt 5pt} -tr { background-color : White } -td.typefieldcomment { background-color : #FFFFFF ; font-size: smaller ;} -div.sig_block {margin-left: 2em} -*:target { background: yellow; } -body {font: 13px sans-serif; color: black; text-align: left; padding: 5px; margin: 0} -h1 { font-size : 20pt ; text-align: center; } -h2 { font-size : 20pt ; text-align: center; } -h3 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90BDFF ;padding: 2px; } -h4 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90DDFF ;padding: 2px; } -h5 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90EDFF ;padding: 2px; } -h6 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90FDFF ;padding: 2px; } -div.h7 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90BDFF ; padding: 2px; } -div.h8 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #E0FFFF ; padding: 2px; } -div.h9 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #F0FFFF ; padding: 2px; } -div.h10 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #FFFFFF ; padding: 2px; } -a {color: #416DFF; text-decoration: none} -a:hover {background-color: #ddd; text-decoration: underline} -pre { margin-bottom: 4px; font-family: monospace; } -pre.verbatim, pre.codepre { } -.indextable {border: 1px #ddd solid; border-collapse: collapse} -.indextable td, .indextable th {border: 1px #ddd solid; min-width: 80px} -.indextable td.module {background-color: #eee ; padding-left: 2px; padding-right: 2px} -.indextable td.module a {color: #4E6272; text-decoration: none; display: block; width: 100%} -.indextable td.module a:hover {text-decoration: underline; background-color: transparent} -.deprecated {color: #888; font-style: italic} -.indextable tr td div.info { margin-left: 2px; margin-right: 2px } -ul.indexlist { margin-left: 0; padding-left: 0;} -ul.indexlist li { list-style-type: none ; margin-left: 0; padding-left: 0; } -ul.info-attributes {list-style: none; margin: 0; padding: 0; } -div.info > p:first-child { margin-top:0; } -div.info-desc > p:first-child { margin-top:0; margin-bottom:0; } \ No newline at end of file diff --git a/2025/ocaml/docs/type_Main.html b/2025/ocaml/docs/type_Main.html deleted file mode 100644 index 43bb69c..0000000 --- a/2025/ocaml/docs/type_Main.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - -Main - - -sig end diff --git a/2025/ocaml/dune-project b/2025/ocaml/dune-project deleted file mode 100644 index 731f4b5..0000000 --- a/2025/ocaml/dune-project +++ /dev/null @@ -1,40 +0,0 @@ -(lang dune 3.20) - -(name advent_of_code) - -(generate_opam_files true) - -(source - (github djoleary/advent_of_code)) - -(authors "DJ O'Leary ") - -(maintainers "DJ O'Leary ") - -(license Unlicense) - -(documentation https://github.com/djoleary/advent_of_code/2025/ocaml/docs/index.html) - -(package - (name advent_of_code) - (synopsis "advent of code solutions for 2025") - (description "project aiming to solve the advent of code puzzles released for 2025") - (depends - (ocaml (>= 5.2)) - (ppx_deriving :build) - (alcotest :with-test) - (qcheck :with-test) - (bisect_ppx :with-test) - (ppx_inline_test :with-test) - (ppx_expect :with-test) - (odoc :with-doc) - cmdliner - fmt - re2 - ) - (tags - ("Advent of Code"))) - -(map_workspace_root false) - -; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html diff --git a/2025/ocaml/flake.lock b/2025/ocaml/flake.lock deleted file mode 100644 index 3baeb69..0000000 --- a/2025/ocaml/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1763421233, - "narHash": "sha256-Stk9ZYRkGrnnpyJ4eqt9eQtdFWRRIvMxpNRf4sIegnw=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "89c2b2330e733d6cdb5eae7b899326930c2c0648", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/2025/ocaml/flake.nix b/2025/ocaml/flake.nix deleted file mode 100644 index 54d7b3f..0000000 --- a/2025/ocaml/flake.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ - inputs = { - nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; - }; - - outputs = - { nixpkgs, ... }: - let - pkgs = nixpkgs.legacyPackages.x86_64-linux; - in - { - devShells.x86_64-linux.default = - let - ocamlPackages = with pkgs.ocaml-ng.ocamlPackages_5_2; [ - ocaml - - dune_3 # build system - - pkgs.opam # package manager - findlib # library manager - - ocaml-lsp # language server - earlybird # debug adapter - ocamlformat # formatter - - utop # REPL - - odoc # documentation generator - - alcotest # unit test framework - bisect_ppx # test coverage - qcheck-core # property-based testing - qcheck-alcotest # property-based testing - janeStreet.ppx_inline_test # inline test library - janeStreet.ppx_expect # inline test library - - janeStreet.re2 # regular expression library - fmt # pretty printing library - cmdliner # command line library - ppx_deriving # derivers - logs # logging library - ]; - sysTools = with pkgs; [ - go-task - gh - ]; - in - pkgs.mkShell { - buildInputs = ocamlPackages ++ sysTools; - }; - }; -} \ No newline at end of file diff --git a/2025/ocaml/lib/application/d01p01.ml b/2025/ocaml/lib/application/d01p01.ml deleted file mode 100644 index 206e757..0000000 --- a/2025/ocaml/lib/application/d01p01.ml +++ /dev/null @@ -1,53 +0,0 @@ -open Domain.D01p01 - -let err_empty_input = "input is empty" - -let rec input_to_lines input = - let lines = - String.split_on_char '\n' input |> List.filter (fun l -> l <> "") - in - pp_lines lines ; lines - -and pp_lines = function - | [] -> - () - | h :: t -> - Logs.debug (fun m -> m "Line=%s" h) ; - pp_lines t - -let parse_line line = - Scanf.sscanf line "%c%d" (fun d n -> - match d with - | 'L' -> - Dial.Left n - | 'R' -> - Dial.Right n - | _ -> - failwith "invalid direction" ) - -let lines_to_turns = - List.map (fun (l : string) -> - Logs.debug (fun m -> m "parsing %s into turn" l) ; - let turn = parse_line l in - Logs.debug (fun m -> m "%s" (Dial.pp_direction turn)) ; - turn ) - -let rec turn_aux count current = function - | [] -> - count - | h :: t -> - let current' = Dial.turn current h in - Logs.debug (fun m -> m "dial at %d" (Dial.to_int current')) ; - if Dial.to_int current' = 0 then turn_aux (count + 1) current' t - else turn_aux count current' t - -let turn current = turn_aux 0 current - -let solve input () = - if input = "" then Error err_empty_input - else - let lines = input_to_lines input in - let turns = lines_to_turns lines in - let start = Dial.start in - let answer = turn start turns in - Ok answer diff --git a/2025/ocaml/lib/application/d01p01.mli b/2025/ocaml/lib/application/d01p01.mli deleted file mode 100644 index 2eecf19..0000000 --- a/2025/ocaml/lib/application/d01p01.mli +++ /dev/null @@ -1,4 +0,0 @@ -val solve : string -> unit -> (int, string) result -(** [solve input ()] is the solution for the first part of the first day of advent of code 2025 for [input]. - The solution for an empty input is [None]. - Requires: [input] to be a multiline string using `\n` as it's line separator. *) diff --git a/2025/ocaml/lib/application/d01p02.ml b/2025/ocaml/lib/application/d01p02.ml deleted file mode 100644 index 7910486..0000000 --- a/2025/ocaml/lib/application/d01p02.ml +++ /dev/null @@ -1,55 +0,0 @@ -open Domain.D01p02 - -let err_empty_input = "input is empty" - -let rec input_to_lines input = - let lines = - String.split_on_char '\n' input |> List.filter (fun l -> l <> "") - in - pp_lines lines ; lines - -and pp_lines = function - | [] -> - () - | h :: t -> - Logs.debug (fun m -> m "Line=%s" h) ; - pp_lines t - -let parse_line line = - Scanf.sscanf line "%c%d" (fun d n -> - match d with - | 'L' -> - Dial.Left n - | 'R' -> - Dial.Right n - | _ -> - failwith "invalid direction" ) - -let lines_to_turns = - List.map (fun (l : string) -> - Logs.debug (fun m -> m "parsing %s into turn" l) ; - let turn = parse_line l in - Logs.debug (fun m -> m "%s" (Dial.pp_direction turn)) ; - turn ) - -let rec turn_aux count dial = function - | [] -> - count - | h :: t -> ( - let dial_and_count = Turner.turn dial count h in - match dial_and_count with - | dial', count' -> - Logs.debug (fun m -> - m "(dial=%d, count=%d)" (Dial.to_int dial') count' ) ; - turn_aux count' dial' t ) - -let turn current = turn_aux 0 current - -let solve input () = - if input = "" then Error err_empty_input - else - let lines = input_to_lines input in - let turns = lines_to_turns lines in - let start = Dial.start in - let answer = turn start turns in - Ok answer diff --git a/2025/ocaml/lib/application/d01p02.mli b/2025/ocaml/lib/application/d01p02.mli deleted file mode 100644 index 9e9a2f2..0000000 --- a/2025/ocaml/lib/application/d01p02.mli +++ /dev/null @@ -1,4 +0,0 @@ -val solve : string -> unit -> (int, string) result -(** [solve input ()] is the solution for the second part of the first day of advent of code 2025 for [input]. - The solution for an empty input is [Error msg]. - Requires: [input] to be a multiline string using `\n` as it's line separator. *) diff --git a/2025/ocaml/lib/application/d02p01.ml b/2025/ocaml/lib/application/d02p01.ml deleted file mode 100644 index f313149..0000000 --- a/2025/ocaml/lib/application/d02p01.ml +++ /dev/null @@ -1,54 +0,0 @@ -open Domain.D02p01 - -let err_empty_input = "input is empty" - -(** [input_to_lines input] is a list of strings that have been split on ','. - Example: [input_to_lines "12-34,56-78,90-100"] becomes the list \[ "12-34"; "56-78"; "90-100" \]. - Requires: [input] to be a string in the format "12-34,56-78,90-100". *) -let input_to_lines input = input |> String.trim |> String.split_on_char ',' - -let pp_lines = Seq.iter (fun l -> Logs.debug (fun m -> m "line=%s" l)) - -(** [lines_to_ranges line] is a list of [Range]s found on [line]. *) -let lines_to_ranges = - Seq.map (fun line -> - Scanf.sscanf line "%d-%d" (fun id1 id2 -> Range.create (id1, id2)) ) - -let pp_ranges = Seq.iter (fun r -> Logs.debug (fun m -> m "%s" (Range.pp r))) - -(** [collect_all ranges] is an unsorted list of [ID]s within the [ranges] (inclusive). *) -let collect_all = - Seq.concat_map (fun range -> List.to_seq (Range.collect range)) - -let pp_collection = Seq.iter (fun c -> Logs.debug (fun m -> m "%s" (ID.pp c))) - -(** [rules] is a list of rules that [ID]s must pass to be considered invalid. *) -let rules = - [ (fun id -> String.length (ID.to_string id) mod 2 = 0) - ; (fun id -> - let id' = ID.to_string id in - let id_len = String.length id' in - let left = String.sub id' 0 (id_len / 2) in - let right = String.sub id' (id_len / 2) (id_len / 2) in - String.equal left right ) ] - -(** [is_invalid id] is [true] for an [id] that passes all of the rules and is considered invalid. *) -let is_invalid = ID.validate rules - -(** [passthrough_with_logs logger lst] is [lst] with the side-effects caused by calling [logger] on [lst]. *) -let passthrough_with_logs logger lst = logger lst ; lst - -let solve input () = - if input = "" then Error err_empty_input - else - let answer = - input |> input_to_lines |> List.to_seq - |> passthrough_with_logs pp_lines - |> lines_to_ranges - |> passthrough_with_logs pp_ranges - |> collect_all - |> passthrough_with_logs pp_collection - |> Seq.filter is_invalid - |> Seq.fold_left (fun acc id -> acc + ID.to_int id) 0 - in - Ok answer diff --git a/2025/ocaml/lib/application/d02p01.mli b/2025/ocaml/lib/application/d02p01.mli deleted file mode 100644 index 8a8eb32..0000000 --- a/2025/ocaml/lib/application/d02p01.mli +++ /dev/null @@ -1,4 +0,0 @@ -val solve : string -> unit -> (int, string) result -(** [solve input ()] is the solution for the first part of the second day of advent of code 2025 for [input]. - The solution for an empty input is [Error msg]. - Requires: [input] to be a string. *) diff --git a/2025/ocaml/lib/application/d02p02.ml b/2025/ocaml/lib/application/d02p02.ml deleted file mode 100644 index d062f7b..0000000 --- a/2025/ocaml/lib/application/d02p02.ml +++ /dev/null @@ -1,86 +0,0 @@ -open Domain.D02p02 - -(* Errors *) - -let err_empty_input = "input is empty" - -(* Logging *) - -(** [passthrough_with_logs logger lst] is [lst] with the side-effects caused by calling [logger] on [lst]. *) -let passthrough_with_logs logger lst = logger lst ; lst - -(** [input_to_lines input] is a list of strings that have been split on ','. - Example: [input_to_lines "12-34,56-78,90-100"] becomes the list \[ "12-34"; "56-78"; "90-100" \]. - Requires: [input] to be a string in the format "12-34,56-78,90-100". *) -let input_to_lines input = input |> String.trim |> String.split_on_char ',' - -let pp_lines = Seq.iter (fun l -> Logs.debug (fun m -> m "line=%s" l)) - -let pp_ranges = Seq.iter (fun r -> Logs.debug (fun m -> m "%s" (Range.pp r))) - -let pp_collection = Seq.iter (fun c -> Logs.debug (fun m -> m "%s" (ID.pp c))) - -(* Core Logic *) - -(** [lines_to_ranges line] is a list of [Range]s found on [line]. *) -let lines_to_ranges = - Seq.map (fun line -> - Scanf.sscanf line "%d-%d" (fun id1 id2 -> Range.create (id1, id2)) ) - -(** [collect_all ranges] is an unsorted list of [ID]s within the [ranges] (inclusive). *) -let collect_all = - Seq.concat_map (fun range -> List.to_seq (Range.collect range)) - -(** [string_to_segments count string] is a list containing [count] string segments with of equal length. - Requires: length of [string] to be divisible by [count] - and [count] to be greater than or equal to zero *) -let rec string_to_segments s = - let segment_lengths = possible_segment_lengths s in - List.map (fun c -> string_to_segments_aux [] c s |> List.rev) segment_lengths - -(** [possible_segment_lengths s] is a list of segment sizes that fit within [s] - that must be checked to rule out a repeating pattern. *) -and possible_segment_lengths s = - List.init (String.length s) (fun x -> x + 1) - |> List.filter (fun x -> x != 1 && String.length s mod x = 0) - -and string_to_segments_aux segments count string = - match List.length segments = count with - | true -> - segments - | false -> - let segment_start = String.length string / count * List.length segments in - let segment_len = String.length string / count in - string_to_segments_aux - (String.sub string segment_start segment_len :: segments) - count string - -(** [cmp_segments segments] is true is all segments are equal *) -let cmp_segments segments = - List.length (List.sort_uniq String.compare segments) = 1 - -(** [is_invalid id] is [true] for an [id] that is considered invalid. *) -let is_invalid id = - Logs.debug (fun m -> m "CHECKING ID=%s" id) ; - let repeating_patterns = - id |> string_to_segments |> List.filter cmp_segments - in - List.iter - (fun l -> Logs.debug (fun m -> m "HAS SUBPATTERN=%s" (List.hd l))) - repeating_patterns ; - List.length repeating_patterns > 0 - -let solve input () = - if input = "" then Error err_empty_input - else - let answer = - input |> input_to_lines |> List.to_seq - |> passthrough_with_logs pp_lines - |> lines_to_ranges - |> passthrough_with_logs pp_ranges - |> collect_all - |> passthrough_with_logs pp_collection - |> Seq.map ID.to_string |> Seq.filter is_invalid - |> Seq.fold_left (fun acc id -> acc + int_of_string id) 0 - in - Ok answer diff --git a/2025/ocaml/lib/application/d02p02.mli b/2025/ocaml/lib/application/d02p02.mli deleted file mode 100644 index 8a8eb32..0000000 --- a/2025/ocaml/lib/application/d02p02.mli +++ /dev/null @@ -1,4 +0,0 @@ -val solve : string -> unit -> (int, string) result -(** [solve input ()] is the solution for the first part of the second day of advent of code 2025 for [input]. - The solution for an empty input is [Error msg]. - Requires: [input] to be a string. *) diff --git a/2025/ocaml/lib/application/d03p01.ml b/2025/ocaml/lib/application/d03p01.ml deleted file mode 100644 index 522f5d0..0000000 --- a/2025/ocaml/lib/application/d03p01.ml +++ /dev/null @@ -1,23 +0,0 @@ -open Domain.D03p01 - -let err_empty_input = "input is empty" - -let to_lines s = - s |> String.split_on_char '\n' |> List.map String.trim - |> List.filter (fun l -> l <> "") - -let solve input () = - if input = "" then Error err_empty_input - else - let answer = - input |> to_lines |> List.to_seq - |> Seq.map BatteryBank.of_string - |> Seq.mapi (fun idx bb -> - let joltage = BatteryBank.highest_joltage bb in - Logs.debug (fun m -> - m "%d line=%s highest_joltage=%d" (idx + 1) (BatteryBank.pp bb) - joltage ) ; - joltage ) - |> Seq.fold_left ( + ) 0 - in - Ok answer diff --git a/2025/ocaml/lib/application/d03p01.mli b/2025/ocaml/lib/application/d03p01.mli deleted file mode 100644 index 31dd0cd..0000000 --- a/2025/ocaml/lib/application/d03p01.mli +++ /dev/null @@ -1,4 +0,0 @@ -val solve : string -> unit -> (int, string) result -(** [solve input ()] is the solution for the first part of the third day of advent of code 2025 for [input]. - The solution for an empty input is [Error msg]. - Requires: [input] to be a string. *) diff --git a/2025/ocaml/lib/application/d03p02.ml b/2025/ocaml/lib/application/d03p02.ml deleted file mode 100644 index 743c658..0000000 --- a/2025/ocaml/lib/application/d03p02.ml +++ /dev/null @@ -1,23 +0,0 @@ -open Domain.D03p02 - -let err_empty_input = "input is empty" - -let to_lines s = - s |> String.split_on_char '\n' |> List.map String.trim - |> List.filter (fun l -> l <> "") - -let solve input () = - if input = "" then Error err_empty_input - else - let answer = - input |> to_lines |> List.to_seq - |> Seq.map BatteryBank.of_string - |> Seq.mapi (fun idx bb -> - let joltage = BatteryBank.highest_joltage bb in - Logs.debug (fun m -> - m "%d line=%s highest_joltage=%d" (idx + 1) (BatteryBank.pp bb) - joltage ) ; - joltage ) - |> Seq.fold_left ( + ) 0 - in - Ok answer diff --git a/2025/ocaml/lib/application/d03p02.mli b/2025/ocaml/lib/application/d03p02.mli deleted file mode 100644 index 93d3b98..0000000 --- a/2025/ocaml/lib/application/d03p02.mli +++ /dev/null @@ -1,4 +0,0 @@ -val solve : string -> unit -> (int, string) result -(** [solve input ()] is the solution for the second part of the third day of advent of code 2025 for [input]. - The solution for an empty input is [Error msg]. - Requires: [input] to be a string. *) diff --git a/2025/ocaml/lib/application/d04p01.ml b/2025/ocaml/lib/application/d04p01.ml deleted file mode 100644 index 7c69de4..0000000 --- a/2025/ocaml/lib/application/d04p01.ml +++ /dev/null @@ -1,48 +0,0 @@ -open Domain - -let err_empty_input = "input is empty" - -let rec solve input () = - if input = "" then Error err_empty_input - else - let matrix = Matrix.M.of_string input in - let count_paper lst = List.length @@ List.filter (fun c -> c <> ".") lst in - let accessible_rolls_of_paper = ref 0 in - for y = 0 to Array.length matrix - 1 do - for x = 0 to Array.length matrix.(y) - 1 do - if matrix.(y).(x) = "." then () - else - let neighbour_coords = Matrix.M.get_valid_neighbours matrix x y in - let neighbours = - List.map - (fun (x', y') -> Matrix.M.get matrix x' y') - neighbour_coords - in - let paper_count = count_paper neighbours in - let is_accessible = paper_count < 4 in - if is_accessible then - accessible_rolls_of_paper := !accessible_rolls_of_paper + 1 ; - print_iteration_debug_msg x y neighbour_coords neighbours paper_count - is_accessible - done - done ; - Ok !accessible_rolls_of_paper - -and print_iteration_debug_msg x y neighbour_coords neighbours paper_count - is_accessible = - Logs.debug (fun m -> - m - "coords=(%d,%d) neighbour_coords=[%s] neighbours=[%s] paper=%d \ - is_accessible=%B" - x y - (List.fold_left - (fun acc (x, y) -> - if acc = "" then - " (" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " - else acc ^ "(" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " ) - "" neighbour_coords ) - (List.fold_left - (fun acc neighbour -> - if acc = "" then " " ^ neighbour ^ "; " else acc ^ neighbour ^ "; " ) - "" neighbours ) - paper_count is_accessible ) diff --git a/2025/ocaml/lib/application/d04p01.mli b/2025/ocaml/lib/application/d04p01.mli deleted file mode 100644 index 873acff..0000000 --- a/2025/ocaml/lib/application/d04p01.mli +++ /dev/null @@ -1,4 +0,0 @@ -val solve : string -> unit -> (int, string) result -(** [solve input ()] is the solution for the first part of the fourth day of advent of code 2025 for [input]. - The solution for an empty input is [Error msg]. - Requires: [input] to be a string. *) diff --git a/2025/ocaml/lib/application/d04p02.ml b/2025/ocaml/lib/application/d04p02.ml deleted file mode 100644 index 75b1236..0000000 --- a/2025/ocaml/lib/application/d04p02.ml +++ /dev/null @@ -1,64 +0,0 @@ -open Domain - -let err_empty_input = "input is empty" - -let rec count_accessible_rolls matrix = - let count_paper lst = List.length @@ List.filter (fun c -> c = "@") lst in - let accessible_rolls_of_paper = ref [] in - let no_change = ref false in - while not !no_change do - let accessible_rolls_of_paper' = !accessible_rolls_of_paper in - count_accessible_rolls_aux matrix count_paper accessible_rolls_of_paper ; - if - List.length accessible_rolls_of_paper' - = List.length !accessible_rolls_of_paper - then no_change := true - done ; - List.length !accessible_rolls_of_paper - -and count_accessible_rolls_aux matrix count_paper accessible_rolls_of_paper = - for y = 0 to Array.length matrix - 1 do - for x = 0 to Array.length matrix.(y) - 1 do - if matrix.(y).(x) = "." then () - else - let neighbour_coords = Matrix.M.get_valid_neighbours matrix x y in - let neighbours = - List.map (fun (nx, ny) -> Matrix.M.get matrix nx ny) neighbour_coords - in - let paper_count = count_paper neighbours in - let is_accessible = paper_count < 4 in - if is_accessible then - accessible_rolls_of_paper := (x, y) :: !accessible_rolls_of_paper ; - print_iteration_debug_msg x y neighbour_coords neighbours paper_count - is_accessible - done - done ; - List.iter - (fun (x', y') -> Matrix.M.set matrix x' y' ".") - !accessible_rolls_of_paper - -and print_iteration_debug_msg x y neighbour_coords neighbours paper_count - is_accessible = - Logs.debug (fun m -> - m - "coords=(%d,%d) neighbour_coords=[%s] neighbours=[%s] paper=%d \ - is_accessible=%B" - x y - (List.fold_left - (fun acc (x, y) -> - if acc = "" then - " (" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " - else acc ^ "(" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " ) - "" neighbour_coords ) - (List.fold_left - (fun acc neighbour -> - if acc = "" then " " ^ neighbour ^ "; " else acc ^ neighbour ^ "; " ) - "" neighbours ) - paper_count is_accessible ) - -let solve input () = - if input = "" then Error err_empty_input - else - let matrix = Matrix.M.of_string input in - let accessible_rolls_of_paper = count_accessible_rolls matrix in - Ok accessible_rolls_of_paper diff --git a/2025/ocaml/lib/application/d04p02.mli b/2025/ocaml/lib/application/d04p02.mli deleted file mode 100644 index 388ec56..0000000 --- a/2025/ocaml/lib/application/d04p02.mli +++ /dev/null @@ -1,4 +0,0 @@ -val solve : string -> unit -> (int, string) result -(** [solve input ()] is the solution for the second part of the fourth day of advent of code 2025 for [input]. - The solution for an empty input is [Error msg]. - Requires: [input] to be a string. *) diff --git a/2025/ocaml/lib/application/d05p01.ml b/2025/ocaml/lib/application/d05p01.ml deleted file mode 100644 index 4bf79ab..0000000 --- a/2025/ocaml/lib/application/d05p01.ml +++ /dev/null @@ -1,3 +0,0 @@ -let err_empty_input = "input is empty" - -let solve input () = if input = "" then Error err_empty_input else Ok 0 diff --git a/2025/ocaml/lib/application/d05p01.mli b/2025/ocaml/lib/application/d05p01.mli deleted file mode 100644 index 85c3dd5..0000000 --- a/2025/ocaml/lib/application/d05p01.mli +++ /dev/null @@ -1,4 +0,0 @@ -val solve : string -> unit -> (int, string) result -(** [solve input ()] is the solution for the first part of the five day of advent of code 2025 for [input]. - The solution for an empty input is [Error msg]. - Requires: [input] to be a string. *) diff --git a/2025/ocaml/lib/application/d06p01.ml b/2025/ocaml/lib/application/d06p01.ml deleted file mode 100644 index 9425810..0000000 --- a/2025/ocaml/lib/application/d06p01.ml +++ /dev/null @@ -1,50 +0,0 @@ -let err_empty_input = "input is empty" - -let log_matrix matrix = - let open Domain.Matrix in - Logs.debug (fun m -> m "%s" (M.of_list matrix |> M.pp (fun elt -> elt))) - -let log_list lst = - Logs.debug (fun m -> - m "[%s]" (List.fold_left (fun acc i -> acc ^ i ^ "; ") " " lst) ) - -type operation = Mult | Add - -let solve input () = - if input = "" then Error err_empty_input - else - let input' = Parser.columns_of_string input in - assert (input' <> []) ; - log_matrix input' ; - let nums = - List.map (List.filter (fun i -> i <> "*" && i <> "+")) input' - |> List.map - @@ List.map (fun n -> - Logs.debug (fun m -> m "peeking: %s" n) ; - int_of_string n ) - and ops = - List.map (List.filter (fun i -> i = "*" || i = "+")) input' - |> List.concat - |> List.map (fun op -> - match op with - | "*" -> - Mult - | "+" -> - Add - | _ -> - failwith "case not covered" ) - in - let answer = - List.map2 - (fun op num_lst -> - match op with - | Add -> - List.fold_left (fun acc i -> acc + i) 0 num_lst - | Mult -> - List.fold_left (fun acc i -> acc * i) 1 num_lst ) - ops nums - |> fun lst -> - log_list @@ List.map string_of_int lst ; - lst |> List.fold_left ( + ) 0 - in - Ok answer diff --git a/2025/ocaml/lib/application/d06p01.mli b/2025/ocaml/lib/application/d06p01.mli deleted file mode 100644 index b3abc1e..0000000 --- a/2025/ocaml/lib/application/d06p01.mli +++ /dev/null @@ -1,4 +0,0 @@ -val solve : string -> unit -> (int, string) result -(** [solve input ()] is the solution for the first part of the sixth day of advent of code 2025 for [input]. - The solution for an empty input is [Error msg]. - Requires: [input] to be a string. *) diff --git a/2025/ocaml/lib/application/d06p02.ml b/2025/ocaml/lib/application/d06p02.ml deleted file mode 100644 index 9425810..0000000 --- a/2025/ocaml/lib/application/d06p02.ml +++ /dev/null @@ -1,50 +0,0 @@ -let err_empty_input = "input is empty" - -let log_matrix matrix = - let open Domain.Matrix in - Logs.debug (fun m -> m "%s" (M.of_list matrix |> M.pp (fun elt -> elt))) - -let log_list lst = - Logs.debug (fun m -> - m "[%s]" (List.fold_left (fun acc i -> acc ^ i ^ "; ") " " lst) ) - -type operation = Mult | Add - -let solve input () = - if input = "" then Error err_empty_input - else - let input' = Parser.columns_of_string input in - assert (input' <> []) ; - log_matrix input' ; - let nums = - List.map (List.filter (fun i -> i <> "*" && i <> "+")) input' - |> List.map - @@ List.map (fun n -> - Logs.debug (fun m -> m "peeking: %s" n) ; - int_of_string n ) - and ops = - List.map (List.filter (fun i -> i = "*" || i = "+")) input' - |> List.concat - |> List.map (fun op -> - match op with - | "*" -> - Mult - | "+" -> - Add - | _ -> - failwith "case not covered" ) - in - let answer = - List.map2 - (fun op num_lst -> - match op with - | Add -> - List.fold_left (fun acc i -> acc + i) 0 num_lst - | Mult -> - List.fold_left (fun acc i -> acc * i) 1 num_lst ) - ops nums - |> fun lst -> - log_list @@ List.map string_of_int lst ; - lst |> List.fold_left ( + ) 0 - in - Ok answer diff --git a/2025/ocaml/lib/application/d06p02.mli b/2025/ocaml/lib/application/d06p02.mli deleted file mode 100644 index 49444de..0000000 --- a/2025/ocaml/lib/application/d06p02.mli +++ /dev/null @@ -1,4 +0,0 @@ -val solve : string -> unit -> (int, string) result -(** [solve input ()] is the solution for the second part of the sixth day of advent of code 2025 for [input]. - The solution for an empty input is [Error msg]. - Requires: [input] to be a string. *) diff --git a/2025/ocaml/lib/application/dune b/2025/ocaml/lib/application/dune deleted file mode 100644 index b78613a..0000000 --- a/2025/ocaml/lib/application/dune +++ /dev/null @@ -1,14 +0,0 @@ -(library - (name application) - (libraries - domain - fmt - logs) - (inline_tests) - (preprocess - (pps - ppx_deriving.show - ppx_deriving.eq - ppx_deriving.ord - ppx_inline_test - ppx_expect))) diff --git a/2025/ocaml/lib/application/parser.ml b/2025/ocaml/lib/application/parser.ml deleted file mode 100644 index 099900b..0000000 --- a/2025/ocaml/lib/application/parser.ml +++ /dev/null @@ -1,36 +0,0 @@ -(** [rows_of_string s] is a list of strings where each element is a row of [s]. *) -let rows_of_string s = - s |> String.split_on_char '\n' - |> List.filter_map (fun l -> - let trimmed = String.trim l in - if trimmed <> "" then Some trimmed else None ) - -(** [columns_of_string ?sep s] is a list of strings where each element is - a column of [s] where each column is separated by [sep]. *) -let rec columns_of_string ?(sep = ' ') s = - rows_of_string s - |> List.map (fun l -> - l |> String.split_on_char sep |> List.filter (fun l -> l <> "") ) - |> transpose - -(** [group_columns lst] is a list where the first element of each - sublist of [lst] is grouped in [acc] and so on for the rest of the elements. - Example: [group_columns [] \[\["abc"; "def"\]; \["ghi"; "jkl"\]\]] is the list [\[\["abc";"ghi"\];\["def";"jkl"\]\]]. *) -and transpose lst = - assert (lst <> []) ; - if List.mem [] lst then [] - else List.map List.hd lst :: transpose (List.map List.tl lst) - -let%expect_test "rows correctly parsed" = - let have = {| abc def - ghi jkl |} in - let rows = rows_of_string have in - List.iter (fun c -> print_string (c ^ " ")) rows ; - [%expect {| abc def ghi jkl |}] - -let%expect_test "columns correctly parsed" = - let have = {| abc def - ghi jkl |} in - let cols = columns_of_string have in - List.iter (fun row -> List.iter (fun c -> print_string (c ^ " ")) row) cols ; - [%expect {| abc ghi def jkl |}] diff --git a/2025/ocaml/lib/domain/d01p01.ml b/2025/ocaml/lib/domain/d01p01.ml deleted file mode 100644 index 6b531f4..0000000 --- a/2025/ocaml/lib/domain/d01p01.ml +++ /dev/null @@ -1,76 +0,0 @@ -module Dial : sig - (** [t] is a dial between 0 and 99 inclusive *) - type t - - (** [direction] is a direction the dial can be turned *) - type direction = Left of int | Right of int - - val start : t - (** [start] is the initial state of the dial *) - - val dial_out_of_range : string - (** [dial_out_of_range] is the error message when the dial is out of range *) - - val turn : t -> direction -> t - (** [turn current direction] is the new state of the dial after turning [direction] from [current] *) - - val to_string : t -> string - (** [to_string t] is the string representation of [t] *) - - val to_int : t -> int - (** [to_int t] is the integer representation of [t] *) - - val pp_direction : direction -> string -end = struct - (** [t] is an int between 0 and 99 inclusive *) - type t = int - - type direction = Left of int | Right of int - - let start = 50 - - let dial_out_of_range = "dial out of range" - - (** [next n] is the new state of the dial after turning [Right]. - Raises: [dial_out_of_range] if [n] is greater than 99. *) - let next = function - | n when n < 99 -> - n + 1 - | 99 -> - 0 - | _ -> - failwith dial_out_of_range - - (** [prev n] is the new state of the dial after turning [Left]. - Raises: [dial_out_of_range] if [n] is less than 0. *) - let prev = function - | n when n > 0 -> - n - 1 - | 0 -> - 99 - | _ -> - failwith dial_out_of_range - - (** [turn current direction] is the new state of the dial after turning [direction] from [current]. - Raises: [dial_out_of_range] if the dial is moved to an invalid state. *) - let rec turn current direction = - match direction with - | Left 0 -> - current - | Left n -> - turn (prev current) (Left (n - 1)) - | Right 0 -> - current - | Right n -> - turn (next current) (Right (n - 1)) - - let to_string = string_of_int - - let to_int t = t - - let pp_direction = function - | Left n -> - "Left " ^ string_of_int n - | Right n -> - "Right " ^ string_of_int n -end diff --git a/2025/ocaml/lib/domain/d01p02.ml b/2025/ocaml/lib/domain/d01p02.ml deleted file mode 100644 index f71e965..0000000 --- a/2025/ocaml/lib/domain/d01p02.ml +++ /dev/null @@ -1,95 +0,0 @@ -module Dial : sig - (** [t] is a dial between 0 and 99 inclusive *) - type t - - (** [direction] is a direction the dial can be turned *) - type direction = Left of int | Right of int - - val start : t - (** [start] is the initial state of the dial *) - - val dial_out_of_range : string - (** [dial_out_of_range] is the error message when the dial is out of range *) - - val prev : t -> t - - val next : t -> t - - val is_zero : t -> bool - - val to_string : t -> string - (** [to_string t] is the string representation of [t] *) - - val to_int : t -> int - (** [to_int t] is the integer representation of [t] *) - - val pp_direction : direction -> string -end = struct - (** [t] is an int between 0 and 99 inclusive *) - type t = int - - type direction = Left of int | Right of int - - let start = 50 - - let dial_out_of_range = "dial out of range" - - (** [next n] is the new state of the dial after turning [Right]. - Raises: [dial_out_of_range] if [n] is greater than 99. *) - let next = function - | n when n < 99 -> - n + 1 - | 99 -> - 0 - | _ -> - failwith dial_out_of_range - - (** [prev n] is the new state of the dial after turning [Left]. - Raises: [dial_out_of_range] if [n] is less than 0. *) - let prev = function - | n when n > 0 -> - n - 1 - | 0 -> - 99 - | _ -> - failwith dial_out_of_range - - let is_zero = function 0 -> true | _ -> false - - let to_string = string_of_int - - let to_int t = t - - let pp_direction = function - | Left n -> - "Left " ^ string_of_int n - | Right n -> - "Right " ^ string_of_int n -end - -module Turner : sig - val turn : Dial.t -> int -> Dial.direction -> Dial.t * int - (** [turn dial direction] is the new state of the dial after turning [direction] on [dial] *) -end = struct - (** [turn dial direction] is the new state of the dial after turning [direction] on [dial]. - Raises: [dial_out_of_range] if the dial is moved to an invalid state. *) - let rec turn dial zero_count direction = - let open Dial in - match direction with - | Left 0 -> - (dial, zero_count) - | Left n -> - let dial' = prev dial in - let zero_count' = - if Dial.is_zero dial' then zero_count + 1 else zero_count - in - turn dial' zero_count' (Left (n - 1)) - | Right 0 -> - (dial, zero_count) - | Right n -> - let dial' = next dial in - let zero_count' = - if Dial.is_zero dial' then zero_count + 1 else zero_count - in - turn dial' zero_count' (Right (n - 1)) -end diff --git a/2025/ocaml/lib/domain/d02p01.ml b/2025/ocaml/lib/domain/d02p01.ml deleted file mode 100644 index 725d4b1..0000000 --- a/2025/ocaml/lib/domain/d02p01.ml +++ /dev/null @@ -1,33 +0,0 @@ -module ID = struct - (** *) - type t = int - - let create num = num - - let validate rules id = List.fold_left (fun acc r -> acc && r id) true rules - - let incr = ( + ) 1 - - let to_int id = id - - let to_string = string_of_int - - let pp id = "ID=" ^ string_of_int id -end - -module Range = struct - type t = ID.t * ID.t - - let create (id1, id2) = (ID.create id1, ID.create id2) - - let rec collect range = collect_range_aux [] range - - and collect_range_aux lst (id1, id2) = - match id1 <= id2 with - | false -> - lst - | true -> - collect_range_aux (id1 :: lst) (ID.incr id1, id2) - - let pp (id1, id2) = "(L" ^ ID.pp id1 ^ ", R" ^ ID.pp id2 ^ ")" -end diff --git a/2025/ocaml/lib/domain/d02p01.mli b/2025/ocaml/lib/domain/d02p01.mli deleted file mode 100644 index 55665fe..0000000 --- a/2025/ocaml/lib/domain/d02p01.mli +++ /dev/null @@ -1,36 +0,0 @@ -module ID : sig - (** [t] is a ID within a range *) - type t - - val create : int -> t - (** [create num] is an ID [t] *) - - val validate : (t -> bool) list -> t -> bool - (** [validate rules id] is [true] if the [id] passes all rule checks *) - - val incr : t -> t - (** [incr id] is the next [id] in the sequence *) - - val to_int : t -> int - (** [to_int id] is an integer representation of [id] *) - - val to_string : t -> string - (** [to_string id] is an string representation of [id] *) - - val pp : t -> string - (** [pp id] is a pretty string representing [id] *) -end - -module Range : sig - (** [t] is a range of values *) - type t - - val create : int * int -> t - (** [create (s, e)] is a range starting at [s] and ending at [e]. *) - - val collect : t -> ID.t list - (** [collect r] is a list containing all elements within the range (inclusive). *) - - val pp : t -> string - (** [pp r] is a pretty string representing [r] *) -end diff --git a/2025/ocaml/lib/domain/d02p02.ml b/2025/ocaml/lib/domain/d02p02.ml deleted file mode 100644 index b2b55f1..0000000 --- a/2025/ocaml/lib/domain/d02p02.ml +++ /dev/null @@ -1,29 +0,0 @@ -module ID = struct - (** *) - type t = int - - let create num = num - - let incr = ( + ) 1 - - let to_string = string_of_int - - let pp id = "ID=" ^ string_of_int id -end - -module Range = struct - type t = ID.t * ID.t - - let create (id1, id2) = (ID.create id1, ID.create id2) - - let rec collect range = collect_range_aux [] range - - and collect_range_aux lst (id1, id2) = - match id1 <= id2 with - | false -> - lst - | true -> - collect_range_aux (id1 :: lst) (ID.incr id1, id2) - - let pp (id1, id2) = "(L" ^ ID.pp id1 ^ ", R" ^ ID.pp id2 ^ ")" -end diff --git a/2025/ocaml/lib/domain/d02p02.mli b/2025/ocaml/lib/domain/d02p02.mli deleted file mode 100644 index f918ae1..0000000 --- a/2025/ocaml/lib/domain/d02p02.mli +++ /dev/null @@ -1,30 +0,0 @@ -module ID : sig - (** [t] is a ID within a range *) - type t - - val create : int -> t - (** [create num] is an ID [t] *) - - val incr : t -> t - (** [incr id] is the next [id] in the sequence *) - - val to_string : t -> string - (** [to_string id] is an string representation of [id] *) - - val pp : t -> string - (** [pp id] is a pretty string representing [id] *) -end - -module Range : sig - (** [t] is a range of values *) - type t - - val create : int * int -> t - (** [create (s, e)] is a range starting at [s] and ending at [e]. *) - - val collect : t -> ID.t list - (** [collect r] is a list containing all elements within the range (inclusive). *) - - val pp : t -> string - (** [pp r] is a pretty string representing [r] *) -end diff --git a/2025/ocaml/lib/domain/d03p01.ml b/2025/ocaml/lib/domain/d03p01.ml deleted file mode 100644 index 1367712..0000000 --- a/2025/ocaml/lib/domain/d03p01.ml +++ /dev/null @@ -1,114 +0,0 @@ -module Battery : sig - type t - - val of_int : int -> t - - val to_int : t -> int - - val to_string : t -> string - - val compare : t -> t -> int - - val pp : t -> string -end = struct - type t = int - - let of_int i = i - - let to_int b = b - - let to_string = string_of_int - - let compare = Int.compare - - let pp = string_of_int -end - -module BatteryBank : sig - type t - - val highest_joltage : t -> int - (** [highest_joltage bb] is an integer created from the two highest - digits in [bb]. - Raises: [Assert_failure] if [bb] is empty. *) - - val of_string : string -> t - - val of_list : Battery.t list -> t - - val to_list : t -> Battery.t list - - val pp : t -> string -end = struct - type t = Battery.t list - - let rec highest_joltage bb = - assert (List.length bb > 0) ; - match highest_joltage_aux (None, None) bb with - | None, None -> - 0 - | Some x, Some y -> - int_of_string (Battery.to_string x ^ Battery.to_string y) - | _ -> - failwith "highest_joltage must be two digits" - - and highest_joltage_aux acc = function - | [] -> - acc - | [elt] -> ( - match acc with - | None, None -> - (Some (Battery.of_int 0), Some elt) - | Some first, None -> - (Some first, Some elt) - | None, Some second -> - (Some second, Some elt) - | Some first, Some second -> - if elt > second then (Some first, Some elt) - else (Some first, Some second) ) - | h :: t -> - highest_joltage_aux - ( match acc with - | None, None -> - (Some h, None) - | Some first, None -> - let second = Battery.of_int 0 in - swap_if_higher first second h - | Some first, Some second -> - swap_if_higher first second h - | _ -> - failwith "this branch should be impossible" ) - t - - and swap_if_higher first second considering = - assert (first >= second) ; - let c_gt_f = Battery.compare considering first = 1 in - let c_gt_s = Battery.compare considering second = 1 in - match (c_gt_f, c_gt_s) with - | true, true -> - (Some considering, None) - | false, true -> - (Some first, Some considering) - | true, false -> - failwith "[second] should never be greater than [first]" - | false, false -> - (Some first, Some second) - - let of_string s = - s |> String.to_seq - |> Seq.map (fun c -> Char.escaped c |> int_of_string |> Battery.of_int) - |> List.of_seq - - let of_list lst = lst - - let to_list bb = bb - - let pp bb = - "[ " - ^ ( bb |> List.map Battery.pp - |> List.fold_left - (fun acc b -> - if acc = "" then b ^ ";" else Printf.sprintf "%s %s;" acc b ) - "" ) - ^ " ]" -end diff --git a/2025/ocaml/lib/domain/d03p02.ml b/2025/ocaml/lib/domain/d03p02.ml deleted file mode 100644 index 88257d0..0000000 --- a/2025/ocaml/lib/domain/d03p02.ml +++ /dev/null @@ -1,116 +0,0 @@ -module Battery : sig - type t - - val of_int : int -> t - - val to_int : t -> int - - val to_string : t -> string - - val compare : t -> t -> int - - val pp : t -> string -end = struct - type t = int - - let of_int i = i - - let to_int b = b - - let to_string = string_of_int - - let compare = Int.compare - - let pp = string_of_int -end - -module BatteryBank : sig - type t - - val highest_joltage : t -> int - (** [highest_joltage bb] is an integer created from the two highest - digits in [bb]. - Raises: [Assert_failure] if [bb] is empty. *) - - val of_string : string -> t - - val of_list : Battery.t list -> t - - val to_list : t -> Battery.t list - - val pp : t -> string -end = struct - type t = Battery.t list - - type highest = {one: Battery.t option; two: Battery.t option} - - let rec highest_joltage bb = - assert (List.length bb > 0) ; - match highest_joltage_aux {one= None; two= None} bb with - | {one= None; two= None} -> - 0 - | {one= Some x; two= Some y} -> - int_of_string (Battery.to_string x ^ Battery.to_string y) - | _ -> - failwith "highest_joltage must be two digits" - - and highest_joltage_aux acc = function - | [] -> - acc - | [elt] -> ( - match acc with - | {one= None; two= None} -> - {one= Some (Battery.of_int 0); two= Some elt} - | {one= Some first; two= None} -> - {one= Some first; two= Some elt} - | {one= None; two= Some second} -> - {one= Some second; two= Some elt} - | {one= Some first; two= Some second} -> - if elt > second then {one= Some first; two= Some elt} - else {one= Some first; two= Some second} ) - | h :: t -> - highest_joltage_aux - ( match acc with - | {one= None; two= None} -> - {one= Some h; two= None} - | {one= Some first; two= None} -> - let second = Battery.of_int 0 in - swap_if_higher first second h - | {one= Some first; two= Some second} -> - swap_if_higher first second h - | _ -> - failwith "this branch should be impossible" ) - t - - and swap_if_higher first second considering = - assert (first >= second) ; - let c_gt_f = Battery.compare considering first = 1 in - let c_gt_s = Battery.compare considering second = 1 in - match (c_gt_f, c_gt_s) with - | true, true -> - {one= Some considering; two= None} - | false, true -> - {one= Some first; two= Some considering} - | true, false -> - failwith "[second] should never be greater than [first]" - | false, false -> - {one= Some first; two= Some second} - - let of_string s = - s |> String.to_seq - |> Seq.map (fun c -> Char.escaped c |> int_of_string |> Battery.of_int) - |> List.of_seq - - let of_list lst = lst - - let to_list bb = bb - - let pp bb = - "[ " - ^ ( bb |> List.map Battery.pp - |> List.fold_left - (fun acc b -> - if acc = "" then b ^ ";" else Printf.sprintf "%s %s;" acc b ) - "" ) - ^ " ]" -end diff --git a/2025/ocaml/lib/domain/dune b/2025/ocaml/lib/domain/dune deleted file mode 100644 index 5b13d31..0000000 --- a/2025/ocaml/lib/domain/dune +++ /dev/null @@ -1,13 +0,0 @@ -(library - (name domain) - (inline_tests) - (libraries - fmt - logs) - (preprocess - (pps - ppx_deriving.show - ppx_deriving.eq - ppx_deriving.ord - ppx_inline_test - ppx_expect))) diff --git a/2025/ocaml/lib/domain/matrix.ml b/2025/ocaml/lib/domain/matrix.ml deleted file mode 100644 index fdf476f..0000000 --- a/2025/ocaml/lib/domain/matrix.ml +++ /dev/null @@ -1,196 +0,0 @@ -module M : sig - type 'a t = 'a array array - - val empty : int -> int -> 'a -> 'a t - (** [empty rows columns initial] is an empty 2D array of size [rows] * [columns] - where each element in the array is initialized to [initial]. *) - - val of_list : 'a list list -> 'a t - (** [of_list lst] is a 2D array where every element in the array is an - element corresponds to an element of the [lst]. *) - - val to_list : 'a t -> 'a list list - (** [to_list matrix] is a 2D list where every element in the list is an - element corresponds to an element of the [matrix]. *) - - val of_string : string -> string t - (** [of_string str] is a 2D array where every character is an element. *) - - val get : 'a t -> int -> int -> 'a - (** [get matrix x y] is an alias for [matrix.(y).(x)]. - Requires: [x] and [y] are positive integers or 0. *) - - val set : 'a t -> int -> int -> 'a -> unit - (** [set matrix x y v] is an alias for [matrix.(y).(x) <- v]. - Requires: [x] and [y] are positive integers or 0. *) - - val get_valid_neighbours : 'a t -> int -> int -> (int * int) list - (** [get_valid_neighbours matrix x y] is a list of pairs in the format - (x * y) that represent the valid surrounding positions of [matrix.(y).(x)]. - Requires: [x] and [y] are positive integers or 0. *) - - val pp : ('a -> string) -> 'a t -> string - (** [pp matrix string_of_elt] is the string representation of the [matrix] - where [string_of_elt] is called on each element. *) -end = struct - type 'a t = 'a array array - - let empty cols rows initial = Array.make_matrix cols rows initial - - let of_list lst = Array.of_list @@ List.map Array.of_list lst - - let to_list matrix = Array.to_list @@ Array.map Array.to_list matrix - - let of_string str = - let lines = - str |> String.split_on_char '\n' - |> List.filter_map (fun line -> - let trimmed = String.trim line in - if trimmed <> "" then Some trimmed else None ) - in - let char_lst = - List.map - (fun s -> s |> String.to_seq |> List.of_seq |> List.map Char.escaped) - lines - in - of_list char_lst - - let get matrix x y = matrix.(y).(x) - - let set matrix x y v = matrix.(y).(x) <- v - - let get_valid_neighbours matrix x y = - let potential_neighbours = - [ (x - 1, y - 1) - ; (x, y - 1) - ; (x + 1, y - 1) - ; (x - 1, y) - ; (x + 1, y) - ; (x - 1, y + 1) - ; (x, y + 1) - ; (x + 1, y + 1) ] - in - let min_x = 0 in - let max_x = Array.length matrix.(y) - 1 in - let min_y = 0 in - let max_y = Array.length matrix - 1 in - List.filter - (fun (x, y) -> min_x <= x && x <= max_x && min_y <= y && y <= max_y) - potential_neighbours - - let pp string_of_elt matrix = - "[" - ^ Array.fold_left - (fun col_acc arr -> - let row = - "[" - ^ Array.fold_left - (fun row_acc elt -> row_acc ^ string_of_elt elt ^ "; ") - " " arr - ^ "]" - in - col_acc ^ " " ^ row ^ "\n" ) - "\n" matrix - ^ "]" -end - -let%expect_test "matrix created from input is pretty-printed as the same matrix" - = - let example = - {|..@@.@@@@. -@@@.@.@.@@ -@@@@@.@.@@ -@.@@@@..@. -@@.@@@@.@@ -.@@@@@@@.@ -.@.@.@.@@@ -@.@@@.@@@@ -.@@@@@@@@. -@.@.@@@.@.|} - in - print_string @@ M.pp (fun s -> s) @@ M.of_string example ; - [%expect - {| - [ - [ .; .; @; @; .; @; @; @; @; .; ] - [ @; @; @; .; @; .; @; .; @; @; ] - [ @; @; @; @; @; .; @; .; @; @; ] - [ @; .; @; @; @; @; .; .; @; .; ] - [ @; @; .; @; @; @; @; .; @; @; ] - [ .; @; @; @; @; @; @; @; .; @; ] - [ .; @; .; @; .; @; .; @; @; @; ] - [ @; .; @; @; @; .; @; @; @; @; ] - [ .; @; @; @; @; @; @; @; @; .; ] - [ @; .; @; .; @; @; @; .; @; .; ] - ] - |}] - -let%expect_test "direct access of the matrix is in form matrix.(y).(x)" = - let example = - {|..@@.@@@@. -@@@.@.@.@@ -@@@@@.@.@@ -@.@@@@..@. -@@.@@@@.@@ -.@@@@@@@.@ -.@.@.@.@@@ -@.@@@.@@@@ -.@@@@@@@@. -@.@.@@@.@.|} - in - let matrix = M.of_string example in - print_string matrix.(0).(0) ; - [%expect "."] ; - print_string matrix.(0).(1) ; - [%expect "."] ; - print_string matrix.(0).(2) ; - [%expect "@"] ; - print_string matrix.(0).(3) ; - [%expect "@"] ; - print_string matrix.(0).(4) ; - [%expect "."] ; - print_string matrix.(0).(5) ; - [%expect "@"] ; - print_string matrix.(0).(6) ; - [%expect "@"] ; - print_string matrix.(0).(7) ; - [%expect "@"] ; - print_string matrix.(0).(8) ; - [%expect "@"] ; - print_string matrix.(0).(9) ; - [%expect "."] - -let%expect_test "[M.get matrix x y] is an alias for [matrix.(y).(x)]" = - let example = - {|..@@.@@@@. -@@@.@.@.@@ -@@@@@.@.@@ -@.@@@@..@. -@@.@@@@.@@ -.@@@@@@@.@ -.@.@.@.@@@ -@.@@@.@@@@ -.@@@@@@@@. -@.@.@@@.@.|} - in - let matrix = M.of_string example in - print_string @@ M.get matrix 0 0 ; - [%expect "."] ; - print_string @@ M.get matrix 1 0 ; - [%expect "."] ; - print_string @@ M.get matrix 2 0 ; - [%expect "@"] ; - print_string @@ M.get matrix 3 0 ; - [%expect "@"] ; - print_string @@ M.get matrix 4 0 ; - [%expect "."] ; - print_string @@ M.get matrix 5 0 ; - [%expect "@"] ; - print_string @@ M.get matrix 6 0 ; - [%expect "@"] ; - print_string @@ M.get matrix 7 0 ; - [%expect "@"] ; - print_string @@ M.get matrix 8 0 ; - [%expect "@"] ; - print_string @@ M.get matrix 9 0 ; - [%expect "."] diff --git a/2025/ocaml/lib/domain/range.ml b/2025/ocaml/lib/domain/range.ml deleted file mode 100644 index a1bdc00..0000000 --- a/2025/ocaml/lib/domain/range.ml +++ /dev/null @@ -1,67 +0,0 @@ -module M : sig - type t - - val of_string : string -> t option - (** [of_string s] is a range of integers if [s] is a string in the form ["%d-%d"] - or [None] otherwise. *) - - val collect : t -> int list - (** [collect range] is a fully realised range as a list. - Example: ["1-5" |> of_string |> collect] is the list [\[1;2;3;4;5\]]. - Example: ["5-1" |> of_string |> collect] is the list [\[5;4;3;2;1\]].*) - - val pp : t -> string - (** [pp range] is a pretty-printed version of the range. *) -end = struct - type t = int * int - - let of_string s = - Scanf.sscanf_opt s "%d-%d" (fun start finish -> (start, finish)) - - let rec collect (start, finish) = - let collected = - match Int.compare start finish with - | -1 -> - collect_range_aux Int.succ ( > ) [] (start, finish) - | 0 -> - [start] - | 1 -> - collect_range_aux Int.pred ( < ) [] (start, finish) - | _ -> - assert false - in - List.rev collected - - and collect_range_aux fn cmp lst (start, finish) = - if cmp start finish then lst - else collect_range_aux fn cmp (start :: lst) (fn start, finish) - - let pp (start, finish) = string_of_int start ^ "-" ^ string_of_int finish -end - -let%expect_test "incrementing range" = - match M.of_string "1-5" with - | None -> - [%expect.unreachable] - | Some range -> - let collected = M.collect range in - List.iter print_int collected ; - [%expect "12345"] - -let%expect_test "decrementing range" = - match M.of_string "5-1" with - | None -> - [%expect.unreachable] - | Some range -> - let collected = M.collect range in - List.iter print_int collected ; - [%expect "54321"] - -let%expect_test "one element range" = - match M.of_string "1-1" with - | None -> - [%expect.unreachable] - | Some range -> - let collected = M.collect range in - List.iter print_int collected ; - [%expect "1"] diff --git a/2025/ocaml/lib/infrastructure/dune b/2025/ocaml/lib/infrastructure/dune deleted file mode 100644 index b9d250f..0000000 --- a/2025/ocaml/lib/infrastructure/dune +++ /dev/null @@ -1,15 +0,0 @@ -(library - (name infrastructure) - (libraries - application - domain - fmt - logs) - (inline_tests) - (preprocess - (pps - ppx_deriving.show - ppx_deriving.eq - ppx_deriving.ord - ppx_inline_test - ppx_expect))) diff --git a/2025/ocaml/lib/infrastructure/file.ml b/2025/ocaml/lib/infrastructure/file.ml deleted file mode 100644 index 3b49c01..0000000 --- a/2025/ocaml/lib/infrastructure/file.ml +++ /dev/null @@ -1,4 +0,0 @@ -let read filename = - let chan = In_channel.open_text filename in - let content = In_channel.input_all chan in - In_channel.close chan ; content diff --git a/2025/ocaml/lib/infrastructure/file.mli b/2025/ocaml/lib/infrastructure/file.mli deleted file mode 100644 index 17d4495..0000000 --- a/2025/ocaml/lib/infrastructure/file.mli +++ /dev/null @@ -1,3 +0,0 @@ -val read : string -> string -(** [read filename] is the contents of [filename] as a string. - The empty string is returned if [filename] does not exist. *) diff --git a/2025/ocaml/lib/presentation/cli.ml b/2025/ocaml/lib/presentation/cli.ml deleted file mode 100644 index 7864e7f..0000000 --- a/2025/ocaml/lib/presentation/cli.ml +++ /dev/null @@ -1,62 +0,0 @@ -(* Business logic behind CLI *) - -let run_solver name solver filepath = - let content = Infrastructure.File.read filepath in - match solver content () with - | Error msg -> - Logs.app (fun m -> m "[%s] Err=%s" name msg) - | Ok answer -> - Logs.app (fun m -> m "[%s] ANSWER=%d" name answer) - -let run_day _ = function - | 1 -> - let filepath = "../_input/day_01.txt" in - run_solver "D01P01" Application.D01p01.solve filepath ; - run_solver "D01P02" Application.D01p02.solve filepath ; - 0 - | 2 -> - let filepath = "../_input/day_02.txt" in - run_solver "D02P01" Application.D02p01.solve filepath ; - run_solver "D02P02" Application.D02p02.solve filepath ; - 0 - | 3 -> - let filepath = "../_input/day_03.txt" in - run_solver "D03P01" Application.D03p01.solve filepath ; - run_solver "D03P02" Application.D03p02.solve filepath ; - 0 - | 4 -> - let filepath = "../_input/day_04.txt" in - run_solver "D04P01" Application.D04p01.solve filepath ; - run_solver "D04P02" Application.D04p02.solve filepath ; - 0 - | 5 -> - let filepath = "../_input/day_05.txt" in - run_solver "D05P01" Application.D05p01.solve filepath ; - 0 - | 6 -> - let filepath = "../_input/day_06.txt" in - run_solver "D06P01" Application.D06p01.solve filepath ; - run_solver "D06P02" Application.D06p02.solve filepath ; - 0 - | _ -> - failwith "day not implemented" - -(* Command line interface *) - -open Cmdliner - -let setup_log style_renderer = Fmt_tty.setup_std_outputs ?style_renderer () - -let setup_log_t = Term.(const setup_log $ Fmt_cli.style_renderer ()) - -let day_arg = - let doc = "Day to run. MUST be in range of 1-12." in - Arg.(value & pos 0 int 1 & info [] ~docv:"DAY" ~doc) - -let day_t = Term.(const run_day $ setup_log_t $ day_arg) - -let cmd = - let info = Cmd.info "aoc" ~version:"0.1.0" in - Cmd.v info day_t - -let run () = Cmd.eval' cmd diff --git a/2025/ocaml/lib/presentation/dune b/2025/ocaml/lib/presentation/dune deleted file mode 100644 index ae1fcb1..0000000 --- a/2025/ocaml/lib/presentation/dune +++ /dev/null @@ -1,16 +0,0 @@ -(library - (name presentation) - (libraries - application - domain - infrastructure - cmdliner - fmt.cli - fmt.tty) - (inline_tests) - (preprocess - (pps - ppx_deriving.show - ppx_deriving.eq - ppx_deriving.ord - ppx_expect))) diff --git a/2025/ocaml/test/application/dune b/2025/ocaml/test/application/dune deleted file mode 100644 index 7a90f81..0000000 --- a/2025/ocaml/test/application/dune +++ /dev/null @@ -1,9 +0,0 @@ -(tests - (names - test_d01p01 - test_d01p02 - test_d04p01) - (libraries - application - domain - alcotest)) diff --git a/2025/ocaml/test/application/test_d01p01.ml b/2025/ocaml/test/application/test_d01p01.ml deleted file mode 100644 index e57db01..0000000 --- a/2025/ocaml/test/application/test_d01p01.ml +++ /dev/null @@ -1,23 +0,0 @@ -open Application.D01p01 - -let example_data = {|L68 -L30 -R48 -L5 -R60 -L55 -L1 -L99 -R14 -L82|} - -let test_full_given_example () = - Alcotest.(check int) - "full given example" 3 - (match solve example_data () with Error _ -> 0 | Ok ans -> ans) - -let () = - let open Alcotest in - run "D01p01" - [ ( "given-example" - , [test_case "full given example" `Quick test_full_given_example] ) ] diff --git a/2025/ocaml/test/application/test_d01p02.ml b/2025/ocaml/test/application/test_d01p02.ml deleted file mode 100644 index bbda035..0000000 --- a/2025/ocaml/test/application/test_d01p02.ml +++ /dev/null @@ -1,23 +0,0 @@ -open Application.D01p02 - -let example_data = {|L68 -L30 -R48 -L5 -R60 -L55 -L1 -L99 -R14 -L82|} - -let test_full_given_example () = - Alcotest.(check int) - "full given example" 6 - (match solve example_data () with Error _ -> 0 | Ok ans -> ans) - -let () = - let open Alcotest in - run "D01p02" - [ ( "given-example" - , [test_case "full given example" `Quick test_full_given_example] ) ] diff --git a/2025/ocaml/test/application/test_d04p01.ml b/2025/ocaml/test/application/test_d04p01.ml deleted file mode 100644 index ffbe102..0000000 --- a/2025/ocaml/test/application/test_d04p01.ml +++ /dev/null @@ -1,24 +0,0 @@ -open Application.D04p01 - -let example = - {|..@@.@@@@. -@@@.@.@.@@ -@@@@@.@.@@ -@.@@@@..@. -@@.@@@@.@@ -.@@@@@@@.@ -.@.@.@.@@@ -@.@@@.@@@@ -.@@@@@@@@. -@.@.@@@.@.|} - -let test_input_to_matrix have want () = - Alcotest.(check int) "number of rolls of paper that are accessible" want - @@ match solve have () with Error _ -> 0 | Ok ans -> ans - -let () = - let open Alcotest in - run "D04p01" - [ ( "given-example" - , [test_case "full example" `Quick @@ test_input_to_matrix example 13] ) - ] diff --git a/2025/ocaml/test/domain/dune b/2025/ocaml/test/domain/dune deleted file mode 100644 index 1364288..0000000 --- a/2025/ocaml/test/domain/dune +++ /dev/null @@ -1,11 +0,0 @@ -(tests - (names - test_d01p01 - test_d01p02 - test_d03p01) - (libraries - domain - alcotest - qcheck-core - qcheck-alcotest) - (modes byte exe)) diff --git a/2025/ocaml/test/domain/test_d01p01.ml b/2025/ocaml/test/domain/test_d01p01.ml deleted file mode 100644 index 23404f6..0000000 --- a/2025/ocaml/test/domain/test_d01p01.ml +++ /dev/null @@ -1,29 +0,0 @@ -open Domain.D01p01 - -let test_start () = - Alcotest.(check int) "incorrect starting state" 50 (Dial.to_int Dial.start) - -let test_turn desc exp dir = - Alcotest.(check int) desc exp (Dial.to_int (Dial.turn Dial.start dir)) - -let test_turn_left_one () = - test_turn "incorrect result when turning left one step" 49 (Dial.Left 1) - -let test_turn_left_100 () = - test_turn "incorrect result when turning left 100 steps" 50 (Dial.Left 100) - -let test_turn_right_one () = - test_turn "incorrect result when turning right one step" 51 (Dial.Right 1) - -let test_turn_right_100 () = - test_turn "incorrect result when turning right 100 steps" 50 (Dial.Right 100) - -let () = - let open Alcotest in - run "D01p01" - [ ("initial-state", [test_case "starting state" `Quick test_start]) - ; ( "turning-dial" - , [ test_case "turning left one step" `Quick test_turn_left_one - ; test_case "turning right one step" `Quick test_turn_right_one - ; test_case "turning left 100 steps" `Quick test_turn_left_100 - ; test_case "turning right 100 steps" `Quick test_turn_right_100 ] ) ] diff --git a/2025/ocaml/test/domain/test_d01p02.ml b/2025/ocaml/test/domain/test_d01p02.ml deleted file mode 100644 index 98c06c3..0000000 --- a/2025/ocaml/test/domain/test_d01p02.ml +++ /dev/null @@ -1,41 +0,0 @@ -open Domain.D01p02 - -let test_start () = - Alcotest.(check int) "incorrect starting state" 50 (Dial.to_int Dial.start) - -let test_turn errmsg exp dir = - Alcotest.(check int) - errmsg exp - (Dial.to_int (fst (Turner.turn Dial.start 0 dir))) - -let test_turn_left_one () = - test_turn "incorrect result when turning left one step" 49 (Dial.Left 1) - -let test_turn_left_100 () = - test_turn "incorrect result when turning left 100 steps" 50 (Dial.Left 100) - -let test_turn_right_one () = - test_turn "incorrect result when turning right one step" 51 (Dial.Right 1) - -let test_turn_right_100 () = - test_turn "incorrect result when turning right 100 steps" 50 (Dial.Right 100) - -let test_zero_count errmsg exp dir = - Alcotest.(check int) errmsg exp (snd (Turner.turn Dial.start 0 dir)) - -let test_zero_count_right_1000 () = - test_zero_count "zero MUST be counted each time it is passed" 10 - (Dial.Right 1000) - -let () = - let open Alcotest in - run "D01p02" - [ ("initial-state", [test_case "starting state" `Quick test_start]) - ; ( "turning-dial-regression" - , [ test_case "turning left one step" `Quick test_turn_left_one - ; test_case "turning right one step" `Quick test_turn_right_one - ; test_case "turning left 100 steps" `Quick test_turn_left_100 - ; test_case "turning right 100 steps" `Quick test_turn_right_100 ] ) - ; ( "zero-count" - , [ test_case "zero count after turning right 1000 steps" `Quick - test_zero_count_right_1000 ] ) ] diff --git a/2025/ocaml/test/domain/test_d03p01.ml b/2025/ocaml/test/domain/test_d03p01.ml deleted file mode 100644 index 6a2b3cf..0000000 --- a/2025/ocaml/test/domain/test_d03p01.ml +++ /dev/null @@ -1,857 +0,0 @@ -open Domain.D03p01 - -let test_highest_joltage have want () = - Alcotest.(check int) - "highest joltage in battery bank" want - (BatteryBank.of_string have |> BatteryBank.highest_joltage) - -let largest_digit_always_in_highest_joltage = - QCheck2.Test.make ~count:1000 - ~name:"highest joltage always has contains the largest digit" - ~print:(fun s -> s) - QCheck2.Gen.(string_of @@ char_range '1' '9') - (fun have -> - QCheck2.assume (String.length have > 2) ; - let got = BatteryBank.of_string have |> BatteryBank.highest_joltage in - let first = got / 10 and second = got mod 10 in - let want = - String.fold_left - (fun acc chr -> if acc < chr then chr else acc) - '0' have - |> Char.escaped |> int_of_string - in - first = want || second = want ) - -let () = - let open Alcotest in - run "D03p01" - [ ( "example BatteryBank.highest_joltage" - , [ test_case "example line 1" `Quick - (test_highest_joltage "987654321111111" 98) - ; test_case "example line 2" `Quick - (test_highest_joltage "811111111111119" 89) - ; test_case "example line 3" `Quick - (test_highest_joltage "234234234234278" 78) - ; test_case "example line 4" `Quick - (test_highest_joltage "818181911112111" 92) ] ) - ; ( "real BatteryBank.highest_joltage" - , [ test_case "real line 001" `Quick - (test_highest_joltage - "3434845634454364546334335333448443354324533545443235414334477424442444346844344244444434445333344314" - 88 ) - ; test_case "real line 002" `Quick - (test_highest_joltage - "4644333464433434855275434364342435544972433324444444432243354936362863356444434344443333433425344432" - 99 ) - ; test_case "real line 003" `Quick - (test_highest_joltage - "3475855376536553445244645449948436834663673457444335454425154463544745554434526565475244677445573464" - 99 ) - ; test_case "real line 004" `Quick - (test_highest_joltage - "2281212713222421222222213222212222221223122222212125222722232132222251212212322222222722312232333312" - 87 ) - ; test_case "real line 005" `Quick - (test_highest_joltage - "4483335423223456243275328893923136663523559445363393353333233337733323263852953335475336578422333454" - 99 ) - ; test_case "real line 006" `Quick - (test_highest_joltage - "3242323333312323243333333353413314243333232242233323431332332353323333353333333332443515323433322274" - 74 ) - ; test_case "real line 007" `Quick - (test_highest_joltage - "2333332242424335244422323333113323312234133433323243344324444233324245126142444324422445113241224322" - 65 ) - ; test_case "real line 008" `Quick - (test_highest_joltage - "5154432634655462574764324542474445554653643456344345657233718556457665426344257554246426445655364424" - 87 ) - ; test_case "real line 009" `Quick - (test_highest_joltage - "2333128322322632533324333135293331312321323332214333232434433322473232223344341214372234132223223343" - 97 ) - ; test_case "real line 010" `Quick - (test_highest_joltage - "4334244423552542164444513547496574512523344184674374558444434436645643164524326453333335444434455453" - 98 ) - ; test_case "real line 011" `Quick - (test_highest_joltage - "4132222247423242222232632242222124423421222271231244222212222222554221311221262314222222224253442337" - 77 ) - ; test_case "real line 012" `Quick - (test_highest_joltage - "2224322221122233225272222222442223222222271115422222121223262743222224222322422224123122262323222821" - 82 ) - ; test_case "real line 013" `Quick - (test_highest_joltage - "6723822256923221233223334322457233343332383332523433343323228646232524535432233323314353333227243323" - 98 ) - ; test_case "real line 014" `Quick - (test_highest_joltage - "6115546238253222343552462122324244122555232412242231255242342542222232432524132213523232121212324545" - 86 ) - ; test_case "real line 015" `Quick - (test_highest_joltage - "6463573733747321131532311243414374652372276761664731354417577566137677557472577677776125327145332589" - 89 ) - ; test_case "real line 016" `Quick - (test_highest_joltage - "3754415413732333338423242374334432153868351274433435343563444545253375644356474264434423432633233432" - 88 ) - ; test_case "real line 017" `Quick - (test_highest_joltage - "5444434434444444443644344444454454451442423444454444444344443433464444435453445443443354555334454454" - 66 ) - ; test_case "real line 018" `Quick - (test_highest_joltage - "2322232322322342121231422323212136222323332232223332423222231222323222323213211222124112122223323222" - 64 ) - ; test_case "real line 019" `Quick - (test_highest_joltage - "2123222121233313232324221212131322222312114122122222223331524242222223522421122132314232232221325212" - 55 ) - ; test_case "real line 020" `Quick - (test_highest_joltage - "4395244125441654554734234542525455274423553314354552543244444237453434822334223553453354325435235232" - 98 ) - ; test_case "real line 021" `Quick - (test_highest_joltage - "2253424214422444223313135223232832334333553541324423242333333332422343235234313221222522523223223324" - 85 ) - ; test_case "real line 022" `Quick - (test_highest_joltage - "5235226542352363765362222242266353124241222253326335131251514362226422434463674353246334124656282122" - 82 ) - ; test_case "real line 023" `Quick - (test_highest_joltage - "2232123624442243412224246433221223412221211333362434222314122321222374432142634122244234226424224222" - 76 ) - ; test_case "real line 024" `Quick - (test_highest_joltage - "3422335122727323322362432313322322322223332332223816131326233213327243223323343212331236353226232321" - 87 ) - ; test_case "real line 025" `Quick - (test_highest_joltage - "4331335233332333423433313313173432431333333532233333433343363343333332336433333237323332253433443333" - 77 ) - ; test_case "real line 026" `Quick - (test_highest_joltage - "4447448444443444434444443243543534134744452133424233431341466844433346243413344384231154343384832444" - 88 ) - ; test_case "real line 027" `Quick - (test_highest_joltage - "2425565264165457455876521452237584855214342125728464762555858525255422733375855552537425835156285135" - 88 ) - ; test_case "real line 028" `Quick - (test_highest_joltage - "1331226211234222222312211322123232233513242526221423223222232214322223122232333222282842423352327112" - 88 ) - ; test_case "real line 029" `Quick - (test_highest_joltage - "3223434225632352425252225227722722534655252243423421222422525141365526221342142264132653472224125223" - 77 ) - ; test_case "real line 030" `Quick - (test_highest_joltage - "6737524344344356458523434443443144444264443348734644444644541613334445654733443544253434843553348938" - 98 ) - ; test_case "real line 031" `Quick - (test_highest_joltage - "3354422734545554524252254222935229552422513243152263456254352212241422142433745251122447413225375454" - 99 ) - ; test_case "real line 032" `Quick - (test_highest_joltage - "2264222763222232222612274362233123232631142294332533623322433223214212132333126222163271553323113546" - 97 ) - ; test_case "real line 033" `Quick - (test_highest_joltage - "3224455231554212335327234452322334222224355323335331221333422324235123435122325533513215236323223567" - 77 ) - ; test_case "real line 034" `Quick - (test_highest_joltage - "3364433535332344433338434131353453131351342344343443434434354345544614344346424444464434454542463163" - 86 ) - ; test_case "real line 035" `Quick - (test_highest_joltage - "3334311333353143563633334222343222333252653351532534233523263233265333333263352353553235353353353333" - 66 ) - ; test_case "real line 036" `Quick - (test_highest_joltage - "2341414245234322423322241415434452323345544444442373441644233534541453435433235452441251543332443224" - 76 ) - ; test_case "real line 037" `Quick - (test_highest_joltage - "2334222122362114522212222223212342234225232414234232252812322221321123236322322233153222246262451321" - 86 ) - ; test_case "real line 038" `Quick - (test_highest_joltage - "3222213324143222521232511323442333353115354312453322323233245125121212335352213422325232423442113222" - 55 ) - ; test_case "real line 039" `Quick - (test_highest_joltage - "4242234421454245624314342344231425552454131112356244265233623318251442244258454232422222544212344274" - 88 ) - ; test_case "real line 040" `Quick - (test_highest_joltage - "1212211622212622232222232221122222234222325251233221113223526233333222121362242225251522222122222412" - 66 ) - ; test_case "real line 041" `Quick - (test_highest_joltage - "1224243142354932247624132223442221243261122146133223131475431216123424343342231246215132422542144273" - 97 ) - ; test_case "real line 042" `Quick - (test_highest_joltage - "2492338342383524333242442252453454334643633243343233244463532534334332436821235243322432152236224423" - 98 ) - ; test_case "real line 043" `Quick - (test_highest_joltage - "7676556546555455945548897555565858677556364454655765687644455555553584455358535855555564435524555636" - 99 ) - ; test_case "real line 044" `Quick - (test_highest_joltage - "4325352353133335234532433545335543523225232353214622764538523233325246333312335343542333525343352832" - 88 ) - ; test_case "real line 045" `Quick - (test_highest_joltage - "3645465557845534765556456438425473478555654328555485735454543757533734857854555473533459558165456246" - 98 ) - ; test_case "real line 046" `Quick - (test_highest_joltage - "4112321222222221222222222212221112212222222213112212233222211122121222223233221122122112222222312222" - 43 ) - ; test_case "real line 047" `Quick - (test_highest_joltage - "3443534216134325237234252533391415524526262523352143237242332332332422332332347132554552354213163423" - 97 ) - ; test_case "real line 048" `Quick - (test_highest_joltage - "3233233443433352233334353335313474353433313433333332332445324732323333325354333345123333238333323343" - 84 ) - ; test_case "real line 049" `Quick - (test_highest_joltage - "2133222332312231142332122412332211233373432222221234222233216312322233333313213322233312422332412321" - 76 ) - ; test_case "real line 050" `Quick - (test_highest_joltage - "2323442214442251652225322254455617413464434422211515255422722166366711262534452258439143226431422432" - 96 ) - ; test_case "real line 051" `Quick - (test_highest_joltage - "3112243242223312224423222512422243342333412424122234223224524214241424224424422232231222421343123313" - 55 ) - ; test_case "real line 052" `Quick - (test_highest_joltage - "2524625652353456554546431455532426742414535225832464326233444242524314463569626432264234665412452611" - 96 ) - ; test_case "real line 053" `Quick - (test_highest_joltage - "2623224224152132332532122544116272213711472512222547253263227634222862855322152332253264721222222626" - 88 ) - ; test_case "real line 054" `Quick - (test_highest_joltage - "3413862324452342446454316341334346457442436472446424131344156965335463455345446483132552535546336316" - 98 ) - ; test_case "real line 055" `Quick - (test_highest_joltage - "5354344333214443434343442334124434322442443544435334333334544442334442344432244334534423233345433234" - 55 ) - ; test_case "real line 056" `Quick - (test_highest_joltage - "2322333211233223212213111213123244423222222232323332222322212221323323433322435233232371222222214231" - 74 ) - ; test_case "real line 057" `Quick - (test_highest_joltage - "9453214484824262259522856649224244244222211592233334238214222322284346591381262187242644522629832223" - 99 ) - ; test_case "real line 058" `Quick - (test_highest_joltage - "1121224242322123123163448337222562512232262643222323252252223543512425222422222223142233221426365461" - 87 ) - ; test_case "real line 059" `Quick - (test_highest_joltage - "1815612518244643515355768361121163421324442178622168752374328877665672243587235441472714316317588369" - 89 ) - ; test_case "real line 060" `Quick - (test_highest_joltage - "3655264263765432665494374452246362724532226675513545216322644525426252338564464426261732379225267837" - 99 ) - ; test_case "real line 061" `Quick - (test_highest_joltage - "8334743433235341445622143424833423324734232433333132424333763435142253533364333734887344542326984333" - 98 ) - ; test_case "real line 062" `Quick - (test_highest_joltage - "6333532332533333262332333332623323523436233333331335433333433323333133233344333378336333352223333232" - 86 ) - ; test_case "real line 063" `Quick - (test_highest_joltage - "3366253633632333372333133255934725232323263352222274223213323533141234362324236915143323333136434233" - 99 ) - ; test_case "real line 064" `Quick - (test_highest_joltage - "5322222323283352224135234432224523225232853242442423241544234623253324537257221444443224442542343223" - 88 ) - ; test_case "real line 065" `Quick - (test_highest_joltage - "2322722344168237322333228462433332227336523244323265343321333443434132333343333492223332435724342464" - 97 ) - ; test_case "real line 066" `Quick - (test_highest_joltage - "5554845434744255655687844665866661355767464556478385448756657654567285577634695565556616935646635793" - 99 ) - ; test_case "real line 067" `Quick - (test_highest_joltage - "6212623112233212232222321423231243253343231323432932323232222135322122237222134283242231231725212252" - 98 ) - ; test_case "real line 068" `Quick - (test_highest_joltage - "3224333333132232431414376222443332422422322223342332333335333224256832733233433622542324333442322953" - 95 ) - ; test_case "real line 069" `Quick - (test_highest_joltage - "1495248962734145243214425252551493515624456535246426262254542145634343245522513375341266233457656414" - 99 ) - ; test_case "real line 070" `Quick - (test_highest_joltage - "3333243514122577341633436443431333234233434851333432454335423136533335534834354554332443233443334233" - 88 ) - ; test_case "real line 071" `Quick - (test_highest_joltage - "6775435378454755775937393515454694792745716493599675572454465734647574159354586497336464643987566774" - 99 ) - ; test_case "real line 072" `Quick - (test_highest_joltage - "3343255423334231444544444344434343234333434343544344434524254354244544424452234444333442244454345533" - 55 ) - ; test_case "real line 073" `Quick - (test_highest_joltage - "5644753727566756775567545585754676596645654664776746756665476666666556676466474667695655766564656657" - 99 ) - ; test_case "real line 074" `Quick - (test_highest_joltage - "4644226464352276354314244224452454342423454622134648332554423425336244425432349642233521244367349342" - 99 ) - ; test_case "real line 075" `Quick - (test_highest_joltage - "3433335543445753633424571853934424332643345643544683232351242335433242324425464344622364532352533437" - 98 ) - ; test_case "real line 076" `Quick - (test_highest_joltage - "4332333343143333333343333474334265443333334214354551322433433243313234333232425433333443443433526343" - 76 ) - ; test_case "real line 077" `Quick - (test_highest_joltage - "4353646245233554434324342343533543343355335434535413535233331345445333253655446433354372556332324562" - 76 ) - ; test_case "real line 078" `Quick - (test_highest_joltage - "4325222211212322234523222222252532247122313123373542232242121352322276216822338182262236346922722222" - 97 ) - ; test_case "real line 079" `Quick - (test_highest_joltage - "2322565523552147152522424415733545252325565263526635227432457357155822584542343524555344525552453543" - 88 ) - ; test_case "real line 080" `Quick - (test_highest_joltage - "2536964744575897532331584947348473443439336365524543425393413335486443645364675453424543335446555498" - 99 ) - ; test_case "real line 081" `Quick - (test_highest_joltage - "2222213441222221121213232422422313244252223442212222212522422224224223231124422422243512212213212342" - 55 ) - ; test_case "real line 082" `Quick - (test_highest_joltage - "2226345127525427354274576964233625784361152623467725726752473254364464625478727754525322265247112673" - 98 ) - ; test_case "real line 083" `Quick - (test_highest_joltage - "3333323323439434233332343233242333523333333322324323333432333327112333312433423132233322331432333323" - 97 ) - ; test_case "real line 084" `Quick - (test_highest_joltage - "3333434534333424243331497446456437324756447475342274464232844334833254353445422233546141144533344344" - 98 ) - ; test_case "real line 085" `Quick - (test_highest_joltage - "3696167353668222145864766623627542566388745676275342437244534257633357663535464676463344222466874743" - 98 ) - ; test_case "real line 086" `Quick - (test_highest_joltage - "2221243212226232221214225213222212321222322222222222321222213152222212232613223422294232232522123112" - 95 ) - ; test_case "real line 087" `Quick - (test_highest_joltage - "2322223332222333222222322222224233422221212122124224222442422313242232322322322132221232323232224243" - 44 ) - ; test_case "real line 088" `Quick - (test_highest_joltage - "3222473241422323343212233253252455323361331212425233323243431532523435223231512332242372313333353233" - 77 ) - ; test_case "real line 089" `Quick - (test_highest_joltage - "5423224242232312223222322332344353122123124433226352334261254221453422322421343522321125621432243323" - 66 ) - ; test_case "real line 090" `Quick - (test_highest_joltage - "8363335374334533365422233335351363332233354423323122333333234434223223333335635423442344353624352436" - 87 ) - ; test_case "real line 091" `Quick - (test_highest_joltage - "3363445765667922559186654477737547964555446686459554733657654766566655765548166378754867769354765564" - 99 ) - ; test_case "real line 092" `Quick - (test_highest_joltage - "6533212432223424334451322232232232443253324232723281475224244243554253222475635635422425222222222428" - 88 ) - ; test_case "real line 093" `Quick - (test_highest_joltage - "1223233332437322323134322352329283423333133433331533223722733221342425515333543413253332323254332144" - 98 ) - ; test_case "real line 094" `Quick - (test_highest_joltage - "5264162233623746665261784343112243254221441535263624414528422142852361426326676128424382222772232624" - 88 ) - ; test_case "real line 095" `Quick - (test_highest_joltage - "1222232222322322212232232122232322222331222223323232222322212223221222223122132222222226222311212222" - 63 ) - ; test_case "real line 096" `Quick - (test_highest_joltage - "2447938353242561333434522543524366633338233372322372443172638272362414623239322222214322338432223233" - 99 ) - ; test_case "real line 097" `Quick - (test_highest_joltage - "4884849914558496499986874887474387879218486546454756422444585589669397378725447645287563673434787636" - 99 ) - ; test_case "real line 098" `Quick - (test_highest_joltage - "1532312355432455353342333225253523373245346353222322243344325423634342343333235353462335235134532214" - 76 ) - ; test_case "real line 099" `Quick - (test_highest_joltage - "3633115526253432364354661645232316365446622332424352521423265511535341213222665264163264143532241789" - 89 ) - ; test_case "real line 100" `Quick - (test_highest_joltage - "1333723464325672225363386433359834138466234942326568553333727332463624757374234473543533336465426327" - 99 ) - ; test_case "real line 101" `Quick - (test_highest_joltage - "8325733945242354443883554334669457355644376375344222347347435474443336786843454755374733473222713744" - 99 ) - ; test_case "real line 102" `Quick - (test_highest_joltage - "3836536232345525523424516573323367655557875645136254455455455555237343456565656555565433564652375615" - 88 ) - ; test_case "real line 103" `Quick - (test_highest_joltage - "1222242222222222222221222222222222222222222222214222242212122221223267222222112122222222222221223723" - 77 ) - ; test_case "real line 104" `Quick - (test_highest_joltage - "2122322223222221341225222331152222221222222221222222222212223231322221121224122222222221222322221221" - 55 ) - ; test_case "real line 105" `Quick - (test_highest_joltage - "2623431563346525416242365622564363427642123425242364433596524126242622114536213245235652674623263514" - 97 ) - ; test_case "real line 106" `Quick - (test_highest_joltage - "2232456232323314322243433252432326454256232632225662224221422412232162235555244527334626356269224262" - 96 ) - ; test_case "real line 107" `Quick - (test_highest_joltage - "2223224223121316212222123214122222321222232222214222223322235255232321232421222233223123422332226121" - 66 ) - ; test_case "real line 108" `Quick - (test_highest_joltage - "2232222211641215121113421342225332233222241313112621422223224321232326222212352222222222322222232142" - 66 ) - ; test_case "real line 109" `Quick - (test_highest_joltage - "3622224322123323543363333123532223522232121233221223212255624222224222331222232221222223332412252226" - 66 ) - ; test_case "real line 110" `Quick - (test_highest_joltage - "2133111331232311212423441441423422333433421444124423221341431323413244441112422424242124214412256789" - 89 ) - ; test_case "real line 111" `Quick - (test_highest_joltage - "4324434435333333333334332245243563333343356334334344235334322234145353323234352455374364553333643433" - 76 ) - ; test_case "real line 112" `Quick - (test_highest_joltage - "3644734534756433422432434356624552253427466225337526564429422436344444454366324544253633541443353244" - 96 ) - ; test_case "real line 113" `Quick - (test_highest_joltage - "2464324635364431443472344454334444343433464546454374434442423242443446444423526443551333243353834444" - 84 ) - ; test_case "real line 114" `Quick - (test_highest_joltage - "5281642433245222222262522922521227221222224253247163523362225212277164151212258322522132327215231222" - 98 ) - ; test_case "real line 115" `Quick - (test_highest_joltage - "4235325226444533343235413742346432342321545545344233233255443834433325423232343235231352342354622553" - 86 ) - ; test_case "real line 116" `Quick - (test_highest_joltage - "8733361224233225222222426322442381855225483432261538256252524243235345825178218568965722348222331146" - 98 ) - ; test_case "real line 117" `Quick - (test_highest_joltage - "3464344445243346232343434322224234534583235444432447445343449318343349436245884322482543363363441674" - 99 ) - ; test_case "real line 118" `Quick - (test_highest_joltage - "7343334231474233525283342422364511453342252222452242451442434432112333432342315946241522245542222535" - 96 ) - ; test_case "real line 119" `Quick - (test_highest_joltage - "4446143264553864474334554447535535453446566233244444636634565645448343225453554444454544364443454574" - 88 ) - ; test_case "real line 120" `Quick - (test_highest_joltage - "2422842224224342322263252223333244522315113324152212372221342231322244231232122332222112227625141222" - 87 ) - ; test_case "real line 121" `Quick - (test_highest_joltage - "4142122222422221222412122222212544522231222223211322362222321222222134432222122542122262223523223222" - 66 ) - ; test_case "real line 122" `Quick - (test_highest_joltage - "4572333542142432343332342132323444433243433454242321332323354233213344233432132323534221333344323134" - 75 ) - ; test_case "real line 123" `Quick - (test_highest_joltage - "1333235433322333324332333323332332233112331232333422333333333235333334322322333323333333323333423735" - 75 ) - ; test_case "real line 124" `Quick - (test_highest_joltage - "5563657356454636674393696655654548835664676555834625756663576376694963475267652644487768866556366963" - 99 ) - ; test_case "real line 125" `Quick - (test_highest_joltage - "4563325436233211733422223372423424234244425452243133154466242445642433343224441473324372257264523657" - 77 ) - ; test_case "real line 126" `Quick - (test_highest_joltage - "2323232163222222141214222333122231122223522528112323222223222322213223223322333322221323333333322311" - 83 ) - ; test_case "real line 127" `Quick - (test_highest_joltage - "2122222234242122427222222226222332211222112222222211222421121538222231222313221435322311232222212242" - 85 ) - ; test_case "real line 128" `Quick - (test_highest_joltage - "5334253235515121222383422225322212542617232476242645322652351543223434635325223237112254433523422324" - 87 ) - ; test_case "real line 129" `Quick - (test_highest_joltage - "1221223532146222222223126712218222123621122123217221212222322221232621162221122222222222122561122292" - 92 ) - ; test_case "real line 130" `Quick - (test_highest_joltage - "1253352424255222142243514444444457255254363328422562213135526512123451523416645352425253125322532244" - 86 ) - ; test_case "real line 131" `Quick - (test_highest_joltage - "7463373334646423355538441534433248593244576575237356213443653633273543376463332733333713232643554552" - 97 ) - ; test_case "real line 132" `Quick - (test_highest_joltage - "5752323474332773249344434354245224321731245442414442735261654665534764636524293424214244444723343251" - 99 ) - ; test_case "real line 133" `Quick - (test_highest_joltage - "2629438637852334643554943482775533446535956343762325623453474525365936398476244597355454656623443534" - 99 ) - ; test_case "real line 134" `Quick - (test_highest_joltage - "4535534443563253332234233434455543533235333233223442233533235354443333733435443343553354434434423313" - 75 ) - ; test_case "real line 135" `Quick - (test_highest_joltage - "3432223113423243423323513443123332315562232532523473232212343355364222323234236322343323225332327733" - 77 ) - ; test_case "real line 136" `Quick - (test_highest_joltage - "1425245335243124322324153454554115125214321244121112544441331342553344311545421455324424455134156789" - 89 ) - ; test_case "real line 137" `Quick - (test_highest_joltage - "5132222223252224223252622414436422231243232252214255323634524534432233273222325435334426442424422223" - 76 ) - ; test_case "real line 138" `Quick - (test_highest_joltage - "3466444436255433325573352262554776625733275447321246237454234455234514752545552532447415643534223435" - 77 ) - ; test_case "real line 139" `Quick - (test_highest_joltage - "3421133222332332322343231232212233662233221312321122224431322313262333313232332243223222233232344323" - 66 ) - ; test_case "real line 140" `Quick - (test_highest_joltage - "3333523333232364333363555455431345243544566532455235335641243353444455452225735366346344243452243545" - 76 ) - ; test_case "real line 141" `Quick - (test_highest_joltage - "2152354211122622723432615322212523224222225212363121124451232267516325421252122342152252424221452215" - 77 ) - ; test_case "real line 142" `Quick - (test_highest_joltage - "1336123121333343222232333212232411534322333233223332323322234351332222331333333233332312233311232332" - 65 ) - ; test_case "real line 143" `Quick - (test_highest_joltage - "2132121232233134232221223353223332232233322251223526132523323234323223335333222534212113232222523324" - 65 ) - ; test_case "real line 144" `Quick - (test_highest_joltage - "5642633765534226222523623324323443222227261332774392232227443393254412356235358631224418312923222245" - 99 ) - ; test_case "real line 145" `Quick - (test_highest_joltage - "8253442425732222332238822732425826242143236154326323242443342334348324234372622433223442523324133216" - 88 ) - ; test_case "real line 146" `Quick - (test_highest_joltage - "2232271226257223212222421324222344224222141222322222222222222122224238422414316422323222222322127221" - 87 ) - ; test_case "real line 147" `Quick - (test_highest_joltage - "6446315487414253623366432633565653444341454233666544632655534656536436114486263961512465435546514714" - 97 ) - ; test_case "real line 148" `Quick - (test_highest_joltage - "3452383332535514435334232222355452522134243132532453384334525234222233545425333315332323214145535341" - 88 ) - ; test_case "real line 149" `Quick - (test_highest_joltage - "3232222223322222122123232312331213231232227213222242313323222122223322222322313334222122231232221331" - 74 ) - ; test_case "real line 150" `Quick - (test_highest_joltage - "3424332322222132232233231123121313223223263323323222342313211312222322221122422223222112232122222222" - 64 ) - ; test_case "real line 151" `Quick - (test_highest_joltage - "4688454513726334654565476674454433554682654943685644463375763364523442437445697396743545599524274524" - 99 ) - ; test_case "real line 152" `Quick - (test_highest_joltage - "4161223222271222942312171248122235227112222552223422326624121622234732522212222232212366825223253121" - 98 ) - ; test_case "real line 153" `Quick - (test_highest_joltage - "6332465533333522555433324356221543352235533566333253334365565323363324552432332233335334227623136325" - 76 ) - ; test_case "real line 154" `Quick - (test_highest_joltage - "3222431333842318323144411327346223122314332323123555122522333732335233333352312534852241433483632313" - 88 ) - ; test_case "real line 155" `Quick - (test_highest_joltage - "2112121212253222222943424322422222512112212312422234143313232222122122222122123122132137327212242242" - 97 ) - ; test_case "real line 156" `Quick - (test_highest_joltage - "3222532316332422221212223221522223111252221221322212217222213222221221211241222312222121222133222221" - 74 ) - ; test_case "real line 157" `Quick - (test_highest_joltage - "2323332223344321223143245373113722211223122226227822325314633213361321445632321823462822337223222445" - 88 ) - ; test_case "real line 158" `Quick - (test_highest_joltage - "1828425212144411422233221323218468423217466223581342222211242262211332238228121222224225122212222232" - 88 ) - ; test_case "real line 159" `Quick - (test_highest_joltage - "3556334454665634545845243453255635572675157347763125427524341342644935377436635334453443684644784434" - 98 ) - ; test_case "real line 160" `Quick - (test_highest_joltage - "2645662623422533225421215431512343431254242214262232525122522244211222145253265353435325571322222253" - 75 ) - ; test_case "real line 161" `Quick - (test_highest_joltage - "2224222242122222222232211112222314222222222234412242326322512423222222232623322222122322112321211122" - 66 ) - ; test_case "real line 162" `Quick - (test_highest_joltage - "3323122223222222333232562133233231243322232322321321232232332322335422322313322223123222222334233322" - 65 ) - ; test_case "real line 163" `Quick - (test_highest_joltage - "3221132223233221322222223221222241223222221222212214223422121422222452222224223222122124212323123221" - 54 ) - ; test_case "real line 164" `Quick - (test_highest_joltage - "2221442252281322222422127282212412222622221232731222223242124222212112212222241262321442522221122252" - 88 ) - ; test_case "real line 165" `Quick - (test_highest_joltage - "2133231122331441531433375353572447432322146333423223632556232232334447444433622228242422332343332434" - 84 ) - ; test_case "real line 166" `Quick - (test_highest_joltage - "5563624513351533334437333324353335333533533355223433335233342353363556423235334233344323342524634321" - 76 ) - ; test_case "real line 167" `Quick - (test_highest_joltage - "2332432233323221431231323223332233383122222132424421134132233232323333433435254432332222332323313232" - 85 ) - ; test_case "real line 168" `Quick - (test_highest_joltage - "2423222822122222222412282231122429224143222242222222223223436242424223242432142221433121142143522227" - 97 ) - ; test_case "real line 169" `Quick - (test_highest_joltage - "1368838843358743334345324211363655364233733332443334321243322336412538532344213423253222366323263695" - 95 ) - ; test_case "real line 170" `Quick - (test_highest_joltage - "4744137463363333333332326343314643324446233366376545372648324744635334435237267661327145553246452321" - 87 ) - ; test_case "real line 171" `Quick - (test_highest_joltage - "3831332122233231223233222433232333233133133242242331362323323332235244434223336362233433352322334423" - 86 ) - ; test_case "real line 172" `Quick - (test_highest_joltage - "3214422223143421344222143132231445423222522643233323314323424344434731323334224323324533352334332442" - 75 ) - ; test_case "real line 173" `Quick - (test_highest_joltage - "3543312373344332233323452214433346347334844253532442224222234312433329254329453434283232534223272192" - 99 ) - ; test_case "real line 174" `Quick - (test_highest_joltage - "3433383314338373834351844233646343475553142224453454325446354334653275541333543135354465333263445242" - 88 ) - ; test_case "real line 175" `Quick - (test_highest_joltage - "4432372233684255322525272434647435924424322624541334413224443634326336461361436414475721222543446321" - 97 ) - ; test_case "real line 176" `Quick - (test_highest_joltage - "5433335293334525543224371764333434988333237353113448943411932743514583594643353323325387583814645347" - 99 ) - ; test_case "real line 177" `Quick - (test_highest_joltage - "5444424957443468654656364755567468455486545344445776761776566466566644555557658947447467449455766646" - 99 ) - ; test_case "real line 178" `Quick - (test_highest_joltage - "2523333233343235435222235534333238434254233255342853335554453253236223223533323533522334332433432543" - 88 ) - ; test_case "real line 179" `Quick - (test_highest_joltage - "1138614552342221321221222413621222223231223222323222814623241222446214226442469244351324343452225242" - 95 ) - ; test_case "real line 180" `Quick - (test_highest_joltage - "3235222332131222222123223222332312322323172132141232121324222213353223224233213123222221223322233343" - 75 ) - ; test_case "real line 181" `Quick - (test_highest_joltage - "4255452362323165242342332223332343343253334456143314452223324924424332355355353341343334425553353324" - 95 ) - ; test_case "real line 182" `Quick - (test_highest_joltage - "3544633234326333143434434638432333354444353822424442333225332444532646543132326565233146643454534545" - 88 ) - ; test_case "real line 183" `Quick - (test_highest_joltage - "5533866835333332446339845544234435374366623344443448343635433373544333343634473633534344325836364423" - 98 ) - ; test_case "real line 184" `Quick - (test_highest_joltage - "2125222512312522233222222532263455412421222232122112222113222512244225244211543622122111124332312445" - 66 ) - ; test_case "real line 185" `Quick - (test_highest_joltage - "5765537453753546524944644454354364643373535664333265356565454434334362334636443764473516535345363344" - 97 ) - ; test_case "real line 186" `Quick - (test_highest_joltage - "1242333313323541312121335173323322252262322222223242223232232232334313232372223332333223325322223324" - 77 ) - ; test_case "real line 187" `Quick - (test_highest_joltage - "4945346555344725534444354865365546455455453445376994344537749854443434373543557699323594547559554353" - 99 ) - ; test_case "real line 188" `Quick - (test_highest_joltage - "8441321232536757468212413223835223356252342241323333442736423243742453922327334232628243572272153736" - 98 ) - ; test_case "real line 189" `Quick - (test_highest_joltage - "3233227533534655473331231323541242222614244236242111121212233442265222232434532624232622312564515622" - 77 ) - ; test_case "real line 190" `Quick - (test_highest_joltage - "5231443534475571334243142383154324332422432224554332949237335344432225546623266434742645424452245374" - 99 ) - ; test_case "real line 191" `Quick - (test_highest_joltage - "2222652752542951212254142576134913223231244514325577152312242364152242322223322323734226324612251433" - 99 ) - ; test_case "real line 192" `Quick - (test_highest_joltage - "3344232272222124265242432232223224453423221222354325223634222122333255463333115233242332132134223228" - 78 ) - ; test_case "real line 193" `Quick - (test_highest_joltage - "2423211646221222214223621414422342222242214622426224222474221222222215322142322234542433223122532751" - 77 ) - ; test_case "real line 194" `Quick - (test_highest_joltage - "1222432222227322222223122113222321223327322223241223322232332322222222222222123323434222222223412222" - 77 ) - ; test_case "real line 195" `Quick - (test_highest_joltage - "2231222262245222221524294251251222212942222142532633726213522122212422222232121922212212262322231242" - 99 ) - ; test_case "real line 196" `Quick - (test_highest_joltage - "3436674387463373644336847545353373813336332865333426743634647353343363237627393333883956336933349336" - 99 ) - ; test_case "real line 197" `Quick - (test_highest_joltage - "4234664334533643445662346466345666647426655444553533366634453846446264346586345365444765364634443146" - 88 ) - ; test_case "real line 198" `Quick - (test_highest_joltage - "4141324433423632324233334423343324244241332653544345362343464242132445242521322431444734316223541332" - 76 ) - ; test_case "real line 199" `Quick - (test_highest_joltage - "3535726262552245152262222275133232659846242165522221422176222262567254622235412424626225322313236476" - 98 ) - ; test_case "real line 200" `Quick - (test_highest_joltage - "2222222333136722224325232232111133222432346324431421212343222142244331223225124653223222322121232213" - 76 ) ] ) - ; ( "BatteryBank.highest_joltage regression" - , [ test_case - "second digit promoted when same digit found and greater than \ - first digit" - `Quick - (test_highest_joltage "343400" 44) - ; test_case "second last digit skipped" `Quick - (test_highest_joltage "789" 89) - ; test_case "I have no idea why this is wrong yet" `Quick - (test_highest_joltage "565" 65) - ; test_case "I have no idea why this is right yet" `Quick - (test_highest_joltage "566" 66) - ; test_case "I have no idea why this is right yet" `Quick - (test_highest_joltage "06760" 76) - ; test_case "I have no idea why this is wrong yet" `Quick - (test_highest_joltage "0067600" 76) ] ) - ; ( "BatteryBank property tests" - , List.map - (QCheck_alcotest.to_alcotest ~colors:true) - [largest_digit_always_in_highest_joltage] ) ] diff --git a/2025/ocaml/test/infrastructure/dune b/2025/ocaml/test/infrastructure/dune deleted file mode 100644 index 5d493d9..0000000 --- a/2025/ocaml/test/infrastructure/dune +++ /dev/null @@ -1,8 +0,0 @@ -(tests - (names - test_example) - (libraries - application - domain - infrastructure - alcotest)) diff --git a/2025/ocaml/test/infrastructure/test_example.ml b/2025/ocaml/test/infrastructure/test_example.ml deleted file mode 100644 index e69de29..0000000 diff --git a/2025/ocaml/test/presentation/dune b/2025/ocaml/test/presentation/dune deleted file mode 100644 index e34e56f..0000000 --- a/2025/ocaml/test/presentation/dune +++ /dev/null @@ -1,11 +0,0 @@ -(tests - (names - test_example) - (libraries - application - domain - infrastructure - presentation - alcotest - qcheck-core - qcheck-alcotest)) diff --git a/2025/ocaml/test/presentation/test_example.ml b/2025/ocaml/test/presentation/test_example.ml deleted file mode 100644 index e69de29..0000000 diff --git a/Taskfile.yml b/Taskfile.yml index d21aae2..1628de5 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -19,9 +19,13 @@ tasks: - DAY vars: FORMATTED_DAY: '{{if (lt (atoi .DAY) 10)}}0{{.DAY}}{{else}}{{.DAY}}{{end}}' - URI: https://adventofcode.com/{{ .YEAR }}/day/{{.DAY}}/input - INPUT_DIR: '{{.YEAR}}/_input' + URI: https://adventofcode.com/{{.YEAR}}/day/{{.DAY}}/input + INPUT_DIR: '/.input/{{.YEAR}}' cmds: - cmd: mkdir --parents {{.INPUT_DIR}} - - cmd: curl --cookie 'session={{.SESSION}}' {{.URI}} > {{.INPUT_DIR}}/day_{{.FORMATTED_DAY}}.txt + - cmd: curl --cookie 'session={{.SESSION}}' {{.URI}} > {{.INPUT_DIR}}/{{.FORMATTED_DAY}}.txt silent: true + + git:hooks: + desc: sets up the githooks for the project + cmd: git config core.hooksPath .githooks diff --git a/go/2023/.envrc b/go/2023/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/go/2023/.envrc @@ -0,0 +1 @@ +use flake diff --git a/go/2023/.gitignore b/go/2023/.gitignore new file mode 100644 index 0000000..9f4cb5b --- /dev/null +++ b/go/2023/.gitignore @@ -0,0 +1,21 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +out/ + +# Go workspace file +go.work +go.work.sum diff --git a/go/2023/README.md b/go/2023/README.md new file mode 100644 index 0000000..9cda93b --- /dev/null +++ b/go/2023/README.md @@ -0,0 +1,21 @@ +# Go 2023 + +## Getting Started + +### How to Build + +```sh +task build +``` + +### How to Run + +```sh +task run +``` + +### How to Test + +```sh +task test +``` diff --git a/go/2023/Taskfile.yml b/go/2023/Taskfile.yml new file mode 100644 index 0000000..a07c24c --- /dev/null +++ b/go/2023/Taskfile.yml @@ -0,0 +1,23 @@ +# https://taskfile.dev + +version: "3" +tasks: + default: + desc: Prints this message + cmds: + - cmd: task --list-all + build: + cmds: + - cmd: go build -o=out/Advent_of_Code_2023 main.go + run: + cmds: + - cmd: go run main.go {{.CLI_ARGS}} + test: + cmds: + - cmd: go test ./... + test:coverage: + desc: Runs tests and produce coverage report + cmds: + - cmd: mkdir --parents out + - cmd: go test -coverprofile=out/coverage.out ./... + - cmd: go tool cover -html=out/coverage.out diff --git a/go/2023/flake.lock b/go/2023/flake.lock new file mode 100644 index 0000000..b7695d9 --- /dev/null +++ b/go/2023/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1741010256, + "narHash": "sha256-WZNlK/KX7Sni0RyqLSqLPbK8k08Kq7H7RijPJbq9KHM=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "ba487dbc9d04e0634c64e3b1f0d25839a0a68246", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/go/2023/flake.nix b/go/2023/flake.nix new file mode 100644 index 0000000..39dda89 --- /dev/null +++ b/go/2023/flake.nix @@ -0,0 +1,28 @@ +{ + description = "Advent of Code 2023: Go"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + + outputs = + { nixpkgs, ... }: + let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + in + { + devShells.x86_64-linux.default = pkgs.mkShell { + # build tools + nativeBuildInputs = with pkgs; [ + go + ]; + # dependencies + buildInputs = with pkgs; [ + delve + gopls + gotools + golangci-lint + ]; + }; + }; +} diff --git a/go/2023/go.mod b/go/2023/go.mod new file mode 100644 index 0000000..9fba23c --- /dev/null +++ b/go/2023/go.mod @@ -0,0 +1,11 @@ +module github.com/DJOLEARY/Advent_of_Code/2023/Go + +go 1.22.3 + +require github.com/outrigdev/outrig v0.8.2 + +require ( + github.com/google/uuid v1.6.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 // indirect +) diff --git a/go/2023/go.sum b/go/2023/go.sum new file mode 100644 index 0000000..3c60b26 --- /dev/null +++ b/go/2023/go.sum @@ -0,0 +1,8 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/outrig v0.8.2 h1:+qR+N6BXvJmFXsHmksaAJk4sgItxHLbWXN+9K8W2UVM= +github.com/outrigdev/outrig v0.8.2/go.mod h1:8ULH17Fc55s9zkf6S2ndQ43UoiJ0j28t9A0lwjs7rQc= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= diff --git a/go/2023/internal/day_01/day_01.go b/go/2023/internal/day_01/day_01.go new file mode 100644 index 0000000..9cd0a49 --- /dev/null +++ b/go/2023/internal/day_01/day_01.go @@ -0,0 +1,114 @@ +package day_01 + +import ( + "errors" + "fmt" + "os" + "strconv" + "strings" +) + +func Solve() { + filename := "../_input/day_01.txt" + content := readFile(filename) + + numbers := []int{} + + lines := strings.Split(content, "\n") + for _, line := range lines { + first_num, last_num, err := findNumbers(line) + if err != nil { + continue + } + + num_str := first_num + last_num + num := strToInt(num_str) + numbers = append(numbers, num) + } + + acc := accumulate(numbers) + + fmt.Println(acc) +} + +func readFile(filename string) string { + content, err := os.ReadFile(filename) + if err != nil { + panic(err) + } + + return string(content) +} + +var num_map = map[string]string{ + "zero": "0", + "0": "0", + "one": "1", + "1": "1", + "two": "2", + "2": "2", + "three": "3", + "3": "3", + "four": "4", + "4": "4", + "five": "5", + "5": "5", + "six": "6", + "6": "6", + "seven": "7", + "7": "7", + "eight": "8", + "8": "8", + "nine": "9", + "9": "9", +} + +func findNumbers(line string) (string, string, error) { + if line == "" || line == "\n" { + return "", "", errors.New("Empty line") + } + + found_numbers := []string{} + window_size := 5 + for i := range line { + window_end := i + window_size + if window_end > len(line) { + window_end = len(line) + } + sub_str := line[i:window_end] + + for key, value := range num_map { + if strings.HasPrefix(sub_str, key) { + found_numbers = append(found_numbers, value) + } + } + } + + if len(found_numbers) == 0 { + return "", "", errors.New("No numbers found") + } + + first := found_numbers[0] + last := found_numbers[len(found_numbers)-1] + + return first, last, nil +} + +func accumulate(numbers []int) int { + acc := 0 + + for _, num := range numbers { + acc += num + } + + return acc +} + +func strToInt(str string) int { + num, err := strconv.ParseInt(str, 0, 0) + if err != nil { + panic(err) + } + + return int(num) +} diff --git a/go/2023/internal/day_01/day_01_test.go b/go/2023/internal/day_01/day_01_test.go new file mode 100644 index 0000000..8063619 --- /dev/null +++ b/go/2023/internal/day_01/day_01_test.go @@ -0,0 +1,31 @@ +package day_01 + +import ( + "testing" +) + +type input string +type answer string + +func TestFindNumbers(t *testing.T) { + data := map[input]answer{ + "1abc2": "12", + "pqr3stu8vwx": "38", + "a1b2c3d4e5f": "15", + "treb7uchet": "77", + } + + for input, expected := range data { + actual_first, actual_last, err := findNumbers(string(input)) + + if err != nil { + t.Error("find_numbers returned an error") + } + + actual := actual_first + actual_last + + if actual != string(expected) { + t.Errorf("Expected %s, got %s", expected, actual) + } + } +} diff --git a/go/2023/internal/day_02/part_01/part_01.go b/go/2023/internal/day_02/part_01/part_01.go new file mode 100644 index 0000000..9efe20f --- /dev/null +++ b/go/2023/internal/day_02/part_01/part_01.go @@ -0,0 +1,138 @@ +package part_01 + +import ( + "os" + "regexp" + "strconv" + "strings" +) + +type cubes struct { + red int + blue int + green int +} + +func Solve() { + contents, err := os.ReadFile("../_input/day_02.txt") + if err != nil { + panic(err) + } + + lines := strings.Split(string(contents), "\n") + possibleGameIds := processLines(lines) + + println(accumulate(possibleGameIds)) +} + +func processLines(lines []string) []int { + var possibleGames []int + + maxCubes := cubes{red: 12, blue: 14, green: 13} + + for _, line := range lines { + if line == "" || line == "\n" { + continue + } + + shownCubes := convertLineToCubes(line) + + isPossible := isPossibleGame(shownCubes, maxCubes) + if isPossible { + gameId := getGameId(line) + possibleGames = append(possibleGames, gameId) + } + } + + return possibleGames +} + +func convertLineToCubes(line string) []cubes { + var shownCubes []cubes + + redRegex := regexp.MustCompile(`(\d+) red`) + blueRegex := regexp.MustCompile(`(\d+) blue`) + greenRegex := regexp.MustCompile(`(\d+) green`) + + for _, round := range strings.Split(line, ";") { + var cubes cubes + + redMatches := redRegex.FindStringSubmatch(round) + if redMatches != nil { + red, err := strconv.Atoi(redMatches[1]) + if err != nil { + panic("Could not convert red to int: " + redMatches[1]) + } else { + cubes.red = red + } + } + + blueMatches := blueRegex.FindStringSubmatch(round) + if blueMatches != nil { + blue, err := strconv.Atoi(blueMatches[1]) + if err != nil { + panic("Could not convert green to int: " + blueMatches[1]) + } else { + cubes.blue = blue + } + } + + greenMatches := greenRegex.FindStringSubmatch(round) + if greenMatches != nil { + green, err := strconv.Atoi(greenMatches[1]) + if err != nil { + panic("Could not convert green to int: " + greenMatches[1]) + } else { + cubes.green = green + } + } + + shownCubes = append(shownCubes, cubes) + } + + return shownCubes +} + +func isPossibleGame(shownCubes []cubes, maxCubes cubes) bool { + for _, cubes := range shownCubes { + if cubes.red > maxCubes.red { + return false + } + + if cubes.blue > maxCubes.blue { + return false + } + + if cubes.green > maxCubes.green { + return false + } + } + + return true +} + +func getGameId(line string) int { + regex := regexp.MustCompile(`^Game (\d+):`) + matches := regex.FindStringSubmatch(line) + if matches == nil || len(matches) < 2 { + println(matches) + panic("No game id found in line: '" + line + "'") + } + + gameId, err := strconv.Atoi(matches[1]) + if err != nil { + panic("Could not convert game id to int: " + matches[1]) + } + + return gameId +} + +func accumulate(gameIds []int) int { + var acc int + + for _, gameId := range gameIds { + acc += gameId + } + + return acc +} diff --git a/go/2023/internal/day_02/part_01/part_01_test.go b/go/2023/internal/day_02/part_01/part_01_test.go new file mode 100644 index 0000000..33e5fbf --- /dev/null +++ b/go/2023/internal/day_02/part_01/part_01_test.go @@ -0,0 +1,102 @@ +package part_01 + +import ( + "testing" +) + +type data struct { + gameId int + isPossible bool + shownCubes []cubes + line string +} + +var testInput = []data{ + { + gameId: 1, + isPossible: true, + shownCubes: []cubes{ + {red: 4, blue: 3}, + {red: 1, blue: 6, green: 2}, + {green: 2}, + }, + line: "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green", + }, + { + gameId: 2, + isPossible: true, + shownCubes: []cubes{ + {blue: 1, green: 2}, + {red: 1, blue: 4, green: 3}, + {blue: 1, green: 1}, + }, + line: "Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue", + }, + { + gameId: 3, + isPossible: false, + shownCubes: []cubes{ + {red: 20, blue: 6, green: 8}, + {red: 4, blue: 5, green: 13}, + {red: 1, green: 5}, + }, + line: "Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red", + }, + { + gameId: 4, + isPossible: false, + shownCubes: []cubes{ + {red: 3, blue: 6, green: 1}, + {red: 6, green: 3}, + {red: 14, blue: 15, green: 3}, + }, + line: "Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red", + }, + { + gameId: 5, + isPossible: true, + shownCubes: []cubes{ + {red: 6, blue: 1, green: 3}, + {red: 1, blue: 2, green: 2}, + }, + line: "Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green", + }, +} + +func TestConvertLineToCubes(t *testing.T) { + for _, data := range testInput { + actualCubes := convertLineToCubes(data.line) + + if len(data.shownCubes) != len(actualCubes) { + t.Errorf("Expected length of shownCubes to be %d, got %d", len(data.shownCubes), len(actualCubes)) + } + + for i, cubes := range data.shownCubes { + if cubes != actualCubes[i] { + t.Errorf("Expected shownCubes to be %v, got %v", cubes, actualCubes[i]) + } + } + } +} + +func TestIsPossibleGame(t *testing.T) { + maxCubes := cubes{red: 12, blue: 13, green: 14} + + for _, data := range testInput { + actualIsPossible := isPossibleGame(data.shownCubes, maxCubes) + + if data.isPossible != actualIsPossible { + t.Errorf("Expected isPossibleGame to be %t, got %t for game id %d", data.isPossible, actualIsPossible, data.gameId) + } + } +} + +func TestGetGameId(t *testing.T) { + for _, data := range testInput { + actualGameId := getGameId(data.line) + + if data.gameId != actualGameId { + t.Errorf("Expected gameId to be %d, got %d", data.gameId, actualGameId) + } + } +} diff --git a/go/2023/internal/day_02/part_02/part_02.go b/go/2023/internal/day_02/part_02/part_02.go new file mode 100644 index 0000000..ffb9691 --- /dev/null +++ b/go/2023/internal/day_02/part_02/part_02.go @@ -0,0 +1,114 @@ +package part_02 + +import ( + "os" + "regexp" + "strconv" + "strings" +) + +type cubes struct { + red int + blue int + green int +} + +func Solve() { + contents, err := os.ReadFile("../_input/day_02.txt") + if err != nil { + panic(err) + } + + lines := strings.Split(string(contents), "\n") + answer := processLines(lines) + + println(answer) +} + +func processLines(lines []string) int { + var answer int + + for _, line := range lines { + if line == "" || line == "\n" { + continue + } + + shownCubes := convertLineToCubes(line) + + maxCubes := getMaximumCubes(shownCubes) + power := getPower(maxCubes) + answer += power + } + + return answer +} + +func convertLineToCubes(line string) []cubes { + var shownCubes []cubes + + redRegex := regexp.MustCompile(`(\d+) red`) + blueRegex := regexp.MustCompile(`(\d+) blue`) + greenRegex := regexp.MustCompile(`(\d+) green`) + + for _, round := range strings.Split(line, ";") { + var cubes cubes + + redMatches := redRegex.FindStringSubmatch(round) + if redMatches != nil { + red, err := strconv.Atoi(redMatches[1]) + if err != nil { + panic("Could not convert red to int: " + redMatches[1]) + } else { + cubes.red = red + } + } + + blueMatches := blueRegex.FindStringSubmatch(round) + if blueMatches != nil { + blue, err := strconv.Atoi(blueMatches[1]) + if err != nil { + panic("Could not convert green to int: " + blueMatches[1]) + } else { + cubes.blue = blue + } + } + + greenMatches := greenRegex.FindStringSubmatch(round) + if greenMatches != nil { + green, err := strconv.Atoi(greenMatches[1]) + if err != nil { + panic("Could not convert green to int: " + greenMatches[1]) + } else { + cubes.green = green + } + } + + shownCubes = append(shownCubes, cubes) + } + + return shownCubes +} + +func getMaximumCubes(shownCubes []cubes) cubes { + var maxCubes cubes + + for _, cubes := range shownCubes { + if cubes.red > maxCubes.red { + maxCubes.red = cubes.red + } + + if cubes.blue > maxCubes.blue { + maxCubes.blue = cubes.blue + } + + if cubes.green > maxCubes.green { + maxCubes.green = cubes.green + } + } + + return maxCubes +} + +func getPower(maxCubes cubes) int { + return maxCubes.red * maxCubes.blue * maxCubes.green +} diff --git a/go/2023/internal/day_02/part_02/part_02_test.go b/go/2023/internal/day_02/part_02/part_02_test.go new file mode 100644 index 0000000..6952899 --- /dev/null +++ b/go/2023/internal/day_02/part_02/part_02_test.go @@ -0,0 +1,84 @@ +package part_02 + +import ( + "testing" +) + +type data struct { + shownCubes []cubes + maxCubes cubes + line string +} + +var testInput = []data{ + { + shownCubes: []cubes{ + {red: 4, blue: 3}, + {red: 1, blue: 6, green: 2}, + {green: 2}, + }, + maxCubes: cubes{red: 4, blue: 6, green: 2}, + line: "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green", + }, + { + shownCubes: []cubes{ + {blue: 1, green: 2}, + {red: 1, blue: 4, green: 3}, + {blue: 1, green: 1}, + }, + maxCubes: cubes{red: 1, blue: 4, green: 3}, + line: "Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue", + }, + { + shownCubes: []cubes{ + {red: 20, blue: 6, green: 8}, + {red: 4, blue: 5, green: 13}, + {red: 1, green: 5}, + }, + maxCubes: cubes{red: 20, blue: 6, green: 13}, + line: "Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red", + }, + { + shownCubes: []cubes{ + {red: 3, blue: 6, green: 1}, + {red: 6, green: 3}, + {red: 14, blue: 15, green: 3}, + }, + maxCubes: cubes{red: 14, blue: 15, green: 3}, + line: "Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red", + }, + { + shownCubes: []cubes{ + {red: 6, blue: 1, green: 3}, + {red: 1, blue: 2, green: 2}, + }, + maxCubes: cubes{red: 6, blue: 2, green: 3}, + line: "Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green", + }, +} + +func TestConvertLineToCubes(t *testing.T) { + for _, data := range testInput { + actualCubes := convertLineToCubes(data.line) + + if len(data.shownCubes) != len(actualCubes) { + t.Errorf("Expected length of shownCubes to be %d, got %d", len(data.shownCubes), len(actualCubes)) + } + + for i, cubes := range data.shownCubes { + if cubes != actualCubes[i] { + t.Errorf("Expected shownCubes to be %v, got %v", cubes, actualCubes[i]) + } + } + } +} + +func TestGetMaximumCubes(t *testing.T) { + for _, data := range testInput { + actualMaxCubes := getMaximumCubes(data.shownCubes) + + if actualMaxCubes != data.maxCubes { + t.Errorf("Expected maxCubes to be %v, got %v", data.maxCubes, actualMaxCubes) + } + } +} diff --git a/go/2023/internal/day_03/part_01/part_01.go b/go/2023/internal/day_03/part_01/part_01.go new file mode 100644 index 0000000..49837b3 --- /dev/null +++ b/go/2023/internal/day_03/part_01/part_01.go @@ -0,0 +1,123 @@ +package part_01 + +/** + * TRIED: + * 530923 + */ + +import ( + "os" + "strconv" + "strings" +) + +const USE_SAMPLE = true + +func Solve() { + var content []byte + if USE_SAMPLE { + content, _ = os.ReadFile("../_input/day_03_part_01_sample.txt") + } else { + content, _ = os.ReadFile("../_input/day_03.txt") + } + lines := strings.Split(string(content), "\n") + + validNumbers := walkLines(lines) + + answer := 0 + for _, number := range validNumbers { + answer += number + } + + println(answer) +} + +func walkLines(lines []string) []int { + var validNumbers []int + + for y := 0; y < len(lines); y++ { + line := lines[y] + + if line == "" || line == "\n" { + continue + } + + // Counts as a symbol... + filteredLine := strings.ReplaceAll(line, "\r", "") + + previousWasNumber := false + number := "" + for x := 0; x < len(filteredLine); x++ { + char := filteredLine[x] + + _, err := strconv.Atoi(string(char)) + isNumeric := err == nil + + if isNumeric { + previousWasNumber = true + number += string(char) + continue + } + + if !previousWasNumber { + continue + } + + isValid := checkValidity(lines, x, y, len(number)) + if isValid { + num, _ := strconv.Atoi(number) + validNumbers = append(validNumbers, num) + } + + println(number) + + previousWasNumber = false + number = "" + } + } + + return validNumbers +} + +// N = number +// k = numOfDigits + 1 +// (x-k,y-1) (x-1,y-1) ( x ,y-1) +// (x-k, y ) N ( x , y ) +// (x-k,y+1) (x-1,y+1) ( x ,y+1) +func checkValidity(lines []string, x int, y int, numOfDigits int) bool { + + for i := max(y-1, 0); i <= y+1; i++ { + + var debugLine string + + for j := max(x-(numOfDigits+1), 0); j <= x; j++ { + + if lineExists := len(lines) > i; !lineExists { + continue + } + + if charExists := len(lines[i]) > j; !charExists { + continue + } + + char := rune(lines[i][j]) + debugLine += string(char) + + if char == '.' { + continue + } + + _, err := strconv.Atoi(string(char)) + isNumeric := err == nil + if isNumeric { + continue + } + + debugLine = "" //nolint:ineffassign // Only used for debugging and needs to be reset + + return true + } + } + + return false +} diff --git a/go/2023/internal/day_03/part_01/part_01_test.go b/go/2023/internal/day_03/part_01/part_01_test.go new file mode 100644 index 0000000..a077fbd --- /dev/null +++ b/go/2023/internal/day_03/part_01/part_01_test.go @@ -0,0 +1,78 @@ +package part_01 + +import ( + "os" + "strings" + "testing" +) + +func TestWalkLines(t *testing.T) { + lines := getSampleLines() + expected := 4361 + + validNumbers := walkLines(lines) + + actual := 0 + for _, number := range validNumbers { + actual += number + } + + if actual != expected { + t.Errorf("Expected answer to be %d, got %d", expected, actual) + } +} + +func TestCheckValidity(t *testing.T) { + lines := getSampleLines() + + tests := []struct { + x int + y int + length int + expected bool + }{ + {3, 0, 3, true}, // 467 + {8, 0, 3, false}, // 114 + {9, 0, 1, false}, // 3 - Added to check for '\r' counting as a symbol + {4, 2, 2, true}, // 35 + {9, 2, 3, true}, // 633 + {9, 3, 1, false}, // 3 - Added to check if number continues to next line + {3, 4, 3, true}, // 617 + {9, 5, 2, false}, // 58 + {5, 6, 3, true}, // 592 + {9, 7, 3, true}, // 775 + {4, 9, 3, true}, // 664 + {8, 9, 3, true}, // 598 + } + + for _, test := range tests { + actual := checkValidity(lines, test.x, test.y, test.length) + + if actual != test.expected { + t.Errorf("Expected %d, %d, %d to be %t, got %t", test.x, test.y, test.length, test.expected, actual) + } + } +} + +func getSampleLines() []string { + contents, err := os.ReadFile("../../../../_input/day_03_part_01_sample.txt") + if err != nil { + panic(err) + } + + lines := strings.Split(string(contents), "\n") + + var filteredLines []string + for _, line := range lines { + if line == "" || line == "\n" { + continue + } + + // Counts as a symbol... + filteredLine := strings.ReplaceAll(line, "\r", "") + + filteredLines = append(filteredLines, filteredLine) + } + + return filteredLines +} diff --git a/go/2023/internal/day_04/part_01/part_01.go b/go/2023/internal/day_04/part_01/part_01.go new file mode 100644 index 0000000..1a215b1 --- /dev/null +++ b/go/2023/internal/day_04/part_01/part_01.go @@ -0,0 +1,130 @@ +package part_01 + +import ( + "os" + "slices" + "strconv" + "strings" +) + +const USE_SAMPLE = false + +type UnparsedCard struct { + id string + winningNumbers []string + haveNumbers []string +} + +type Card struct { + id int + winningNumbers []int + haveNumbers []int +} + +func (c UnparsedCard) parse() Card { + var card Card + + id, err := strconv.Atoi(c.id) + if err != nil { + panic(err) + } + card.id = id + + for _, number := range c.winningNumbers { + num, err := strconv.Atoi(number) + if err != nil { + panic(err) + } + card.winningNumbers = append(card.winningNumbers, num) + } + + for _, number := range c.haveNumbers { + num, err := strconv.Atoi(number) + if err != nil { + panic(err) + } + card.haveNumbers = append(card.haveNumbers, num) + } + + return card +} + +func Solve() { + var contents []byte + if USE_SAMPLE { + contents, _ = os.ReadFile("../_input/day_04_part_01_sample.txt") + } else { + contents, _ = os.ReadFile("../_input/day_04.txt") + } + + lines := strings.Split(string(contents), "\n") + + answer := 0 + for _, line := range lines { + if line == "" || line == "\n" { + continue + } + + card := parseCardLine(line) + score := tallyScore(card) + + answer += score + } + + println(answer) +} + +// "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53" +func parseCardLine(line string) Card { + var unparsedCard UnparsedCard + + filteredLine := strings.ReplaceAll(line, "\r", "") + + // "Card 1" + card_parts := strings.Split(filteredLine, ":")[0] + id_parts := strings.Split(card_parts, " ") + id := id_parts[len(id_parts)-1] + unparsedCard.id = strings.ReplaceAll(id, " ", "") + + // "41 48 83 86 17 | 83 86 6 31 17 9 48 53" + numbers := strings.Split(filteredLine, ":")[1] + + // " 41 48 83 86 17" + winningNumberString := strings.Split(numbers, "|")[0] + // ["", "41", "48", "83", "86", "17"] + winningNumbers := strings.Split(winningNumberString, " ") + for _, num := range winningNumbers { + if num == "" || num == " " { + continue + } + unparsedCard.winningNumbers = append(unparsedCard.winningNumbers, num) + } + + // " 83 86 6 31 17 9 48 53" + haveNumberString := strings.Split(numbers, "|")[1] + // ["", "83", "86", "", "6", "31", "17", "", "9", "48", "53"] + haveNumbers := strings.Split(haveNumberString, " ") + for _, num := range haveNumbers { + if num == "" || num == " " { + continue + } + unparsedCard.haveNumbers = append(unparsedCard.haveNumbers, num) + } + + return unparsedCard.parse() +} + +func tallyScore(card Card) int { + score := 0 + for _, winningNumber := range card.winningNumbers { + if slices.Contains(card.haveNumbers, winningNumber) { + if score == 0 { + score = 1 + } else { + score *= 2 + } + } + } + + return score +} diff --git a/go/2023/internal/day_04/part_01/part_01_test.go b/go/2023/internal/day_04/part_01/part_01_test.go new file mode 100644 index 0000000..f6f1a83 --- /dev/null +++ b/go/2023/internal/day_04/part_01/part_01_test.go @@ -0,0 +1,48 @@ +package part_01 + +import ( + "testing" +) + +func TestParseCardLine(t *testing.T) { + input := "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53" + expected := Card{1, []int{41, 48, 83, 86, 17}, []int{83, 86, 6, 31, 17, 9, 48, 53}} + + actual := parseCardLine(input) + + if actual.id != expected.id { + t.Errorf("ERROR: ID %d does not match expected %d", actual.id, expected.id) + } + + if len(actual.winningNumbers) != len(expected.winningNumbers) { + t.Errorf("ERROR: len(WinningNumbers) %d does not match expected %d", len(actual.winningNumbers), len(expected.winningNumbers)) + } + + if len(actual.haveNumbers) != len(expected.haveNumbers) { + t.Errorf("ERROR: len(HaveNumbers) %d does not match expected %d", len(actual.haveNumbers), len(expected.haveNumbers)) + } + + for i, num := range actual.winningNumbers { + if num != expected.winningNumbers[i] { + t.Errorf("ERROR: WinningNumber %d does not match expected %d", num, expected.winningNumbers[i]) + } + } + + for i, num := range actual.haveNumbers { + if num != expected.haveNumbers[i] { + t.Errorf("ERROR: HaveNumber %d does not match expected %d", num, expected.haveNumbers[i]) + } + } + +} + +func TestTallyScore(t *testing.T) { + card := Card{1, []int{41, 48, 83, 86, 17}, []int{83, 86, 6, 31, 17, 9, 48, 53}} + expected := 8 + + actual := tallyScore(card) + + if actual != expected { + t.Errorf("ERROR: Score %d does not match expected %d", actual, expected) + } +} diff --git a/go/2023/internal/day_04/part_02/part_02.go b/go/2023/internal/day_04/part_02/part_02.go new file mode 100644 index 0000000..a9e3152 --- /dev/null +++ b/go/2023/internal/day_04/part_02/part_02.go @@ -0,0 +1,142 @@ +package part_02 + +import ( + "os" + "slices" + "strconv" + "strings" +) + +const USE_SAMPLE = false + +type UnparsedCard struct { + id string + winningNumbers []string + haveNumbers []string +} + +type Card struct { + id int + winningNumbers []int + haveNumbers []int +} + +func (c UnparsedCard) parse() Card { + var card Card + + id, err := strconv.Atoi(c.id) + if err != nil { + panic(err) + } + card.id = id + + for _, number := range c.winningNumbers { + num, err := strconv.Atoi(number) + if err != nil { + panic(err) + } + card.winningNumbers = append(card.winningNumbers, num) + } + + for _, number := range c.haveNumbers { + num, err := strconv.Atoi(number) + if err != nil { + panic(err) + } + card.haveNumbers = append(card.haveNumbers, num) + } + + return card +} + +func Solve() { + var contents []byte + if USE_SAMPLE { + contents, _ = os.ReadFile("../_input/day_04_part_02_sample.txt") + } else { + contents, _ = os.ReadFile("../_input/day_04.txt") + } + + lines := strings.Split(string(contents), "\n") + + dict := map[int]Card{} + var cards []Card + for _, line := range lines { + if line == "" || line == "\n" { + continue + } + + card := parseCardLine(line) + + dict[card.id] = card + cards = append(cards, card) + } + + answer := processCards(dict, cards) + + println(answer) +} + +// "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53" +func parseCardLine(line string) Card { + var unparsedCard UnparsedCard + + filteredLine := strings.ReplaceAll(line, "\r", "") + + // "Card 1" + card_parts := strings.Split(filteredLine, ":")[0] + id_parts := strings.Split(card_parts, " ") + id := id_parts[len(id_parts)-1] + unparsedCard.id = strings.ReplaceAll(id, " ", "") + + // "41 48 83 86 17 | 83 86 6 31 17 9 48 53" + numbers := strings.Split(filteredLine, ":")[1] + + // " 41 48 83 86 17" + winningNumberString := strings.Split(numbers, "|")[0] + // ["", "41", "48", "83", "86", "17"] + winningNumbers := strings.Split(winningNumberString, " ") + for _, num := range winningNumbers { + if num == "" || num == " " { + continue + } + unparsedCard.winningNumbers = append(unparsedCard.winningNumbers, num) + } + + // " 83 86 6 31 17 9 48 53" + haveNumberString := strings.Split(numbers, "|")[1] + // ["", "83", "86", "", "6", "31", "17", "", "9", "48", "53"] + haveNumbers := strings.Split(haveNumberString, " ") + for _, num := range haveNumbers { + if num == "" || num == " " { + continue + } + unparsedCard.haveNumbers = append(unparsedCard.haveNumbers, num) + } + + return unparsedCard.parse() +} + +func processCards(dict map[int]Card, cards []Card) int { + for i := 0; i < len(cards); i++ { + card := cards[i] + matchCount := calculateNumberOfMatches(card) + for j := card.id + 1; j < (card.id + 1 + matchCount); j++ { + copyCard := dict[j] + cards = append(cards, copyCard) + } + } + + return len(cards) +} + +func calculateNumberOfMatches(card Card) int { + matchCount := 0 + for _, winningNumber := range card.winningNumbers { + if slices.Contains(card.haveNumbers, winningNumber) { + matchCount++ + } + } + + return matchCount +} diff --git a/go/2023/internal/day_04/part_02/part_02_test.go b/go/2023/internal/day_04/part_02/part_02_test.go new file mode 100644 index 0000000..3c66a2e --- /dev/null +++ b/go/2023/internal/day_04/part_02/part_02_test.go @@ -0,0 +1,143 @@ +package part_02 + +import ( + "testing" +) + +func TestParseCardLine(t *testing.T) { + input := "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53" + expected := Card{1, []int{41, 48, 83, 86, 17}, []int{83, 86, 6, 31, 17, 9, 48, 53}} + + actual := parseCardLine(input) + + if actual.id != expected.id { + t.Errorf("ERROR: ID %d does not match expected %d", actual.id, expected.id) + } + + if len(actual.winningNumbers) != len(expected.winningNumbers) { + t.Errorf("ERROR: len(WinningNumbers) %d does not match expected %d", len(actual.winningNumbers), len(expected.winningNumbers)) + } + + if len(actual.haveNumbers) != len(expected.haveNumbers) { + t.Errorf("ERROR: len(HaveNumbers) %d does not match expected %d", len(actual.haveNumbers), len(expected.haveNumbers)) + } + + for i, num := range actual.winningNumbers { + if num != expected.winningNumbers[i] { + t.Errorf("ERROR: WinningNumber %d does not match expected %d", num, expected.winningNumbers[i]) + } + } + + for i, num := range actual.haveNumbers { + if num != expected.haveNumbers[i] { + t.Errorf("ERROR: HaveNumber %d does not match expected %d", num, expected.haveNumbers[i]) + } + } + +} + +func TestProcessCards(t *testing.T) { + cards := []Card{ + // Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 + { + 1, + []int{41, 48, 83, 86, 17}, + []int{83, 86, 6, 31, 17, 9, 48, 53}, + }, + // Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 + { + 2, + []int{13, 32, 20, 16, 61}, + []int{61, 30, 68, 82, 17, 32, 24, 19}, + }, + // Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 + { + 3, + []int{1, 21, 53, 59, 44}, + []int{69, 82, 63, 72, 16, 21, 14, 1}, + }, + // Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 + { + 4, + []int{41, 92, 73, 84, 69}, + []int{59, 84, 76, 51, 58, 5, 54, 83}, + }, + // Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 + { + 5, + []int{87, 83, 26, 28, 32}, + []int{88, 30, 70, 12, 93, 22, 82, 36}, + }, + // Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 + { + 6, + []int{31, 18, 13, 56, 72}, + []int{74, 77, 10, 23, 35, 67, 36, 11}, + }, + } + dict := map[int]Card{} + for _, card := range cards { + dict[card.id] = card + } + expected := 30 + + actual := processCards(dict, cards) + + if actual != expected { + t.Errorf("ERROR: Total number of cards %d does not match expected %d", actual, expected) + } +} + +func TestCalculateNumberOfMatches(t *testing.T) { + cards := []Card{ + // Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 + { + 1, + []int{41, 48, 83, 86, 17}, + []int{83, 86, 6, 31, 17, 9, 48, 53}, + }, + // Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 + { + 2, + []int{13, 32, 20, 16, 61}, + []int{61, 30, 68, 82, 17, 32, 24, 19}, + }, + // Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 + { + 3, + []int{1, 21, 53, 59, 44}, + []int{69, 82, 63, 72, 16, 21, 14, 1}, + }, + // Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 + { + 4, + []int{41, 92, 73, 84, 69}, + []int{59, 84, 76, 51, 58, 5, 54, 83}, + }, + // Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 + { + 5, + []int{87, 83, 26, 28, 32}, + []int{88, 30, 70, 12, 93, 22, 82, 36}, + }, + // Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 + { + 6, + []int{31, 18, 13, 56, 72}, + []int{74, 77, 10, 23, 35, 67, 36, 11}, + }, + } + expected := 9 + + total := 0 + for _, card := range cards { + matches := calculateNumberOfMatches(card) + total += matches + + cards = append(cards, card) + } + + if total != expected { + t.Errorf("ERROR: Score %d does not match expected %d", total, expected) + } +} diff --git a/go/2023/internal/day_06/part_01/part_01.go b/go/2023/internal/day_06/part_01/part_01.go new file mode 100644 index 0000000..5735924 --- /dev/null +++ b/go/2023/internal/day_06/part_01/part_01.go @@ -0,0 +1,85 @@ +package part_01 + +import ( + "os" + "strconv" + "strings" +) + +const USE_SAMPLE = false + +/** + * Tried: + * - 72128 (too low) + * - 3317888 (correct!) + */ +func Solve() { + var filePath string + if USE_SAMPLE { + filePath = "../_input/day_06_part_01_sample.txt" + } else { + filePath = "../_input/day_06.txt" + } + contents, _ := os.ReadFile(filePath) + + lines := strings.Split(string(contents), "\r\n") + + timeLine := lines[0] + times := parseTimeLine(timeLine) + + distLine := lines[1] + distances := parseDistanceLine(distLine) + + answer := calculateAnswer(times, distances) + + println(answer) +} + +func parseTimeLine(timeLine string) []int { + prefix := "Time:" + return parseLine(prefix, timeLine) +} + +func parseDistanceLine(distLine string) []int { + prefix := "Distance:" + return parseLine(prefix, distLine) +} + +func parseLine(prefix string, line string) []int { + withoutTitle := strings.TrimPrefix(line, prefix) + + numbers := []int{} + for _, number := range strings.Split(withoutTitle, " ") { + if number == "" { + continue + } + numberInt, _ := strconv.Atoi(number) + numbers = append(numbers, numberInt) + } + return numbers +} + +func calculateAnswer(times []int, distances []int) int { + answer := 1 + for i, maxTime := range times { + recordDistance := distances[i] + + var greaterThanRecord []int + for buttonPressedFor := 0; buttonPressedFor <= maxTime; buttonPressedFor++ { + distance := calculateDistance(buttonPressedFor, maxTime) + if distance > recordDistance { + greaterThanRecord = append(greaterThanRecord, distance) + } + } + + if len(greaterThanRecord) > 0 { + answer *= len(greaterThanRecord) + } + } + + return answer +} + +func calculateDistance(buttonPressedFor int, maxTime int) int { + return buttonPressedFor * (maxTime - buttonPressedFor) +} diff --git a/go/2023/internal/day_06/part_01/part_01_test.go b/go/2023/internal/day_06/part_01/part_01_test.go new file mode 100644 index 0000000..7af828c --- /dev/null +++ b/go/2023/internal/day_06/part_01/part_01_test.go @@ -0,0 +1,96 @@ +package part_01 + +import "testing" + +func TestParseTimeLine(t *testing.T) { + tests := map[string]struct { + input string + expected []int + }{ + "empty": {"", []int{}}, + "sample": {"Time: 7 15 30", []int{7, 15, 30}}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := parseTimeLine(test.input) + if len(actual) != len(test.expected) { + t.Errorf("Expected %d, got %d", len(test.expected), len(actual)) + } + for i := 0; i < len(actual); i++ { + if actual[i] != test.expected[i] { + t.Errorf("Expected %d, got %d", test.expected[i], actual[i]) + } + } + }) + } +} + +func TestParseDistLine(t *testing.T) { + tests := map[string]struct { + input string + expected []int + }{ + "empty": {"", []int{}}, + "sample": {"Distance: 9 40 200", []int{9, 40, 200}}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := parseDistanceLine(test.input) + if len(actual) != len(test.expected) { + t.Errorf("Expected %d, got %d", len(test.expected), len(actual)) + } + for i := 0; i < len(actual); i++ { + if actual[i] != test.expected[i] { + t.Errorf("Expected %d, got %d", test.expected[i], actual[i]) + } + } + }) + } +} + +func TestCalculateAnswer(t *testing.T) { + tests := map[string]struct { + times []int + distances []int + expected int + }{ + "sample": {[]int{7, 15, 30}, []int{9, 40, 200}, 288}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := calculateAnswer(test.times, test.distances) + if actual != test.expected { + t.Errorf("Expected %d, got %d", test.expected, actual) + } + }) + } +} + +func TestCalculateDistance(t *testing.T) { + tests := map[string]struct { + maxTime int + buttonPressedFor int + expected int + }{ + "7_7": {7, 7, 0}, + "7_6": {7, 6, 6}, + "7_5": {7, 5, 10}, + "7_4": {7, 4, 12}, + "7_3": {7, 3, 12}, + "7_2": {7, 2, 10}, + "7_1": {7, 1, 6}, + "7_0": {7, 0, 0}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := calculateDistance(test.buttonPressedFor, test.maxTime) + if actual != test.expected { + t.Errorf("Expected %d, got %d", test.expected, actual) + } + }) + } +} diff --git a/go/2023/internal/day_06/part_02/part_02.go b/go/2023/internal/day_06/part_02/part_02.go new file mode 100644 index 0000000..316b169 --- /dev/null +++ b/go/2023/internal/day_06/part_02/part_02.go @@ -0,0 +1,81 @@ +package part_02 + +import ( + "os" + "strconv" + "strings" +) + +const USE_SAMPLE = false + +/** + * Tried: + * - 24655068 (correct!) + */ +func Solve() { + var filePath string + if USE_SAMPLE { + filePath = "../_input/day_06_part_02_sample.txt" + } else { + filePath = "../_input/day_06.txt" + } + contents, _ := os.ReadFile(filePath) + + lines := strings.Split(string(contents), "\r\n") + + timeLine := lines[0] + time := parseTimeLine(timeLine) + + distLine := lines[1] + distance := parseDistanceLine(distLine) + + answer := calculateAnswer(time, distance) + + println(answer) +} + +func parseTimeLine(timeLine string) int { + prefix := "Time:" + return parseLine(prefix, timeLine) +} + +func parseDistanceLine(distLine string) int { + prefix := "Distance:" + return parseLine(prefix, distLine) +} + +func parseLine(prefix string, line string) int { + withoutTitle := strings.TrimPrefix(line, prefix) + + numbers := []string{} + for _, number := range strings.Split(withoutTitle, " ") { + if number == "" { + continue + } + numbers = append(numbers, number) + } + + number := strings.Join(numbers, "") + numberInt, err := strconv.Atoi(number) + if err != nil { + panic(err) + } + + return numberInt +} + +func calculateAnswer(maxTime int, recordDistance int) int { + var greaterThanRecord []int + for buttonPressedFor := 0; buttonPressedFor <= maxTime; buttonPressedFor++ { + distance := calculateDistance(buttonPressedFor, maxTime) + if distance > recordDistance { + greaterThanRecord = append(greaterThanRecord, distance) + } + } + + return len(greaterThanRecord) +} + +func calculateDistance(buttonPressedFor int, maxTime int) int { + return buttonPressedFor * (maxTime - buttonPressedFor) +} diff --git a/go/2023/internal/day_06/part_02/part_02_test.go b/go/2023/internal/day_06/part_02/part_02_test.go new file mode 100644 index 0000000..67924dc --- /dev/null +++ b/go/2023/internal/day_06/part_02/part_02_test.go @@ -0,0 +1,84 @@ +package part_02 + +import "testing" + +func TestParseTimeLine(t *testing.T) { + tests := map[string]struct { + input string + expected int + }{ + "sample": {"Time: 7 15 30", 71530}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := parseTimeLine(test.input) + if actual != test.expected { + t.Errorf("Expected %d, got %d", test.expected, actual) + } + }) + } +} + +func TestParseDistLine(t *testing.T) { + tests := map[string]struct { + input string + expected int + }{ + "sample": {"Distance: 9 40 200", 940200}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := parseDistanceLine(test.input) + if actual != test.expected { + t.Errorf("Expected %d, got %d", test.expected, actual) + } + }) + } +} + +func TestCalculateAnswer(t *testing.T) { + tests := map[string]struct { + maxTime int + recordDistance int + expected int + }{ + "sample": {71530, 940200, 71503}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := calculateAnswer(test.maxTime, test.recordDistance) + if actual != test.expected { + t.Errorf("Expected %d, got %d", test.expected, actual) + } + }) + } +} + +func TestCalculateDistance(t *testing.T) { + tests := map[string]struct { + maxTime int + buttonPressedFor int + expected int + }{ + "7_7": {7, 7, 0}, + "7_6": {7, 6, 6}, + "7_5": {7, 5, 10}, + "7_4": {7, 4, 12}, + "7_3": {7, 3, 12}, + "7_2": {7, 2, 10}, + "7_1": {7, 1, 6}, + "7_0": {7, 0, 0}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := calculateDistance(test.buttonPressedFor, test.maxTime) + if actual != test.expected { + t.Errorf("Expected %d, got %d", test.expected, actual) + } + }) + } +} diff --git a/go/2023/internal/day_07/part_01/part_01.go b/go/2023/internal/day_07/part_01/part_01.go new file mode 100644 index 0000000..a485dfd --- /dev/null +++ b/go/2023/internal/day_07/part_01/part_01.go @@ -0,0 +1,273 @@ +package part_01 + +import ( + "os" + "strconv" + "strings" +) + +const USE_SAMPLE = false + +type handType int + +const ( + HIGH_CARD handType = iota + PAIR + TWO_PAIR + THREE_OF_A_KIND + FULL_HOUSE + FOUR_OF_A_KIND + FIVE_OF_A_KIND +) + +func (h handType) String() string { + return [...]string{"HIGH_CARD", "PAIR", "TWO_PAIR", "THREE_OF_A_KIND", "FULL_HOUSE", "FOUR_OF_A_KIND", "FIVE_OF_A_KIND"}[h] +} + +type cardType int + +const ( + TWO cardType = iota + THREE + FOUR + FIVE + SIX + SEVEN + EIGHT + NINE + TEN + JACK + QUEEN + KING + ACE +) + +func (c cardType) String() string { + return [...]string{"TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "TEN", "JACK", "QUEEN", "KING", "ACE"}[c] +} + +type hand struct { + strength handType + cards []cardType + bet int +} + +/** + * Tried: + * - 252489894 (too high) + * - 229176554 (too low) + * - 200253108 (too low) + */ +func Solve() { + var filePath string + if USE_SAMPLE { + filePath = "../_input/day_07_part_01_sample.txt" + } else { + filePath = "../_input/day_07.txt" + } + contents, err := os.ReadFile(filePath) + if err != nil { + panic(err) + } + + lines := strings.Split(string(contents), "\r\n") + var hands []hand + for _, line := range lines { + if line == "" { + continue + } + + hands = append(hands, parseLine(line)) + } + + mappedByStrength := mapByStrength(hands) + + sortedByHighest := make(map[handType][]hand) + for _, strength := range []handType{HIGH_CARD, PAIR, TWO_PAIR, THREE_OF_A_KIND, FULL_HOUSE, FOUR_OF_A_KIND, FIVE_OF_A_KIND} { + if _, ok := mappedByStrength[strength]; !ok { + continue + } + + toBeSorted := mappedByStrength[strength] + sortedByHighest[strength] = sortByHighest(toBeSorted) + } + + flattened := flatten(sortedByHighest) + + answer := calculateWinnings(flattened) + + println("Answer: ", answer) +} + +func parseLine(line string) hand { + parts := strings.Split(line, " ") + + unparsedCards := strings.Split(parts[0], "") + cards := parseCards(unparsedCards) + strength := calculateStrength(cards) + + bet, err := strconv.Atoi(parts[1]) + if err != nil { + panic(err) + } + + return hand{ + strength, + cards, + bet, + } +} + +func parseCards(unparsedCards []string) []cardType { + var cards []cardType + for _, card := range unparsedCards { + switch card { + case "2": + cards = append(cards, TWO) + case "3": + cards = append(cards, THREE) + case "4": + cards = append(cards, FOUR) + case "5": + cards = append(cards, FIVE) + case "6": + cards = append(cards, SIX) + case "7": + cards = append(cards, SEVEN) + case "8": + cards = append(cards, EIGHT) + case "9": + cards = append(cards, NINE) + case "T": + cards = append(cards, TEN) + case "J": + cards = append(cards, JACK) + case "Q": + cards = append(cards, QUEEN) + case "K": + cards = append(cards, KING) + case "A": + cards = append(cards, ACE) + } + } + return cards +} + +func calculateStrength(cards []cardType) handType { + strength := map[cardType]int{} + for _, card := range cards { + if _, ok := strength[card]; !ok { + strength[card] = 0 + } + strength[card]++ + } + + var pairs int + var hasThreeOfAKind bool + for _, count := range strength { + if count == 5 { + return FIVE_OF_A_KIND + } + + if count == 4 { + return FOUR_OF_A_KIND + } + + if count == 3 { + hasThreeOfAKind = true + } + + if count == 2 { + pairs++ + } + } + + if hasThreeOfAKind && pairs == 1 { + return FULL_HOUSE + } + + if hasThreeOfAKind { + return THREE_OF_A_KIND + } + + if pairs == 2 { + return TWO_PAIR + } + + if pairs == 1 { + return PAIR + } + + return HIGH_CARD +} + +func mapByStrength(hands []hand) map[handType][]hand { + + handsByStrength := make(map[handType][]hand) + for _, hand := range hands { + handsByStrength[hand.strength] = append(handsByStrength[hand.strength], hand) + } + + return handsByStrength +} + +// low to high +func sortByHighest(hands []hand) []hand { + var partition = func(arr []hand, low, high int) ([]hand, int) { + pivot := arr[high] + index := low + + for i := low; i < high; i++ { + // Always 5 cards + for j := 0; j < 5; j++ { + if arr[i].cards[j] < pivot.cards[j] { + arr[i], arr[index] = arr[index], arr[i] + index++ + break + } + + if arr[i].cards[j] != pivot.cards[j] { + break + } + } + } + + arr[high], arr[index] = arr[index], arr[high] + + return arr, index + } + + var quickSort func(arr []hand, low, high int) []hand + quickSort = func(arr []hand, low, high int) []hand { + if low < high { + arr, pivot := partition(arr, low, high) + + quickSort(arr, low, pivot-1) + quickSort(arr, pivot+1, high) + } + + return arr + } + + hands = quickSort(hands, 0, len(hands)-1) + + return hands +} + +func flatten(hands map[handType][]hand) []hand { + var flat []hand + for _, handType := range []handType{HIGH_CARD, PAIR, TWO_PAIR, THREE_OF_A_KIND, FULL_HOUSE, FOUR_OF_A_KIND, FIVE_OF_A_KIND} { + flat = append(flat, hands[handType]...) + } + return flat +} + +func calculateWinnings(hands []hand) int { + var winnings int + for i, hand := range hands { + earned := hand.bet * (i + 1) + winnings += earned + println("hand ", i, ": { strength: ", hand.strength.String(), ", cards: [", hand.cards[0], hand.cards[1], hand.cards[2], hand.cards[3], hand.cards[4], "], earned: ", earned, " }") + } + return winnings +} diff --git a/go/2023/internal/day_07/part_01/part_01_test.go b/go/2023/internal/day_07/part_01/part_01_test.go new file mode 100644 index 0000000..6ceabe8 --- /dev/null +++ b/go/2023/internal/day_07/part_01/part_01_test.go @@ -0,0 +1,432 @@ +package part_01 + +import "testing" + +func TestParseLine(t *testing.T) { + tests := map[string]struct { + input string + expected hand + }{ + "sample_1": { + input: "32T3K 765", + expected: hand{ + strength: PAIR, + cards: []cardType{THREE, TWO, TEN, THREE, KING}, + bet: 765, + }, + }, + "sample_2": { + input: "T55J5 684", + expected: hand{ + strength: THREE_OF_A_KIND, + cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, + bet: 684, + }, + }, + "sample_3": { + input: "KK677 28", + expected: hand{ + strength: TWO_PAIR, + cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, + bet: 28, + }, + }, + "sample_4": { + input: "KTJJT 220", + expected: hand{ + strength: TWO_PAIR, + cards: []cardType{KING, TEN, JACK, JACK, TEN}, + bet: 220, + }, + }, + "sample_5": { + input: "QQQJA 483", + expected: hand{ + strength: THREE_OF_A_KIND, + cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, + bet: 483, + }, + }, + "full_house": { + input: "QQQAA 999", + expected: hand{ + strength: FULL_HOUSE, + cards: []cardType{QUEEN, QUEEN, QUEEN, ACE, ACE}, + bet: 999, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := parseLine(test.input) + + if actual.strength != test.expected.strength { + t.Errorf("Strength: expected %v, got %v", test.expected.strength, actual.strength) + } + + if len(actual.cards) != len(test.expected.cards) { + t.Errorf("Cards: expected %v, got %v", test.expected.cards, actual.cards) + } + + for i, card := range actual.cards { + if card != test.expected.cards[i] { + t.Errorf("Card Order: expected %v, got %v", test.expected.cards, actual.cards) + } + } + + if actual.bet != test.expected.bet { + t.Errorf("Bet: expected %v, got %v", test.expected.bet, actual.bet) + } + }) + } +} + +func TestMapByStrength(t *testing.T) { + tests := map[string]struct { + input []hand + expected map[handType][]hand + }{ + "sample": { + input: []hand{ + { + strength: PAIR, + cards: []cardType{THREE, TWO, TEN, THREE, KING}, + bet: 765, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, + bet: 684, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, + bet: 28, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, TEN, JACK, JACK, TEN}, + bet: 220, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, + bet: 483, + }, + }, + expected: map[handType][]hand{ + PAIR: { + { + strength: PAIR, + cards: []cardType{THREE, TWO, TEN, THREE, KING}, + bet: 765, + }, + }, + TWO_PAIR: { + { + strength: TWO_PAIR, + cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, + bet: 28, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, TEN, JACK, JACK, TEN}, + bet: 220, + }, + }, + THREE_OF_A_KIND: { + { + strength: THREE_OF_A_KIND, + cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, + bet: 684, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, + bet: 483, + }, + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := mapByStrength(test.input) + + for strength, hands := range actual { + expected := test.expected[strength] + + if len(hands) != len(expected) { + t.Errorf("Length: expected %v, got %v", len(expected), len(hands)) + } + + for i, hand := range hands { + if hand.strength != expected[i].strength { + t.Errorf("Strength: expected %v, got %v", expected[i].strength, hand.strength) + } + + if len(hand.cards) != len(expected[i].cards) { + t.Errorf("Cards: expected %v, got %v", expected[i].cards, hand.cards) + } + + for j, card := range hand.cards { + if card != expected[i].cards[j] { + t.Errorf("Card Order: expected %v, got %v", expected[i].cards, hand.cards) + } + } + + if hand.bet != expected[i].bet { + t.Errorf("Bet: expected %v, got %v", expected[i].bet, hand.bet) + } + } + } + }) + } +} + +func TestSortByHighest(t *testing.T) { + tests := map[string]struct { + input []hand + expected []hand + }{ + "sample": { + input: []hand{ + { + strength: PAIR, + cards: []cardType{THREE, TWO, TEN, THREE, KING}, + bet: 765, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, + bet: 684, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, + bet: 28, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, TEN, JACK, JACK, TEN}, + bet: 220, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, + bet: 483, + }, + }, + expected: []hand{ + { + strength: PAIR, + cards: []cardType{THREE, TWO, TEN, THREE, KING}, + bet: 765, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, + bet: 684, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, + bet: 483, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, TEN, JACK, JACK, TEN}, + bet: 220, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, + bet: 28, + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := sortByHighest(test.input) + + if len(actual) != len(test.expected) { + t.Errorf("Length: expected %v, got %v", len(test.expected), len(actual)) + } + + for i, hand := range actual { + if hand.strength != test.expected[i].strength { + t.Errorf("Strength: expected %v, got %v", test.expected[i].strength, hand.strength) + } + + if len(hand.cards) != len(test.expected[i].cards) { + t.Errorf("Cards: expected %v, got %v", test.expected[i].cards, hand.cards) + } + + for j, card := range hand.cards { + if card != test.expected[i].cards[j] { + t.Errorf("Card Order: expected %v, got %v", test.expected[i].cards, hand.cards) + } + } + + if hand.bet != test.expected[i].bet { + t.Errorf("Bet: expected %v, got %v", test.expected[i].bet, hand.bet) + } + } + }) + } +} + +func TestFlatten(t *testing.T) { + tests := map[string]struct { + input map[handType][]hand + expected []hand + }{ + "sample": { + // Order jumbled to ensure sorting works + input: map[handType][]hand{ + TWO_PAIR: { + { + strength: TWO_PAIR, + cards: []cardType{KING, TEN, JACK, JACK, TEN}, + bet: 220, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, + bet: 28, + }, + }, + PAIR: { + { + strength: PAIR, + cards: []cardType{THREE, TWO, TEN, THREE, KING}, + bet: 765, + }, + }, + THREE_OF_A_KIND: { + { + strength: THREE_OF_A_KIND, + cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, + bet: 684, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, + bet: 483, + }, + }, + }, + expected: []hand{ + { + strength: PAIR, + cards: []cardType{THREE, TWO, TEN, THREE, KING}, + bet: 765, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, TEN, JACK, JACK, TEN}, + bet: 220, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, + bet: 28, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, + bet: 684, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, + bet: 483, + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := flatten(test.input) + + if len(actual) != len(test.expected) { + t.Errorf("Length: expected %v, got %v", len(test.expected), len(actual)) + } + + for i, hand := range actual { + expected := test.expected[i] + + if hand.strength != expected.strength { + t.Errorf("Strength: expected %v, got %v", expected.strength, hand.strength) + } + + if len(hand.cards) != len(expected.cards) { + t.Errorf("Cards: expected %v, got %v", expected.cards, hand.cards) + } + + for i, card := range hand.cards { + if card != expected.cards[i] { + t.Errorf("Card Order: expected %v, got %v", expected.cards, hand.cards) + } + } + + if hand.bet != expected.bet { + t.Errorf("Bet: expected %v, got %v", expected.bet, hand.bet) + } + } + }) + } +} + +func TestCalculateWinnings(t *testing.T) { + tests := map[string]struct { + input []hand + expected int + }{ + "sample": { + input: []hand{ + { + strength: PAIR, + cards: []cardType{THREE, TWO, TEN, THREE, KING}, + bet: 765, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, TEN, JACK, JACK, TEN}, + bet: 220, + }, + { + strength: TWO_PAIR, + cards: []cardType{KING, KING, SIX, SEVEN, SEVEN}, + bet: 28, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{TEN, FIVE, FIVE, JACK, FIVE}, + bet: 684, + }, + { + strength: THREE_OF_A_KIND, + cards: []cardType{QUEEN, QUEEN, QUEEN, JACK, ACE}, + bet: 483, + }, + }, + expected: 6440, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := calculateWinnings(test.input) + + if actual != test.expected { + t.Errorf("Winnings: expected %v, got %v", test.expected, actual) + } + }) + } +} diff --git a/go/2023/main.go b/go/2023/main.go new file mode 100644 index 0000000..7d71b47 --- /dev/null +++ b/go/2023/main.go @@ -0,0 +1,86 @@ +package main + +import ( + "flag" + "os" + + "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_01" + day_02_part_01 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_02/part_01" + day_02_part_02 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_02/part_02" + day_03_part_01 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_03/part_01" + day_04_part_01 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_04/part_01" + day_04_part_02 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_04/part_02" + day_06_part_01 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_06/part_01" + day_06_part_02 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_06/part_02" + day_07_part_01 "github.com/DJOLEARY/Advent_of_Code/2023/Go/internal/day_07/part_01" + _ "github.com/outrigdev/outrig/autoinit" +) + +func main() { + day := flag.Int("day", 1, "Day to run") + part := flag.Int("part", 2, "Part to run, must be 1 or 2") + + flag.Parse() + + if *part < 1 || *part > 2 { + println("Invalid part number, must be 1 or 2") + os.Exit(1) + } + + switch *day { + case 1: + switch *part { + case 1: + println("Day 1 part 1 not implemented") + os.Exit(1) + case 2: + day_01.Solve() + } + + case 2: + switch *part { + case 1: + day_02_part_01.Solve() + case 2: + day_02_part_02.Solve() + } + + case 3: + switch *part { + case 1: + day_03_part_01.Solve() + case 2: + println("Day 3 part 2 not implemented") + os.Exit(1) + } + + case 4: + switch *part { + case 1: + day_04_part_01.Solve() + case 2: + day_04_part_02.Solve() + } + + case 6: + switch *part { + case 1: + day_06_part_01.Solve() + case 2: + day_06_part_02.Solve() + } + + case 7: + switch *part { + case 1: + day_07_part_01.Solve() + case 2: + println("Day 7 part 2 not implemented") + os.Exit(1) + } + + default: + println("Day not implemented") + os.Exit(1) + } +} diff --git a/ocaml/.editorconfig b/ocaml/.editorconfig new file mode 100644 index 0000000..64b41ca --- /dev/null +++ b/ocaml/.editorconfig @@ -0,0 +1,27 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space + +[*.{ml,mli}] +indent_style = space +indent_size = 2 +tab_width = 2 + +[*.{opam,dune,install,sexp,conf,in}] +indent_style = space +indent_size = 4 + +[*.{md}] +indent_style = tab +trim_trailing_whitespace = true + +[*.{nix}] +indent_style = space +indent_size = 2 +insert_final_newline = false +trim_trailing_whitespace = true diff --git a/ocaml/.env.template b/ocaml/.env.template new file mode 100644 index 0000000..3adee3e --- /dev/null +++ b/ocaml/.env.template @@ -0,0 +1 @@ +LOG_LEVEL=debug diff --git a/ocaml/.envrc b/ocaml/.envrc new file mode 100644 index 0000000..cf8df4b --- /dev/null +++ b/ocaml/.envrc @@ -0,0 +1,3 @@ +dotenv + +use flake diff --git a/ocaml/.gitattributes b/ocaml/.gitattributes new file mode 100644 index 0000000..1bc2e72 --- /dev/null +++ b/ocaml/.gitattributes @@ -0,0 +1,7 @@ +# http://git-scm.com/docs/gitattributes#_end_of_line_conversion +* text=auto +.* text eol=lf + +flake.lock linguist-generated +ocaml_template.install linguist-generated +ocaml_template.opam linguist-generated diff --git a/ocaml/.gitignore b/ocaml/.gitignore new file mode 100644 index 0000000..5456007 --- /dev/null +++ b/ocaml/.gitignore @@ -0,0 +1,45 @@ +*.annot +*.cmo +*.cma +*.cmi +*.a +*.o +*.cmx +*.cmxs +*.cmxa + +# Files containing detailed information about the compilation (generated +# by `ocamlc`/`ocamlopt` when invoked using the option `-bin-annot`). +# These files are typically useful for code inspection tools +# (e.g. Merlin). +*.cmt +*.cmti + +# ocamlbuild and Dune default working directory +_build/ + +# ocamlbuild targets +*.byte +*.native + +# oasis generated files +setup.data +setup.log + +# Merlin configuring file for Vim and Emacs +.merlin + +# Dune generated files +*.install + +# Local OPAM switch +_opam/ + +# DirEnv cache +.direnv/ + +# Environment variables +*.env + +# Taskfile cache +.task/ diff --git a/ocaml/.ocamlformat b/ocaml/.ocamlformat new file mode 100644 index 0000000..6fdcd22 --- /dev/null +++ b/ocaml/.ocamlformat @@ -0,0 +1,3 @@ +profile = ocamlformat +version = 0.28.1 + diff --git a/ocaml/CODEOWNERS b/ocaml/CODEOWNERS new file mode 100644 index 0000000..66c50a2 --- /dev/null +++ b/ocaml/CODEOWNERS @@ -0,0 +1 @@ +* @djoleary diff --git a/ocaml/CONTRIBUTING.md b/ocaml/CONTRIBUTING.md new file mode 100644 index 0000000..832b94b --- /dev/null +++ b/ocaml/CONTRIBUTING.md @@ -0,0 +1,5 @@ +# Contributing + +## Commit Messages + +We use conventional commits for all commit messages. See diff --git a/ocaml/LICENSE b/ocaml/LICENSE new file mode 100644 index 0000000..1e9858b --- /dev/null +++ b/ocaml/LICENSE @@ -0,0 +1,25 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + diff --git a/ocaml/README.md b/ocaml/README.md new file mode 100644 index 0000000..772c76b --- /dev/null +++ b/ocaml/README.md @@ -0,0 +1,54 @@ +# OCaml Template + +This is a template for OCaml projects. + +## Quick Start + +1. Fork this repository +2. Rename the repository to your project name +3. Replace all instances of `advent_of_code` with your project name, both in files and filenames themselves. + +## Project Structure + +```txt +. +โ”œโ”€โ”€ _build # build outputs +โ”œโ”€โ”€ bin # program entry points +โ”œโ”€โ”€ docs # documentation +โ”œโ”€โ”€ lib # public modules +โ”œโ”€โ”€ test # tests +โ”œโ”€โ”€ .editorconfig +โ”œโ”€โ”€ .envrc +โ”œโ”€โ”€ .gitignore +โ”œโ”€โ”€ .ocamlformat # ocamlformat configuration +โ”œโ”€โ”€ CODEOWNERS +โ”œโ”€โ”€ CONTRIBUTING.md +โ”œโ”€โ”€ LICENSE +โ”œโ”€โ”€ README.md +โ”œโ”€โ”€ Taskfile.yml # task runner configuration similar to make +โ”œโ”€โ”€ dune-project # dune project configuration +โ”œโ”€โ”€ flake.lock +โ”œโ”€โ”€ flake.nix # nix flake configuration +โ”œโ”€โ”€ advent_of_code.install # autogenerated install script +โ””โ”€โ”€ advent_of_code.opam # autogenerated project description +``` + +## Tools + +- [task](https://taskfile.dev/) +- [dune](https://dune.build/) +- [opam](https://opam.ocaml.org/) +- [ocamlformat](https://ocaml.org/p/ocamlformat/latest/doc/index.html) +- [odoc](https://ocaml.github.io/odoc/odoc/index.html) +- [alchotest](https://github.com/mirage/alcotest) +- [bisect_ppx](https://github.com/aantron/bisect_ppx) +- [ocaml-lsp](https://github.com/ocaml/ocaml-lsp) +- [earlybird](https://github.com/ocaml-lsp/earlybird) +- [findlib](https://github.com/ocaml/ocamlfind) +- [re2](https://github.com/janestreet/re2) +- [fmt](https://erratique.ch/software/fmt) +- [cmdliner](https://erratique.ch/software/cmdliner) + +## License + +This project is licensed under the [Unlicense](https://unlicense.org/). diff --git a/ocaml/Taskfile.yaml b/ocaml/Taskfile.yaml new file mode 100644 index 0000000..4a4cbe2 --- /dev/null +++ b/ocaml/Taskfile.yaml @@ -0,0 +1,103 @@ +# https://taskfile.dev +version: "3" + +tasks: + + default: + desc: Prints this message + cmds: + - cmd: task --list-all + + docs: + desc: Builds documentation + preconditions: + - command -v ocamldoc + cmds: + - cmd: ocamldoc -html -d docs bin/main.ml + silent: true + + test: + desc: Runs the tests + preconditions: + - command -v dune + deps: + - task: build + cmds: + - cmd: dune runtest {{.CLI_ARGS}} + + test:watch: + desc: Continuously runs the tests + ignore_error: true + sources: + - lib/**/*.ml + - test/**/*.ml + preconditions: + - command -v dune + deps: + - task: build + cmds: + - cmd: dune runtest --watch {{.CLI_ARGS}} + + test:coverage: + desc: Runs the tests and reports coverage + preconditions: + - command -v dune + - command -v bisect-ppx-report + deps: + - task: build + cmds: + - cmd: rm --recursive --force _coverage + - cmd: rm --force _build/default/test/bisect*.coverage + - cmd: dune runtest --instrument-with bisect_ppx --force {{.CLI_ARGS}} + - cmd: bisect-ppx-report html + + test:suite: + desc: Run a singluar test suite, e.g. `task test:suite SUITE=test/domain/d01p01` + preconditions: + - command -v dune + deps: + - task: build + requires: + vars: + - SUITE + cmds: + - cmd: dune exec {{.SUITE}}.exe -- {{.CLI_ARGS}} + + build: + desc: Builds the project + preconditions: + - command -v dune + cmds: + - cmd: dune build + + build:watch: + desc: Builds the project when files change + preconditions: + - command -v dune + cmds: + - cmd: dune build --watch {{.CLI_ARGS}} + + build:release: + desc: Builds a release version the project + preconditions: + - command -v dune + cmds: + - cmd: dune build --release + + run: + desc: Runs the project + preconditions: + - command -v dune + deps: + - task: build + cmds: + - cmd: dune exec -- ./bin/main.exe {{.CLI_ARGS}} + + repl: + desc: Opens a REPL for the project + interactive: true + preconditions: + - command -v dune + - command -v utop + cmds: + - cmd: dune utop diff --git a/ocaml/advent_of_code.opam b/ocaml/advent_of_code.opam new file mode 100644 index 0000000..103f2ce --- /dev/null +++ b/ocaml/advent_of_code.opam @@ -0,0 +1,42 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: "advent of code solutions for 2025" +description: + "project aiming to solve the advent of code puzzles released for 2025" +maintainer: ["DJ O'Leary "] +authors: ["DJ O'Leary "] +license: "Unlicense" +tags: ["Advent of Code"] +homepage: "https://github.com/djoleary/advent_of_code" +doc: "https://github.com/djoleary/advent_of_code/2025/ocaml/docs/index.html" +bug-reports: "https://github.com/djoleary/advent_of_code/issues" +depends: [ + "dune" {>= "3.20"} + "ocaml" {>= "5.2"} + "ppx_deriving" {build} + "alcotest" {with-test} + "qcheck" {with-test} + "bisect_ppx" {with-test} + "ppx_inline_test" {with-test} + "ppx_expect" {with-test} + "odoc" {with-doc} + "cmdliner" + "fmt" + "re2" +] +build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] +dev-repo: "git+https://github.com/djoleary/advent_of_code.git" +x-maintenance-intent: ["(latest)"] diff --git a/ocaml/bin/dune b/ocaml/bin/dune new file mode 100644 index 0000000..0f6768d --- /dev/null +++ b/ocaml/bin/dune @@ -0,0 +1,11 @@ +(executable + (public_name advent_of_code) + (name main) + (libraries + domain + application + infrastructure + presentation + logs + logs.fmt) + (modes byte exe)) diff --git a/ocaml/bin/main.ml b/ocaml/bin/main.ml new file mode 100644 index 0000000..cbfe6d2 --- /dev/null +++ b/ocaml/bin/main.ml @@ -0,0 +1,16 @@ +let env_log_level = + match Sys.getenv_opt "LOG_LEVEL" with + | None -> + Some Logs.Warning + | Some lvl -> ( + match Logs.level_of_string lvl with Error _ -> None | Ok v -> v ) + +let main () = + let log_level = env_log_level in + Logs.set_level log_level ; + Logs.set_reporter (Logs_fmt.reporter ()) ; + Logs.info (fun m -> + m "Started application with LOG_LEVEL=%s" (Logs.level_to_string log_level) ) ; + Presentation.Cli.run () + +let () = if !Sys.interactive then () else exit (main ()) diff --git a/ocaml/docs/Main.html b/ocaml/docs/Main.html new file mode 100644 index 0000000..11d7257 --- /dev/null +++ b/ocaml/docs/Main.html @@ -0,0 +1,18 @@ + + + + + + + + + +Main + + + +

Module Main

+ +
module Main: sig .. end

+ diff --git a/ocaml/docs/index.html b/ocaml/docs/index.html new file mode 100644 index 0000000..f536f1a --- /dev/null +++ b/ocaml/docs/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + +
Main
+ + diff --git a/ocaml/docs/index_attributes.html b/ocaml/docs/index_attributes.html new file mode 100644 index 0000000..3450f40 --- /dev/null +++ b/ocaml/docs/index_attributes.html @@ -0,0 +1,17 @@ + + + + + + + +Index of class attributes + + + +

Index of class attributes

+ +
+ + diff --git a/ocaml/docs/index_class_types.html b/ocaml/docs/index_class_types.html new file mode 100644 index 0000000..be3ecc7 --- /dev/null +++ b/ocaml/docs/index_class_types.html @@ -0,0 +1,17 @@ + + + + + + + +Index of class types + + + +

Index of class types

+ +
+ + diff --git a/ocaml/docs/index_classes.html b/ocaml/docs/index_classes.html new file mode 100644 index 0000000..08c083b --- /dev/null +++ b/ocaml/docs/index_classes.html @@ -0,0 +1,17 @@ + + + + + + + +Index of classes + + + +

Index of classes

+ +
+ + diff --git a/ocaml/docs/index_exceptions.html b/ocaml/docs/index_exceptions.html new file mode 100644 index 0000000..0767720 --- /dev/null +++ b/ocaml/docs/index_exceptions.html @@ -0,0 +1,17 @@ + + + + + + + +Index of exceptions + + + +

Index of exceptions

+ +
+ + diff --git a/ocaml/docs/index_extensions.html b/ocaml/docs/index_extensions.html new file mode 100644 index 0000000..38625df --- /dev/null +++ b/ocaml/docs/index_extensions.html @@ -0,0 +1,17 @@ + + + + + + + +Index of extensions + + + +

Index of extensions

+ +
+ + diff --git a/ocaml/docs/index_methods.html b/ocaml/docs/index_methods.html new file mode 100644 index 0000000..7debe46 --- /dev/null +++ b/ocaml/docs/index_methods.html @@ -0,0 +1,17 @@ + + + + + + + +Index of class methods + + + +

Index of class methods

+ +
+ + diff --git a/ocaml/docs/index_module_types.html b/ocaml/docs/index_module_types.html new file mode 100644 index 0000000..0b75a38 --- /dev/null +++ b/ocaml/docs/index_module_types.html @@ -0,0 +1,17 @@ + + + + + + + +Index of module types + + + +

Index of module types

+ +
+ + diff --git a/ocaml/docs/index_modules.html b/ocaml/docs/index_modules.html new file mode 100644 index 0000000..b6588a3 --- /dev/null +++ b/ocaml/docs/index_modules.html @@ -0,0 +1,20 @@ + + + + + + + +Index of modules + + + +

Index of modules

+ + + + +
M
Main
+ + diff --git a/ocaml/docs/index_types.html b/ocaml/docs/index_types.html new file mode 100644 index 0000000..61efcda --- /dev/null +++ b/ocaml/docs/index_types.html @@ -0,0 +1,17 @@ + + + + + + + +Index of types + + + +

Index of types

+ +
+ + diff --git a/ocaml/docs/index_values.html b/ocaml/docs/index_values.html new file mode 100644 index 0000000..fc7294b --- /dev/null +++ b/ocaml/docs/index_values.html @@ -0,0 +1,17 @@ + + + + + + + +Index of values + + + +

Index of values

+ +
+ + diff --git a/ocaml/docs/style.css b/ocaml/docs/style.css new file mode 100644 index 0000000..259bd49 --- /dev/null +++ b/ocaml/docs/style.css @@ -0,0 +1,43 @@ +.keyword { font-weight : bold ; color : Red } +.keywordsign { color : #C04600 } +.comment { color : Green } +.constructor { color : Blue } +.type { color : #5C6585 } +.string { color : Maroon } +.warning { color : Red ; font-weight : bold } +.info { margin-left : 3em; margin-right: 3em } +.param_info { margin-top: 4px; margin-left : 3em; margin-right : 3em } +.code { color : #465F91 ; } +.typetable { border-style : hidden } +.paramstable { border-style : hidden ; padding: 5pt 5pt} +tr { background-color : White } +td.typefieldcomment { background-color : #FFFFFF ; font-size: smaller ;} +div.sig_block {margin-left: 2em} +*:target { background: yellow; } +body {font: 13px sans-serif; color: black; text-align: left; padding: 5px; margin: 0} +h1 { font-size : 20pt ; text-align: center; } +h2 { font-size : 20pt ; text-align: center; } +h3 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90BDFF ;padding: 2px; } +h4 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90DDFF ;padding: 2px; } +h5 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90EDFF ;padding: 2px; } +h6 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90FDFF ;padding: 2px; } +div.h7 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90BDFF ; padding: 2px; } +div.h8 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #E0FFFF ; padding: 2px; } +div.h9 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #F0FFFF ; padding: 2px; } +div.h10 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #FFFFFF ; padding: 2px; } +a {color: #416DFF; text-decoration: none} +a:hover {background-color: #ddd; text-decoration: underline} +pre { margin-bottom: 4px; font-family: monospace; } +pre.verbatim, pre.codepre { } +.indextable {border: 1px #ddd solid; border-collapse: collapse} +.indextable td, .indextable th {border: 1px #ddd solid; min-width: 80px} +.indextable td.module {background-color: #eee ; padding-left: 2px; padding-right: 2px} +.indextable td.module a {color: #4E6272; text-decoration: none; display: block; width: 100%} +.indextable td.module a:hover {text-decoration: underline; background-color: transparent} +.deprecated {color: #888; font-style: italic} +.indextable tr td div.info { margin-left: 2px; margin-right: 2px } +ul.indexlist { margin-left: 0; padding-left: 0;} +ul.indexlist li { list-style-type: none ; margin-left: 0; padding-left: 0; } +ul.info-attributes {list-style: none; margin: 0; padding: 0; } +div.info > p:first-child { margin-top:0; } +div.info-desc > p:first-child { margin-top:0; margin-bottom:0; } \ No newline at end of file diff --git a/ocaml/docs/type_Main.html b/ocaml/docs/type_Main.html new file mode 100644 index 0000000..43bb69c --- /dev/null +++ b/ocaml/docs/type_Main.html @@ -0,0 +1,10 @@ + + + + + + +Main + + +sig end diff --git a/ocaml/dune-project b/ocaml/dune-project new file mode 100644 index 0000000..731f4b5 --- /dev/null +++ b/ocaml/dune-project @@ -0,0 +1,40 @@ +(lang dune 3.20) + +(name advent_of_code) + +(generate_opam_files true) + +(source + (github djoleary/advent_of_code)) + +(authors "DJ O'Leary ") + +(maintainers "DJ O'Leary ") + +(license Unlicense) + +(documentation https://github.com/djoleary/advent_of_code/2025/ocaml/docs/index.html) + +(package + (name advent_of_code) + (synopsis "advent of code solutions for 2025") + (description "project aiming to solve the advent of code puzzles released for 2025") + (depends + (ocaml (>= 5.2)) + (ppx_deriving :build) + (alcotest :with-test) + (qcheck :with-test) + (bisect_ppx :with-test) + (ppx_inline_test :with-test) + (ppx_expect :with-test) + (odoc :with-doc) + cmdliner + fmt + re2 + ) + (tags + ("Advent of Code"))) + +(map_workspace_root false) + +; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html diff --git a/ocaml/flake.lock b/ocaml/flake.lock new file mode 100644 index 0000000..3baeb69 --- /dev/null +++ b/ocaml/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1763421233, + "narHash": "sha256-Stk9ZYRkGrnnpyJ4eqt9eQtdFWRRIvMxpNRf4sIegnw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "89c2b2330e733d6cdb5eae7b899326930c2c0648", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/ocaml/flake.nix b/ocaml/flake.nix new file mode 100644 index 0000000..54d7b3f --- /dev/null +++ b/ocaml/flake.nix @@ -0,0 +1,52 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + + outputs = + { nixpkgs, ... }: + let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + in + { + devShells.x86_64-linux.default = + let + ocamlPackages = with pkgs.ocaml-ng.ocamlPackages_5_2; [ + ocaml + + dune_3 # build system + + pkgs.opam # package manager + findlib # library manager + + ocaml-lsp # language server + earlybird # debug adapter + ocamlformat # formatter + + utop # REPL + + odoc # documentation generator + + alcotest # unit test framework + bisect_ppx # test coverage + qcheck-core # property-based testing + qcheck-alcotest # property-based testing + janeStreet.ppx_inline_test # inline test library + janeStreet.ppx_expect # inline test library + + janeStreet.re2 # regular expression library + fmt # pretty printing library + cmdliner # command line library + ppx_deriving # derivers + logs # logging library + ]; + sysTools = with pkgs; [ + go-task + gh + ]; + in + pkgs.mkShell { + buildInputs = ocamlPackages ++ sysTools; + }; + }; +} \ No newline at end of file diff --git a/ocaml/lib/application/d01p01.ml b/ocaml/lib/application/d01p01.ml new file mode 100644 index 0000000..206e757 --- /dev/null +++ b/ocaml/lib/application/d01p01.ml @@ -0,0 +1,53 @@ +open Domain.D01p01 + +let err_empty_input = "input is empty" + +let rec input_to_lines input = + let lines = + String.split_on_char '\n' input |> List.filter (fun l -> l <> "") + in + pp_lines lines ; lines + +and pp_lines = function + | [] -> + () + | h :: t -> + Logs.debug (fun m -> m "Line=%s" h) ; + pp_lines t + +let parse_line line = + Scanf.sscanf line "%c%d" (fun d n -> + match d with + | 'L' -> + Dial.Left n + | 'R' -> + Dial.Right n + | _ -> + failwith "invalid direction" ) + +let lines_to_turns = + List.map (fun (l : string) -> + Logs.debug (fun m -> m "parsing %s into turn" l) ; + let turn = parse_line l in + Logs.debug (fun m -> m "%s" (Dial.pp_direction turn)) ; + turn ) + +let rec turn_aux count current = function + | [] -> + count + | h :: t -> + let current' = Dial.turn current h in + Logs.debug (fun m -> m "dial at %d" (Dial.to_int current')) ; + if Dial.to_int current' = 0 then turn_aux (count + 1) current' t + else turn_aux count current' t + +let turn current = turn_aux 0 current + +let solve input () = + if input = "" then Error err_empty_input + else + let lines = input_to_lines input in + let turns = lines_to_turns lines in + let start = Dial.start in + let answer = turn start turns in + Ok answer diff --git a/ocaml/lib/application/d01p01.mli b/ocaml/lib/application/d01p01.mli new file mode 100644 index 0000000..2eecf19 --- /dev/null +++ b/ocaml/lib/application/d01p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the first day of advent of code 2025 for [input]. + The solution for an empty input is [None]. + Requires: [input] to be a multiline string using `\n` as it's line separator. *) diff --git a/ocaml/lib/application/d01p02.ml b/ocaml/lib/application/d01p02.ml new file mode 100644 index 0000000..7910486 --- /dev/null +++ b/ocaml/lib/application/d01p02.ml @@ -0,0 +1,55 @@ +open Domain.D01p02 + +let err_empty_input = "input is empty" + +let rec input_to_lines input = + let lines = + String.split_on_char '\n' input |> List.filter (fun l -> l <> "") + in + pp_lines lines ; lines + +and pp_lines = function + | [] -> + () + | h :: t -> + Logs.debug (fun m -> m "Line=%s" h) ; + pp_lines t + +let parse_line line = + Scanf.sscanf line "%c%d" (fun d n -> + match d with + | 'L' -> + Dial.Left n + | 'R' -> + Dial.Right n + | _ -> + failwith "invalid direction" ) + +let lines_to_turns = + List.map (fun (l : string) -> + Logs.debug (fun m -> m "parsing %s into turn" l) ; + let turn = parse_line l in + Logs.debug (fun m -> m "%s" (Dial.pp_direction turn)) ; + turn ) + +let rec turn_aux count dial = function + | [] -> + count + | h :: t -> ( + let dial_and_count = Turner.turn dial count h in + match dial_and_count with + | dial', count' -> + Logs.debug (fun m -> + m "(dial=%d, count=%d)" (Dial.to_int dial') count' ) ; + turn_aux count' dial' t ) + +let turn current = turn_aux 0 current + +let solve input () = + if input = "" then Error err_empty_input + else + let lines = input_to_lines input in + let turns = lines_to_turns lines in + let start = Dial.start in + let answer = turn start turns in + Ok answer diff --git a/ocaml/lib/application/d01p02.mli b/ocaml/lib/application/d01p02.mli new file mode 100644 index 0000000..9e9a2f2 --- /dev/null +++ b/ocaml/lib/application/d01p02.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the second part of the first day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a multiline string using `\n` as it's line separator. *) diff --git a/ocaml/lib/application/d02p01.ml b/ocaml/lib/application/d02p01.ml new file mode 100644 index 0000000..f313149 --- /dev/null +++ b/ocaml/lib/application/d02p01.ml @@ -0,0 +1,54 @@ +open Domain.D02p01 + +let err_empty_input = "input is empty" + +(** [input_to_lines input] is a list of strings that have been split on ','. + Example: [input_to_lines "12-34,56-78,90-100"] becomes the list \[ "12-34"; "56-78"; "90-100" \]. + Requires: [input] to be a string in the format "12-34,56-78,90-100". *) +let input_to_lines input = input |> String.trim |> String.split_on_char ',' + +let pp_lines = Seq.iter (fun l -> Logs.debug (fun m -> m "line=%s" l)) + +(** [lines_to_ranges line] is a list of [Range]s found on [line]. *) +let lines_to_ranges = + Seq.map (fun line -> + Scanf.sscanf line "%d-%d" (fun id1 id2 -> Range.create (id1, id2)) ) + +let pp_ranges = Seq.iter (fun r -> Logs.debug (fun m -> m "%s" (Range.pp r))) + +(** [collect_all ranges] is an unsorted list of [ID]s within the [ranges] (inclusive). *) +let collect_all = + Seq.concat_map (fun range -> List.to_seq (Range.collect range)) + +let pp_collection = Seq.iter (fun c -> Logs.debug (fun m -> m "%s" (ID.pp c))) + +(** [rules] is a list of rules that [ID]s must pass to be considered invalid. *) +let rules = + [ (fun id -> String.length (ID.to_string id) mod 2 = 0) + ; (fun id -> + let id' = ID.to_string id in + let id_len = String.length id' in + let left = String.sub id' 0 (id_len / 2) in + let right = String.sub id' (id_len / 2) (id_len / 2) in + String.equal left right ) ] + +(** [is_invalid id] is [true] for an [id] that passes all of the rules and is considered invalid. *) +let is_invalid = ID.validate rules + +(** [passthrough_with_logs logger lst] is [lst] with the side-effects caused by calling [logger] on [lst]. *) +let passthrough_with_logs logger lst = logger lst ; lst + +let solve input () = + if input = "" then Error err_empty_input + else + let answer = + input |> input_to_lines |> List.to_seq + |> passthrough_with_logs pp_lines + |> lines_to_ranges + |> passthrough_with_logs pp_ranges + |> collect_all + |> passthrough_with_logs pp_collection + |> Seq.filter is_invalid + |> Seq.fold_left (fun acc id -> acc + ID.to_int id) 0 + in + Ok answer diff --git a/ocaml/lib/application/d02p01.mli b/ocaml/lib/application/d02p01.mli new file mode 100644 index 0000000..8a8eb32 --- /dev/null +++ b/ocaml/lib/application/d02p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the second day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d02p02.ml b/ocaml/lib/application/d02p02.ml new file mode 100644 index 0000000..d062f7b --- /dev/null +++ b/ocaml/lib/application/d02p02.ml @@ -0,0 +1,86 @@ +open Domain.D02p02 + +(* Errors *) + +let err_empty_input = "input is empty" + +(* Logging *) + +(** [passthrough_with_logs logger lst] is [lst] with the side-effects caused by calling [logger] on [lst]. *) +let passthrough_with_logs logger lst = logger lst ; lst + +(** [input_to_lines input] is a list of strings that have been split on ','. + Example: [input_to_lines "12-34,56-78,90-100"] becomes the list \[ "12-34"; "56-78"; "90-100" \]. + Requires: [input] to be a string in the format "12-34,56-78,90-100". *) +let input_to_lines input = input |> String.trim |> String.split_on_char ',' + +let pp_lines = Seq.iter (fun l -> Logs.debug (fun m -> m "line=%s" l)) + +let pp_ranges = Seq.iter (fun r -> Logs.debug (fun m -> m "%s" (Range.pp r))) + +let pp_collection = Seq.iter (fun c -> Logs.debug (fun m -> m "%s" (ID.pp c))) + +(* Core Logic *) + +(** [lines_to_ranges line] is a list of [Range]s found on [line]. *) +let lines_to_ranges = + Seq.map (fun line -> + Scanf.sscanf line "%d-%d" (fun id1 id2 -> Range.create (id1, id2)) ) + +(** [collect_all ranges] is an unsorted list of [ID]s within the [ranges] (inclusive). *) +let collect_all = + Seq.concat_map (fun range -> List.to_seq (Range.collect range)) + +(** [string_to_segments count string] is a list containing [count] string segments with of equal length. + Requires: length of [string] to be divisible by [count] + and [count] to be greater than or equal to zero *) +let rec string_to_segments s = + let segment_lengths = possible_segment_lengths s in + List.map (fun c -> string_to_segments_aux [] c s |> List.rev) segment_lengths + +(** [possible_segment_lengths s] is a list of segment sizes that fit within [s] + that must be checked to rule out a repeating pattern. *) +and possible_segment_lengths s = + List.init (String.length s) (fun x -> x + 1) + |> List.filter (fun x -> x != 1 && String.length s mod x = 0) + +and string_to_segments_aux segments count string = + match List.length segments = count with + | true -> + segments + | false -> + let segment_start = String.length string / count * List.length segments in + let segment_len = String.length string / count in + string_to_segments_aux + (String.sub string segment_start segment_len :: segments) + count string + +(** [cmp_segments segments] is true is all segments are equal *) +let cmp_segments segments = + List.length (List.sort_uniq String.compare segments) = 1 + +(** [is_invalid id] is [true] for an [id] that is considered invalid. *) +let is_invalid id = + Logs.debug (fun m -> m "CHECKING ID=%s" id) ; + let repeating_patterns = + id |> string_to_segments |> List.filter cmp_segments + in + List.iter + (fun l -> Logs.debug (fun m -> m "HAS SUBPATTERN=%s" (List.hd l))) + repeating_patterns ; + List.length repeating_patterns > 0 + +let solve input () = + if input = "" then Error err_empty_input + else + let answer = + input |> input_to_lines |> List.to_seq + |> passthrough_with_logs pp_lines + |> lines_to_ranges + |> passthrough_with_logs pp_ranges + |> collect_all + |> passthrough_with_logs pp_collection + |> Seq.map ID.to_string |> Seq.filter is_invalid + |> Seq.fold_left (fun acc id -> acc + int_of_string id) 0 + in + Ok answer diff --git a/ocaml/lib/application/d02p02.mli b/ocaml/lib/application/d02p02.mli new file mode 100644 index 0000000..8a8eb32 --- /dev/null +++ b/ocaml/lib/application/d02p02.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the second day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d03p01.ml b/ocaml/lib/application/d03p01.ml new file mode 100644 index 0000000..522f5d0 --- /dev/null +++ b/ocaml/lib/application/d03p01.ml @@ -0,0 +1,23 @@ +open Domain.D03p01 + +let err_empty_input = "input is empty" + +let to_lines s = + s |> String.split_on_char '\n' |> List.map String.trim + |> List.filter (fun l -> l <> "") + +let solve input () = + if input = "" then Error err_empty_input + else + let answer = + input |> to_lines |> List.to_seq + |> Seq.map BatteryBank.of_string + |> Seq.mapi (fun idx bb -> + let joltage = BatteryBank.highest_joltage bb in + Logs.debug (fun m -> + m "%d line=%s highest_joltage=%d" (idx + 1) (BatteryBank.pp bb) + joltage ) ; + joltage ) + |> Seq.fold_left ( + ) 0 + in + Ok answer diff --git a/ocaml/lib/application/d03p01.mli b/ocaml/lib/application/d03p01.mli new file mode 100644 index 0000000..31dd0cd --- /dev/null +++ b/ocaml/lib/application/d03p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the third day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d03p02.ml b/ocaml/lib/application/d03p02.ml new file mode 100644 index 0000000..743c658 --- /dev/null +++ b/ocaml/lib/application/d03p02.ml @@ -0,0 +1,23 @@ +open Domain.D03p02 + +let err_empty_input = "input is empty" + +let to_lines s = + s |> String.split_on_char '\n' |> List.map String.trim + |> List.filter (fun l -> l <> "") + +let solve input () = + if input = "" then Error err_empty_input + else + let answer = + input |> to_lines |> List.to_seq + |> Seq.map BatteryBank.of_string + |> Seq.mapi (fun idx bb -> + let joltage = BatteryBank.highest_joltage bb in + Logs.debug (fun m -> + m "%d line=%s highest_joltage=%d" (idx + 1) (BatteryBank.pp bb) + joltage ) ; + joltage ) + |> Seq.fold_left ( + ) 0 + in + Ok answer diff --git a/ocaml/lib/application/d03p02.mli b/ocaml/lib/application/d03p02.mli new file mode 100644 index 0000000..93d3b98 --- /dev/null +++ b/ocaml/lib/application/d03p02.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the second part of the third day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d04p01.ml b/ocaml/lib/application/d04p01.ml new file mode 100644 index 0000000..7c69de4 --- /dev/null +++ b/ocaml/lib/application/d04p01.ml @@ -0,0 +1,48 @@ +open Domain + +let err_empty_input = "input is empty" + +let rec solve input () = + if input = "" then Error err_empty_input + else + let matrix = Matrix.M.of_string input in + let count_paper lst = List.length @@ List.filter (fun c -> c <> ".") lst in + let accessible_rolls_of_paper = ref 0 in + for y = 0 to Array.length matrix - 1 do + for x = 0 to Array.length matrix.(y) - 1 do + if matrix.(y).(x) = "." then () + else + let neighbour_coords = Matrix.M.get_valid_neighbours matrix x y in + let neighbours = + List.map + (fun (x', y') -> Matrix.M.get matrix x' y') + neighbour_coords + in + let paper_count = count_paper neighbours in + let is_accessible = paper_count < 4 in + if is_accessible then + accessible_rolls_of_paper := !accessible_rolls_of_paper + 1 ; + print_iteration_debug_msg x y neighbour_coords neighbours paper_count + is_accessible + done + done ; + Ok !accessible_rolls_of_paper + +and print_iteration_debug_msg x y neighbour_coords neighbours paper_count + is_accessible = + Logs.debug (fun m -> + m + "coords=(%d,%d) neighbour_coords=[%s] neighbours=[%s] paper=%d \ + is_accessible=%B" + x y + (List.fold_left + (fun acc (x, y) -> + if acc = "" then + " (" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " + else acc ^ "(" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " ) + "" neighbour_coords ) + (List.fold_left + (fun acc neighbour -> + if acc = "" then " " ^ neighbour ^ "; " else acc ^ neighbour ^ "; " ) + "" neighbours ) + paper_count is_accessible ) diff --git a/ocaml/lib/application/d04p01.mli b/ocaml/lib/application/d04p01.mli new file mode 100644 index 0000000..873acff --- /dev/null +++ b/ocaml/lib/application/d04p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the fourth day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d04p02.ml b/ocaml/lib/application/d04p02.ml new file mode 100644 index 0000000..75b1236 --- /dev/null +++ b/ocaml/lib/application/d04p02.ml @@ -0,0 +1,64 @@ +open Domain + +let err_empty_input = "input is empty" + +let rec count_accessible_rolls matrix = + let count_paper lst = List.length @@ List.filter (fun c -> c = "@") lst in + let accessible_rolls_of_paper = ref [] in + let no_change = ref false in + while not !no_change do + let accessible_rolls_of_paper' = !accessible_rolls_of_paper in + count_accessible_rolls_aux matrix count_paper accessible_rolls_of_paper ; + if + List.length accessible_rolls_of_paper' + = List.length !accessible_rolls_of_paper + then no_change := true + done ; + List.length !accessible_rolls_of_paper + +and count_accessible_rolls_aux matrix count_paper accessible_rolls_of_paper = + for y = 0 to Array.length matrix - 1 do + for x = 0 to Array.length matrix.(y) - 1 do + if matrix.(y).(x) = "." then () + else + let neighbour_coords = Matrix.M.get_valid_neighbours matrix x y in + let neighbours = + List.map (fun (nx, ny) -> Matrix.M.get matrix nx ny) neighbour_coords + in + let paper_count = count_paper neighbours in + let is_accessible = paper_count < 4 in + if is_accessible then + accessible_rolls_of_paper := (x, y) :: !accessible_rolls_of_paper ; + print_iteration_debug_msg x y neighbour_coords neighbours paper_count + is_accessible + done + done ; + List.iter + (fun (x', y') -> Matrix.M.set matrix x' y' ".") + !accessible_rolls_of_paper + +and print_iteration_debug_msg x y neighbour_coords neighbours paper_count + is_accessible = + Logs.debug (fun m -> + m + "coords=(%d,%d) neighbour_coords=[%s] neighbours=[%s] paper=%d \ + is_accessible=%B" + x y + (List.fold_left + (fun acc (x, y) -> + if acc = "" then + " (" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " + else acc ^ "(" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " ) + "" neighbour_coords ) + (List.fold_left + (fun acc neighbour -> + if acc = "" then " " ^ neighbour ^ "; " else acc ^ neighbour ^ "; " ) + "" neighbours ) + paper_count is_accessible ) + +let solve input () = + if input = "" then Error err_empty_input + else + let matrix = Matrix.M.of_string input in + let accessible_rolls_of_paper = count_accessible_rolls matrix in + Ok accessible_rolls_of_paper diff --git a/ocaml/lib/application/d04p02.mli b/ocaml/lib/application/d04p02.mli new file mode 100644 index 0000000..388ec56 --- /dev/null +++ b/ocaml/lib/application/d04p02.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the second part of the fourth day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d05p01.ml b/ocaml/lib/application/d05p01.ml new file mode 100644 index 0000000..4bf79ab --- /dev/null +++ b/ocaml/lib/application/d05p01.ml @@ -0,0 +1,3 @@ +let err_empty_input = "input is empty" + +let solve input () = if input = "" then Error err_empty_input else Ok 0 diff --git a/ocaml/lib/application/d05p01.mli b/ocaml/lib/application/d05p01.mli new file mode 100644 index 0000000..85c3dd5 --- /dev/null +++ b/ocaml/lib/application/d05p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the five day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d06p01.ml b/ocaml/lib/application/d06p01.ml new file mode 100644 index 0000000..9425810 --- /dev/null +++ b/ocaml/lib/application/d06p01.ml @@ -0,0 +1,50 @@ +let err_empty_input = "input is empty" + +let log_matrix matrix = + let open Domain.Matrix in + Logs.debug (fun m -> m "%s" (M.of_list matrix |> M.pp (fun elt -> elt))) + +let log_list lst = + Logs.debug (fun m -> + m "[%s]" (List.fold_left (fun acc i -> acc ^ i ^ "; ") " " lst) ) + +type operation = Mult | Add + +let solve input () = + if input = "" then Error err_empty_input + else + let input' = Parser.columns_of_string input in + assert (input' <> []) ; + log_matrix input' ; + let nums = + List.map (List.filter (fun i -> i <> "*" && i <> "+")) input' + |> List.map + @@ List.map (fun n -> + Logs.debug (fun m -> m "peeking: %s" n) ; + int_of_string n ) + and ops = + List.map (List.filter (fun i -> i = "*" || i = "+")) input' + |> List.concat + |> List.map (fun op -> + match op with + | "*" -> + Mult + | "+" -> + Add + | _ -> + failwith "case not covered" ) + in + let answer = + List.map2 + (fun op num_lst -> + match op with + | Add -> + List.fold_left (fun acc i -> acc + i) 0 num_lst + | Mult -> + List.fold_left (fun acc i -> acc * i) 1 num_lst ) + ops nums + |> fun lst -> + log_list @@ List.map string_of_int lst ; + lst |> List.fold_left ( + ) 0 + in + Ok answer diff --git a/ocaml/lib/application/d06p01.mli b/ocaml/lib/application/d06p01.mli new file mode 100644 index 0000000..b3abc1e --- /dev/null +++ b/ocaml/lib/application/d06p01.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the first part of the sixth day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/d06p02.ml b/ocaml/lib/application/d06p02.ml new file mode 100644 index 0000000..9425810 --- /dev/null +++ b/ocaml/lib/application/d06p02.ml @@ -0,0 +1,50 @@ +let err_empty_input = "input is empty" + +let log_matrix matrix = + let open Domain.Matrix in + Logs.debug (fun m -> m "%s" (M.of_list matrix |> M.pp (fun elt -> elt))) + +let log_list lst = + Logs.debug (fun m -> + m "[%s]" (List.fold_left (fun acc i -> acc ^ i ^ "; ") " " lst) ) + +type operation = Mult | Add + +let solve input () = + if input = "" then Error err_empty_input + else + let input' = Parser.columns_of_string input in + assert (input' <> []) ; + log_matrix input' ; + let nums = + List.map (List.filter (fun i -> i <> "*" && i <> "+")) input' + |> List.map + @@ List.map (fun n -> + Logs.debug (fun m -> m "peeking: %s" n) ; + int_of_string n ) + and ops = + List.map (List.filter (fun i -> i = "*" || i = "+")) input' + |> List.concat + |> List.map (fun op -> + match op with + | "*" -> + Mult + | "+" -> + Add + | _ -> + failwith "case not covered" ) + in + let answer = + List.map2 + (fun op num_lst -> + match op with + | Add -> + List.fold_left (fun acc i -> acc + i) 0 num_lst + | Mult -> + List.fold_left (fun acc i -> acc * i) 1 num_lst ) + ops nums + |> fun lst -> + log_list @@ List.map string_of_int lst ; + lst |> List.fold_left ( + ) 0 + in + Ok answer diff --git a/ocaml/lib/application/d06p02.mli b/ocaml/lib/application/d06p02.mli new file mode 100644 index 0000000..49444de --- /dev/null +++ b/ocaml/lib/application/d06p02.mli @@ -0,0 +1,4 @@ +val solve : string -> unit -> (int, string) result +(** [solve input ()] is the solution for the second part of the sixth day of advent of code 2025 for [input]. + The solution for an empty input is [Error msg]. + Requires: [input] to be a string. *) diff --git a/ocaml/lib/application/dune b/ocaml/lib/application/dune new file mode 100644 index 0000000..b78613a --- /dev/null +++ b/ocaml/lib/application/dune @@ -0,0 +1,14 @@ +(library + (name application) + (libraries + domain + fmt + logs) + (inline_tests) + (preprocess + (pps + ppx_deriving.show + ppx_deriving.eq + ppx_deriving.ord + ppx_inline_test + ppx_expect))) diff --git a/ocaml/lib/application/parser.ml b/ocaml/lib/application/parser.ml new file mode 100644 index 0000000..099900b --- /dev/null +++ b/ocaml/lib/application/parser.ml @@ -0,0 +1,36 @@ +(** [rows_of_string s] is a list of strings where each element is a row of [s]. *) +let rows_of_string s = + s |> String.split_on_char '\n' + |> List.filter_map (fun l -> + let trimmed = String.trim l in + if trimmed <> "" then Some trimmed else None ) + +(** [columns_of_string ?sep s] is a list of strings where each element is + a column of [s] where each column is separated by [sep]. *) +let rec columns_of_string ?(sep = ' ') s = + rows_of_string s + |> List.map (fun l -> + l |> String.split_on_char sep |> List.filter (fun l -> l <> "") ) + |> transpose + +(** [group_columns lst] is a list where the first element of each + sublist of [lst] is grouped in [acc] and so on for the rest of the elements. + Example: [group_columns [] \[\["abc"; "def"\]; \["ghi"; "jkl"\]\]] is the list [\[\["abc";"ghi"\];\["def";"jkl"\]\]]. *) +and transpose lst = + assert (lst <> []) ; + if List.mem [] lst then [] + else List.map List.hd lst :: transpose (List.map List.tl lst) + +let%expect_test "rows correctly parsed" = + let have = {| abc def + ghi jkl |} in + let rows = rows_of_string have in + List.iter (fun c -> print_string (c ^ " ")) rows ; + [%expect {| abc def ghi jkl |}] + +let%expect_test "columns correctly parsed" = + let have = {| abc def + ghi jkl |} in + let cols = columns_of_string have in + List.iter (fun row -> List.iter (fun c -> print_string (c ^ " ")) row) cols ; + [%expect {| abc ghi def jkl |}] diff --git a/ocaml/lib/domain/d01p01.ml b/ocaml/lib/domain/d01p01.ml new file mode 100644 index 0000000..6b531f4 --- /dev/null +++ b/ocaml/lib/domain/d01p01.ml @@ -0,0 +1,76 @@ +module Dial : sig + (** [t] is a dial between 0 and 99 inclusive *) + type t + + (** [direction] is a direction the dial can be turned *) + type direction = Left of int | Right of int + + val start : t + (** [start] is the initial state of the dial *) + + val dial_out_of_range : string + (** [dial_out_of_range] is the error message when the dial is out of range *) + + val turn : t -> direction -> t + (** [turn current direction] is the new state of the dial after turning [direction] from [current] *) + + val to_string : t -> string + (** [to_string t] is the string representation of [t] *) + + val to_int : t -> int + (** [to_int t] is the integer representation of [t] *) + + val pp_direction : direction -> string +end = struct + (** [t] is an int between 0 and 99 inclusive *) + type t = int + + type direction = Left of int | Right of int + + let start = 50 + + let dial_out_of_range = "dial out of range" + + (** [next n] is the new state of the dial after turning [Right]. + Raises: [dial_out_of_range] if [n] is greater than 99. *) + let next = function + | n when n < 99 -> + n + 1 + | 99 -> + 0 + | _ -> + failwith dial_out_of_range + + (** [prev n] is the new state of the dial after turning [Left]. + Raises: [dial_out_of_range] if [n] is less than 0. *) + let prev = function + | n when n > 0 -> + n - 1 + | 0 -> + 99 + | _ -> + failwith dial_out_of_range + + (** [turn current direction] is the new state of the dial after turning [direction] from [current]. + Raises: [dial_out_of_range] if the dial is moved to an invalid state. *) + let rec turn current direction = + match direction with + | Left 0 -> + current + | Left n -> + turn (prev current) (Left (n - 1)) + | Right 0 -> + current + | Right n -> + turn (next current) (Right (n - 1)) + + let to_string = string_of_int + + let to_int t = t + + let pp_direction = function + | Left n -> + "Left " ^ string_of_int n + | Right n -> + "Right " ^ string_of_int n +end diff --git a/ocaml/lib/domain/d01p02.ml b/ocaml/lib/domain/d01p02.ml new file mode 100644 index 0000000..f71e965 --- /dev/null +++ b/ocaml/lib/domain/d01p02.ml @@ -0,0 +1,95 @@ +module Dial : sig + (** [t] is a dial between 0 and 99 inclusive *) + type t + + (** [direction] is a direction the dial can be turned *) + type direction = Left of int | Right of int + + val start : t + (** [start] is the initial state of the dial *) + + val dial_out_of_range : string + (** [dial_out_of_range] is the error message when the dial is out of range *) + + val prev : t -> t + + val next : t -> t + + val is_zero : t -> bool + + val to_string : t -> string + (** [to_string t] is the string representation of [t] *) + + val to_int : t -> int + (** [to_int t] is the integer representation of [t] *) + + val pp_direction : direction -> string +end = struct + (** [t] is an int between 0 and 99 inclusive *) + type t = int + + type direction = Left of int | Right of int + + let start = 50 + + let dial_out_of_range = "dial out of range" + + (** [next n] is the new state of the dial after turning [Right]. + Raises: [dial_out_of_range] if [n] is greater than 99. *) + let next = function + | n when n < 99 -> + n + 1 + | 99 -> + 0 + | _ -> + failwith dial_out_of_range + + (** [prev n] is the new state of the dial after turning [Left]. + Raises: [dial_out_of_range] if [n] is less than 0. *) + let prev = function + | n when n > 0 -> + n - 1 + | 0 -> + 99 + | _ -> + failwith dial_out_of_range + + let is_zero = function 0 -> true | _ -> false + + let to_string = string_of_int + + let to_int t = t + + let pp_direction = function + | Left n -> + "Left " ^ string_of_int n + | Right n -> + "Right " ^ string_of_int n +end + +module Turner : sig + val turn : Dial.t -> int -> Dial.direction -> Dial.t * int + (** [turn dial direction] is the new state of the dial after turning [direction] on [dial] *) +end = struct + (** [turn dial direction] is the new state of the dial after turning [direction] on [dial]. + Raises: [dial_out_of_range] if the dial is moved to an invalid state. *) + let rec turn dial zero_count direction = + let open Dial in + match direction with + | Left 0 -> + (dial, zero_count) + | Left n -> + let dial' = prev dial in + let zero_count' = + if Dial.is_zero dial' then zero_count + 1 else zero_count + in + turn dial' zero_count' (Left (n - 1)) + | Right 0 -> + (dial, zero_count) + | Right n -> + let dial' = next dial in + let zero_count' = + if Dial.is_zero dial' then zero_count + 1 else zero_count + in + turn dial' zero_count' (Right (n - 1)) +end diff --git a/ocaml/lib/domain/d02p01.ml b/ocaml/lib/domain/d02p01.ml new file mode 100644 index 0000000..725d4b1 --- /dev/null +++ b/ocaml/lib/domain/d02p01.ml @@ -0,0 +1,33 @@ +module ID = struct + (** *) + type t = int + + let create num = num + + let validate rules id = List.fold_left (fun acc r -> acc && r id) true rules + + let incr = ( + ) 1 + + let to_int id = id + + let to_string = string_of_int + + let pp id = "ID=" ^ string_of_int id +end + +module Range = struct + type t = ID.t * ID.t + + let create (id1, id2) = (ID.create id1, ID.create id2) + + let rec collect range = collect_range_aux [] range + + and collect_range_aux lst (id1, id2) = + match id1 <= id2 with + | false -> + lst + | true -> + collect_range_aux (id1 :: lst) (ID.incr id1, id2) + + let pp (id1, id2) = "(L" ^ ID.pp id1 ^ ", R" ^ ID.pp id2 ^ ")" +end diff --git a/ocaml/lib/domain/d02p01.mli b/ocaml/lib/domain/d02p01.mli new file mode 100644 index 0000000..55665fe --- /dev/null +++ b/ocaml/lib/domain/d02p01.mli @@ -0,0 +1,36 @@ +module ID : sig + (** [t] is a ID within a range *) + type t + + val create : int -> t + (** [create num] is an ID [t] *) + + val validate : (t -> bool) list -> t -> bool + (** [validate rules id] is [true] if the [id] passes all rule checks *) + + val incr : t -> t + (** [incr id] is the next [id] in the sequence *) + + val to_int : t -> int + (** [to_int id] is an integer representation of [id] *) + + val to_string : t -> string + (** [to_string id] is an string representation of [id] *) + + val pp : t -> string + (** [pp id] is a pretty string representing [id] *) +end + +module Range : sig + (** [t] is a range of values *) + type t + + val create : int * int -> t + (** [create (s, e)] is a range starting at [s] and ending at [e]. *) + + val collect : t -> ID.t list + (** [collect r] is a list containing all elements within the range (inclusive). *) + + val pp : t -> string + (** [pp r] is a pretty string representing [r] *) +end diff --git a/ocaml/lib/domain/d02p02.ml b/ocaml/lib/domain/d02p02.ml new file mode 100644 index 0000000..b2b55f1 --- /dev/null +++ b/ocaml/lib/domain/d02p02.ml @@ -0,0 +1,29 @@ +module ID = struct + (** *) + type t = int + + let create num = num + + let incr = ( + ) 1 + + let to_string = string_of_int + + let pp id = "ID=" ^ string_of_int id +end + +module Range = struct + type t = ID.t * ID.t + + let create (id1, id2) = (ID.create id1, ID.create id2) + + let rec collect range = collect_range_aux [] range + + and collect_range_aux lst (id1, id2) = + match id1 <= id2 with + | false -> + lst + | true -> + collect_range_aux (id1 :: lst) (ID.incr id1, id2) + + let pp (id1, id2) = "(L" ^ ID.pp id1 ^ ", R" ^ ID.pp id2 ^ ")" +end diff --git a/ocaml/lib/domain/d02p02.mli b/ocaml/lib/domain/d02p02.mli new file mode 100644 index 0000000..f918ae1 --- /dev/null +++ b/ocaml/lib/domain/d02p02.mli @@ -0,0 +1,30 @@ +module ID : sig + (** [t] is a ID within a range *) + type t + + val create : int -> t + (** [create num] is an ID [t] *) + + val incr : t -> t + (** [incr id] is the next [id] in the sequence *) + + val to_string : t -> string + (** [to_string id] is an string representation of [id] *) + + val pp : t -> string + (** [pp id] is a pretty string representing [id] *) +end + +module Range : sig + (** [t] is a range of values *) + type t + + val create : int * int -> t + (** [create (s, e)] is a range starting at [s] and ending at [e]. *) + + val collect : t -> ID.t list + (** [collect r] is a list containing all elements within the range (inclusive). *) + + val pp : t -> string + (** [pp r] is a pretty string representing [r] *) +end diff --git a/ocaml/lib/domain/d03p01.ml b/ocaml/lib/domain/d03p01.ml new file mode 100644 index 0000000..1367712 --- /dev/null +++ b/ocaml/lib/domain/d03p01.ml @@ -0,0 +1,114 @@ +module Battery : sig + type t + + val of_int : int -> t + + val to_int : t -> int + + val to_string : t -> string + + val compare : t -> t -> int + + val pp : t -> string +end = struct + type t = int + + let of_int i = i + + let to_int b = b + + let to_string = string_of_int + + let compare = Int.compare + + let pp = string_of_int +end + +module BatteryBank : sig + type t + + val highest_joltage : t -> int + (** [highest_joltage bb] is an integer created from the two highest + digits in [bb]. + Raises: [Assert_failure] if [bb] is empty. *) + + val of_string : string -> t + + val of_list : Battery.t list -> t + + val to_list : t -> Battery.t list + + val pp : t -> string +end = struct + type t = Battery.t list + + let rec highest_joltage bb = + assert (List.length bb > 0) ; + match highest_joltage_aux (None, None) bb with + | None, None -> + 0 + | Some x, Some y -> + int_of_string (Battery.to_string x ^ Battery.to_string y) + | _ -> + failwith "highest_joltage must be two digits" + + and highest_joltage_aux acc = function + | [] -> + acc + | [elt] -> ( + match acc with + | None, None -> + (Some (Battery.of_int 0), Some elt) + | Some first, None -> + (Some first, Some elt) + | None, Some second -> + (Some second, Some elt) + | Some first, Some second -> + if elt > second then (Some first, Some elt) + else (Some first, Some second) ) + | h :: t -> + highest_joltage_aux + ( match acc with + | None, None -> + (Some h, None) + | Some first, None -> + let second = Battery.of_int 0 in + swap_if_higher first second h + | Some first, Some second -> + swap_if_higher first second h + | _ -> + failwith "this branch should be impossible" ) + t + + and swap_if_higher first second considering = + assert (first >= second) ; + let c_gt_f = Battery.compare considering first = 1 in + let c_gt_s = Battery.compare considering second = 1 in + match (c_gt_f, c_gt_s) with + | true, true -> + (Some considering, None) + | false, true -> + (Some first, Some considering) + | true, false -> + failwith "[second] should never be greater than [first]" + | false, false -> + (Some first, Some second) + + let of_string s = + s |> String.to_seq + |> Seq.map (fun c -> Char.escaped c |> int_of_string |> Battery.of_int) + |> List.of_seq + + let of_list lst = lst + + let to_list bb = bb + + let pp bb = + "[ " + ^ ( bb |> List.map Battery.pp + |> List.fold_left + (fun acc b -> + if acc = "" then b ^ ";" else Printf.sprintf "%s %s;" acc b ) + "" ) + ^ " ]" +end diff --git a/ocaml/lib/domain/d03p02.ml b/ocaml/lib/domain/d03p02.ml new file mode 100644 index 0000000..88257d0 --- /dev/null +++ b/ocaml/lib/domain/d03p02.ml @@ -0,0 +1,116 @@ +module Battery : sig + type t + + val of_int : int -> t + + val to_int : t -> int + + val to_string : t -> string + + val compare : t -> t -> int + + val pp : t -> string +end = struct + type t = int + + let of_int i = i + + let to_int b = b + + let to_string = string_of_int + + let compare = Int.compare + + let pp = string_of_int +end + +module BatteryBank : sig + type t + + val highest_joltage : t -> int + (** [highest_joltage bb] is an integer created from the two highest + digits in [bb]. + Raises: [Assert_failure] if [bb] is empty. *) + + val of_string : string -> t + + val of_list : Battery.t list -> t + + val to_list : t -> Battery.t list + + val pp : t -> string +end = struct + type t = Battery.t list + + type highest = {one: Battery.t option; two: Battery.t option} + + let rec highest_joltage bb = + assert (List.length bb > 0) ; + match highest_joltage_aux {one= None; two= None} bb with + | {one= None; two= None} -> + 0 + | {one= Some x; two= Some y} -> + int_of_string (Battery.to_string x ^ Battery.to_string y) + | _ -> + failwith "highest_joltage must be two digits" + + and highest_joltage_aux acc = function + | [] -> + acc + | [elt] -> ( + match acc with + | {one= None; two= None} -> + {one= Some (Battery.of_int 0); two= Some elt} + | {one= Some first; two= None} -> + {one= Some first; two= Some elt} + | {one= None; two= Some second} -> + {one= Some second; two= Some elt} + | {one= Some first; two= Some second} -> + if elt > second then {one= Some first; two= Some elt} + else {one= Some first; two= Some second} ) + | h :: t -> + highest_joltage_aux + ( match acc with + | {one= None; two= None} -> + {one= Some h; two= None} + | {one= Some first; two= None} -> + let second = Battery.of_int 0 in + swap_if_higher first second h + | {one= Some first; two= Some second} -> + swap_if_higher first second h + | _ -> + failwith "this branch should be impossible" ) + t + + and swap_if_higher first second considering = + assert (first >= second) ; + let c_gt_f = Battery.compare considering first = 1 in + let c_gt_s = Battery.compare considering second = 1 in + match (c_gt_f, c_gt_s) with + | true, true -> + {one= Some considering; two= None} + | false, true -> + {one= Some first; two= Some considering} + | true, false -> + failwith "[second] should never be greater than [first]" + | false, false -> + {one= Some first; two= Some second} + + let of_string s = + s |> String.to_seq + |> Seq.map (fun c -> Char.escaped c |> int_of_string |> Battery.of_int) + |> List.of_seq + + let of_list lst = lst + + let to_list bb = bb + + let pp bb = + "[ " + ^ ( bb |> List.map Battery.pp + |> List.fold_left + (fun acc b -> + if acc = "" then b ^ ";" else Printf.sprintf "%s %s;" acc b ) + "" ) + ^ " ]" +end diff --git a/ocaml/lib/domain/dune b/ocaml/lib/domain/dune new file mode 100644 index 0000000..5b13d31 --- /dev/null +++ b/ocaml/lib/domain/dune @@ -0,0 +1,13 @@ +(library + (name domain) + (inline_tests) + (libraries + fmt + logs) + (preprocess + (pps + ppx_deriving.show + ppx_deriving.eq + ppx_deriving.ord + ppx_inline_test + ppx_expect))) diff --git a/ocaml/lib/domain/matrix.ml b/ocaml/lib/domain/matrix.ml new file mode 100644 index 0000000..fdf476f --- /dev/null +++ b/ocaml/lib/domain/matrix.ml @@ -0,0 +1,196 @@ +module M : sig + type 'a t = 'a array array + + val empty : int -> int -> 'a -> 'a t + (** [empty rows columns initial] is an empty 2D array of size [rows] * [columns] + where each element in the array is initialized to [initial]. *) + + val of_list : 'a list list -> 'a t + (** [of_list lst] is a 2D array where every element in the array is an + element corresponds to an element of the [lst]. *) + + val to_list : 'a t -> 'a list list + (** [to_list matrix] is a 2D list where every element in the list is an + element corresponds to an element of the [matrix]. *) + + val of_string : string -> string t + (** [of_string str] is a 2D array where every character is an element. *) + + val get : 'a t -> int -> int -> 'a + (** [get matrix x y] is an alias for [matrix.(y).(x)]. + Requires: [x] and [y] are positive integers or 0. *) + + val set : 'a t -> int -> int -> 'a -> unit + (** [set matrix x y v] is an alias for [matrix.(y).(x) <- v]. + Requires: [x] and [y] are positive integers or 0. *) + + val get_valid_neighbours : 'a t -> int -> int -> (int * int) list + (** [get_valid_neighbours matrix x y] is a list of pairs in the format + (x * y) that represent the valid surrounding positions of [matrix.(y).(x)]. + Requires: [x] and [y] are positive integers or 0. *) + + val pp : ('a -> string) -> 'a t -> string + (** [pp matrix string_of_elt] is the string representation of the [matrix] + where [string_of_elt] is called on each element. *) +end = struct + type 'a t = 'a array array + + let empty cols rows initial = Array.make_matrix cols rows initial + + let of_list lst = Array.of_list @@ List.map Array.of_list lst + + let to_list matrix = Array.to_list @@ Array.map Array.to_list matrix + + let of_string str = + let lines = + str |> String.split_on_char '\n' + |> List.filter_map (fun line -> + let trimmed = String.trim line in + if trimmed <> "" then Some trimmed else None ) + in + let char_lst = + List.map + (fun s -> s |> String.to_seq |> List.of_seq |> List.map Char.escaped) + lines + in + of_list char_lst + + let get matrix x y = matrix.(y).(x) + + let set matrix x y v = matrix.(y).(x) <- v + + let get_valid_neighbours matrix x y = + let potential_neighbours = + [ (x - 1, y - 1) + ; (x, y - 1) + ; (x + 1, y - 1) + ; (x - 1, y) + ; (x + 1, y) + ; (x - 1, y + 1) + ; (x, y + 1) + ; (x + 1, y + 1) ] + in + let min_x = 0 in + let max_x = Array.length matrix.(y) - 1 in + let min_y = 0 in + let max_y = Array.length matrix - 1 in + List.filter + (fun (x, y) -> min_x <= x && x <= max_x && min_y <= y && y <= max_y) + potential_neighbours + + let pp string_of_elt matrix = + "[" + ^ Array.fold_left + (fun col_acc arr -> + let row = + "[" + ^ Array.fold_left + (fun row_acc elt -> row_acc ^ string_of_elt elt ^ "; ") + " " arr + ^ "]" + in + col_acc ^ " " ^ row ^ "\n" ) + "\n" matrix + ^ "]" +end + +let%expect_test "matrix created from input is pretty-printed as the same matrix" + = + let example = + {|..@@.@@@@. +@@@.@.@.@@ +@@@@@.@.@@ +@.@@@@..@. +@@.@@@@.@@ +.@@@@@@@.@ +.@.@.@.@@@ +@.@@@.@@@@ +.@@@@@@@@. +@.@.@@@.@.|} + in + print_string @@ M.pp (fun s -> s) @@ M.of_string example ; + [%expect + {| + [ + [ .; .; @; @; .; @; @; @; @; .; ] + [ @; @; @; .; @; .; @; .; @; @; ] + [ @; @; @; @; @; .; @; .; @; @; ] + [ @; .; @; @; @; @; .; .; @; .; ] + [ @; @; .; @; @; @; @; .; @; @; ] + [ .; @; @; @; @; @; @; @; .; @; ] + [ .; @; .; @; .; @; .; @; @; @; ] + [ @; .; @; @; @; .; @; @; @; @; ] + [ .; @; @; @; @; @; @; @; @; .; ] + [ @; .; @; .; @; @; @; .; @; .; ] + ] + |}] + +let%expect_test "direct access of the matrix is in form matrix.(y).(x)" = + let example = + {|..@@.@@@@. +@@@.@.@.@@ +@@@@@.@.@@ +@.@@@@..@. +@@.@@@@.@@ +.@@@@@@@.@ +.@.@.@.@@@ +@.@@@.@@@@ +.@@@@@@@@. +@.@.@@@.@.|} + in + let matrix = M.of_string example in + print_string matrix.(0).(0) ; + [%expect "."] ; + print_string matrix.(0).(1) ; + [%expect "."] ; + print_string matrix.(0).(2) ; + [%expect "@"] ; + print_string matrix.(0).(3) ; + [%expect "@"] ; + print_string matrix.(0).(4) ; + [%expect "."] ; + print_string matrix.(0).(5) ; + [%expect "@"] ; + print_string matrix.(0).(6) ; + [%expect "@"] ; + print_string matrix.(0).(7) ; + [%expect "@"] ; + print_string matrix.(0).(8) ; + [%expect "@"] ; + print_string matrix.(0).(9) ; + [%expect "."] + +let%expect_test "[M.get matrix x y] is an alias for [matrix.(y).(x)]" = + let example = + {|..@@.@@@@. +@@@.@.@.@@ +@@@@@.@.@@ +@.@@@@..@. +@@.@@@@.@@ +.@@@@@@@.@ +.@.@.@.@@@ +@.@@@.@@@@ +.@@@@@@@@. +@.@.@@@.@.|} + in + let matrix = M.of_string example in + print_string @@ M.get matrix 0 0 ; + [%expect "."] ; + print_string @@ M.get matrix 1 0 ; + [%expect "."] ; + print_string @@ M.get matrix 2 0 ; + [%expect "@"] ; + print_string @@ M.get matrix 3 0 ; + [%expect "@"] ; + print_string @@ M.get matrix 4 0 ; + [%expect "."] ; + print_string @@ M.get matrix 5 0 ; + [%expect "@"] ; + print_string @@ M.get matrix 6 0 ; + [%expect "@"] ; + print_string @@ M.get matrix 7 0 ; + [%expect "@"] ; + print_string @@ M.get matrix 8 0 ; + [%expect "@"] ; + print_string @@ M.get matrix 9 0 ; + [%expect "."] diff --git a/ocaml/lib/domain/range.ml b/ocaml/lib/domain/range.ml new file mode 100644 index 0000000..a1bdc00 --- /dev/null +++ b/ocaml/lib/domain/range.ml @@ -0,0 +1,67 @@ +module M : sig + type t + + val of_string : string -> t option + (** [of_string s] is a range of integers if [s] is a string in the form ["%d-%d"] + or [None] otherwise. *) + + val collect : t -> int list + (** [collect range] is a fully realised range as a list. + Example: ["1-5" |> of_string |> collect] is the list [\[1;2;3;4;5\]]. + Example: ["5-1" |> of_string |> collect] is the list [\[5;4;3;2;1\]].*) + + val pp : t -> string + (** [pp range] is a pretty-printed version of the range. *) +end = struct + type t = int * int + + let of_string s = + Scanf.sscanf_opt s "%d-%d" (fun start finish -> (start, finish)) + + let rec collect (start, finish) = + let collected = + match Int.compare start finish with + | -1 -> + collect_range_aux Int.succ ( > ) [] (start, finish) + | 0 -> + [start] + | 1 -> + collect_range_aux Int.pred ( < ) [] (start, finish) + | _ -> + assert false + in + List.rev collected + + and collect_range_aux fn cmp lst (start, finish) = + if cmp start finish then lst + else collect_range_aux fn cmp (start :: lst) (fn start, finish) + + let pp (start, finish) = string_of_int start ^ "-" ^ string_of_int finish +end + +let%expect_test "incrementing range" = + match M.of_string "1-5" with + | None -> + [%expect.unreachable] + | Some range -> + let collected = M.collect range in + List.iter print_int collected ; + [%expect "12345"] + +let%expect_test "decrementing range" = + match M.of_string "5-1" with + | None -> + [%expect.unreachable] + | Some range -> + let collected = M.collect range in + List.iter print_int collected ; + [%expect "54321"] + +let%expect_test "one element range" = + match M.of_string "1-1" with + | None -> + [%expect.unreachable] + | Some range -> + let collected = M.collect range in + List.iter print_int collected ; + [%expect "1"] diff --git a/ocaml/lib/infrastructure/dune b/ocaml/lib/infrastructure/dune new file mode 100644 index 0000000..b9d250f --- /dev/null +++ b/ocaml/lib/infrastructure/dune @@ -0,0 +1,15 @@ +(library + (name infrastructure) + (libraries + application + domain + fmt + logs) + (inline_tests) + (preprocess + (pps + ppx_deriving.show + ppx_deriving.eq + ppx_deriving.ord + ppx_inline_test + ppx_expect))) diff --git a/ocaml/lib/infrastructure/file.ml b/ocaml/lib/infrastructure/file.ml new file mode 100644 index 0000000..3b49c01 --- /dev/null +++ b/ocaml/lib/infrastructure/file.ml @@ -0,0 +1,4 @@ +let read filename = + let chan = In_channel.open_text filename in + let content = In_channel.input_all chan in + In_channel.close chan ; content diff --git a/ocaml/lib/infrastructure/file.mli b/ocaml/lib/infrastructure/file.mli new file mode 100644 index 0000000..17d4495 --- /dev/null +++ b/ocaml/lib/infrastructure/file.mli @@ -0,0 +1,3 @@ +val read : string -> string +(** [read filename] is the contents of [filename] as a string. + The empty string is returned if [filename] does not exist. *) diff --git a/ocaml/lib/presentation/cli.ml b/ocaml/lib/presentation/cli.ml new file mode 100644 index 0000000..7864e7f --- /dev/null +++ b/ocaml/lib/presentation/cli.ml @@ -0,0 +1,62 @@ +(* Business logic behind CLI *) + +let run_solver name solver filepath = + let content = Infrastructure.File.read filepath in + match solver content () with + | Error msg -> + Logs.app (fun m -> m "[%s] Err=%s" name msg) + | Ok answer -> + Logs.app (fun m -> m "[%s] ANSWER=%d" name answer) + +let run_day _ = function + | 1 -> + let filepath = "../_input/day_01.txt" in + run_solver "D01P01" Application.D01p01.solve filepath ; + run_solver "D01P02" Application.D01p02.solve filepath ; + 0 + | 2 -> + let filepath = "../_input/day_02.txt" in + run_solver "D02P01" Application.D02p01.solve filepath ; + run_solver "D02P02" Application.D02p02.solve filepath ; + 0 + | 3 -> + let filepath = "../_input/day_03.txt" in + run_solver "D03P01" Application.D03p01.solve filepath ; + run_solver "D03P02" Application.D03p02.solve filepath ; + 0 + | 4 -> + let filepath = "../_input/day_04.txt" in + run_solver "D04P01" Application.D04p01.solve filepath ; + run_solver "D04P02" Application.D04p02.solve filepath ; + 0 + | 5 -> + let filepath = "../_input/day_05.txt" in + run_solver "D05P01" Application.D05p01.solve filepath ; + 0 + | 6 -> + let filepath = "../_input/day_06.txt" in + run_solver "D06P01" Application.D06p01.solve filepath ; + run_solver "D06P02" Application.D06p02.solve filepath ; + 0 + | _ -> + failwith "day not implemented" + +(* Command line interface *) + +open Cmdliner + +let setup_log style_renderer = Fmt_tty.setup_std_outputs ?style_renderer () + +let setup_log_t = Term.(const setup_log $ Fmt_cli.style_renderer ()) + +let day_arg = + let doc = "Day to run. MUST be in range of 1-12." in + Arg.(value & pos 0 int 1 & info [] ~docv:"DAY" ~doc) + +let day_t = Term.(const run_day $ setup_log_t $ day_arg) + +let cmd = + let info = Cmd.info "aoc" ~version:"0.1.0" in + Cmd.v info day_t + +let run () = Cmd.eval' cmd diff --git a/ocaml/lib/presentation/dune b/ocaml/lib/presentation/dune new file mode 100644 index 0000000..ae1fcb1 --- /dev/null +++ b/ocaml/lib/presentation/dune @@ -0,0 +1,16 @@ +(library + (name presentation) + (libraries + application + domain + infrastructure + cmdliner + fmt.cli + fmt.tty) + (inline_tests) + (preprocess + (pps + ppx_deriving.show + ppx_deriving.eq + ppx_deriving.ord + ppx_expect))) diff --git a/ocaml/test/application/dune b/ocaml/test/application/dune new file mode 100644 index 0000000..7a90f81 --- /dev/null +++ b/ocaml/test/application/dune @@ -0,0 +1,9 @@ +(tests + (names + test_d01p01 + test_d01p02 + test_d04p01) + (libraries + application + domain + alcotest)) diff --git a/ocaml/test/application/test_d01p01.ml b/ocaml/test/application/test_d01p01.ml new file mode 100644 index 0000000..e57db01 --- /dev/null +++ b/ocaml/test/application/test_d01p01.ml @@ -0,0 +1,23 @@ +open Application.D01p01 + +let example_data = {|L68 +L30 +R48 +L5 +R60 +L55 +L1 +L99 +R14 +L82|} + +let test_full_given_example () = + Alcotest.(check int) + "full given example" 3 + (match solve example_data () with Error _ -> 0 | Ok ans -> ans) + +let () = + let open Alcotest in + run "D01p01" + [ ( "given-example" + , [test_case "full given example" `Quick test_full_given_example] ) ] diff --git a/ocaml/test/application/test_d01p02.ml b/ocaml/test/application/test_d01p02.ml new file mode 100644 index 0000000..bbda035 --- /dev/null +++ b/ocaml/test/application/test_d01p02.ml @@ -0,0 +1,23 @@ +open Application.D01p02 + +let example_data = {|L68 +L30 +R48 +L5 +R60 +L55 +L1 +L99 +R14 +L82|} + +let test_full_given_example () = + Alcotest.(check int) + "full given example" 6 + (match solve example_data () with Error _ -> 0 | Ok ans -> ans) + +let () = + let open Alcotest in + run "D01p02" + [ ( "given-example" + , [test_case "full given example" `Quick test_full_given_example] ) ] diff --git a/ocaml/test/application/test_d04p01.ml b/ocaml/test/application/test_d04p01.ml new file mode 100644 index 0000000..ffbe102 --- /dev/null +++ b/ocaml/test/application/test_d04p01.ml @@ -0,0 +1,24 @@ +open Application.D04p01 + +let example = + {|..@@.@@@@. +@@@.@.@.@@ +@@@@@.@.@@ +@.@@@@..@. +@@.@@@@.@@ +.@@@@@@@.@ +.@.@.@.@@@ +@.@@@.@@@@ +.@@@@@@@@. +@.@.@@@.@.|} + +let test_input_to_matrix have want () = + Alcotest.(check int) "number of rolls of paper that are accessible" want + @@ match solve have () with Error _ -> 0 | Ok ans -> ans + +let () = + let open Alcotest in + run "D04p01" + [ ( "given-example" + , [test_case "full example" `Quick @@ test_input_to_matrix example 13] ) + ] diff --git a/ocaml/test/domain/dune b/ocaml/test/domain/dune new file mode 100644 index 0000000..1364288 --- /dev/null +++ b/ocaml/test/domain/dune @@ -0,0 +1,11 @@ +(tests + (names + test_d01p01 + test_d01p02 + test_d03p01) + (libraries + domain + alcotest + qcheck-core + qcheck-alcotest) + (modes byte exe)) diff --git a/ocaml/test/domain/test_d01p01.ml b/ocaml/test/domain/test_d01p01.ml new file mode 100644 index 0000000..23404f6 --- /dev/null +++ b/ocaml/test/domain/test_d01p01.ml @@ -0,0 +1,29 @@ +open Domain.D01p01 + +let test_start () = + Alcotest.(check int) "incorrect starting state" 50 (Dial.to_int Dial.start) + +let test_turn desc exp dir = + Alcotest.(check int) desc exp (Dial.to_int (Dial.turn Dial.start dir)) + +let test_turn_left_one () = + test_turn "incorrect result when turning left one step" 49 (Dial.Left 1) + +let test_turn_left_100 () = + test_turn "incorrect result when turning left 100 steps" 50 (Dial.Left 100) + +let test_turn_right_one () = + test_turn "incorrect result when turning right one step" 51 (Dial.Right 1) + +let test_turn_right_100 () = + test_turn "incorrect result when turning right 100 steps" 50 (Dial.Right 100) + +let () = + let open Alcotest in + run "D01p01" + [ ("initial-state", [test_case "starting state" `Quick test_start]) + ; ( "turning-dial" + , [ test_case "turning left one step" `Quick test_turn_left_one + ; test_case "turning right one step" `Quick test_turn_right_one + ; test_case "turning left 100 steps" `Quick test_turn_left_100 + ; test_case "turning right 100 steps" `Quick test_turn_right_100 ] ) ] diff --git a/ocaml/test/domain/test_d01p02.ml b/ocaml/test/domain/test_d01p02.ml new file mode 100644 index 0000000..98c06c3 --- /dev/null +++ b/ocaml/test/domain/test_d01p02.ml @@ -0,0 +1,41 @@ +open Domain.D01p02 + +let test_start () = + Alcotest.(check int) "incorrect starting state" 50 (Dial.to_int Dial.start) + +let test_turn errmsg exp dir = + Alcotest.(check int) + errmsg exp + (Dial.to_int (fst (Turner.turn Dial.start 0 dir))) + +let test_turn_left_one () = + test_turn "incorrect result when turning left one step" 49 (Dial.Left 1) + +let test_turn_left_100 () = + test_turn "incorrect result when turning left 100 steps" 50 (Dial.Left 100) + +let test_turn_right_one () = + test_turn "incorrect result when turning right one step" 51 (Dial.Right 1) + +let test_turn_right_100 () = + test_turn "incorrect result when turning right 100 steps" 50 (Dial.Right 100) + +let test_zero_count errmsg exp dir = + Alcotest.(check int) errmsg exp (snd (Turner.turn Dial.start 0 dir)) + +let test_zero_count_right_1000 () = + test_zero_count "zero MUST be counted each time it is passed" 10 + (Dial.Right 1000) + +let () = + let open Alcotest in + run "D01p02" + [ ("initial-state", [test_case "starting state" `Quick test_start]) + ; ( "turning-dial-regression" + , [ test_case "turning left one step" `Quick test_turn_left_one + ; test_case "turning right one step" `Quick test_turn_right_one + ; test_case "turning left 100 steps" `Quick test_turn_left_100 + ; test_case "turning right 100 steps" `Quick test_turn_right_100 ] ) + ; ( "zero-count" + , [ test_case "zero count after turning right 1000 steps" `Quick + test_zero_count_right_1000 ] ) ] diff --git a/ocaml/test/domain/test_d03p01.ml b/ocaml/test/domain/test_d03p01.ml new file mode 100644 index 0000000..6a2b3cf --- /dev/null +++ b/ocaml/test/domain/test_d03p01.ml @@ -0,0 +1,857 @@ +open Domain.D03p01 + +let test_highest_joltage have want () = + Alcotest.(check int) + "highest joltage in battery bank" want + (BatteryBank.of_string have |> BatteryBank.highest_joltage) + +let largest_digit_always_in_highest_joltage = + QCheck2.Test.make ~count:1000 + ~name:"highest joltage always has contains the largest digit" + ~print:(fun s -> s) + QCheck2.Gen.(string_of @@ char_range '1' '9') + (fun have -> + QCheck2.assume (String.length have > 2) ; + let got = BatteryBank.of_string have |> BatteryBank.highest_joltage in + let first = got / 10 and second = got mod 10 in + let want = + String.fold_left + (fun acc chr -> if acc < chr then chr else acc) + '0' have + |> Char.escaped |> int_of_string + in + first = want || second = want ) + +let () = + let open Alcotest in + run "D03p01" + [ ( "example BatteryBank.highest_joltage" + , [ test_case "example line 1" `Quick + (test_highest_joltage "987654321111111" 98) + ; test_case "example line 2" `Quick + (test_highest_joltage "811111111111119" 89) + ; test_case "example line 3" `Quick + (test_highest_joltage "234234234234278" 78) + ; test_case "example line 4" `Quick + (test_highest_joltage "818181911112111" 92) ] ) + ; ( "real BatteryBank.highest_joltage" + , [ test_case "real line 001" `Quick + (test_highest_joltage + "3434845634454364546334335333448443354324533545443235414334477424442444346844344244444434445333344314" + 88 ) + ; test_case "real line 002" `Quick + (test_highest_joltage + "4644333464433434855275434364342435544972433324444444432243354936362863356444434344443333433425344432" + 99 ) + ; test_case "real line 003" `Quick + (test_highest_joltage + "3475855376536553445244645449948436834663673457444335454425154463544745554434526565475244677445573464" + 99 ) + ; test_case "real line 004" `Quick + (test_highest_joltage + "2281212713222421222222213222212222221223122222212125222722232132222251212212322222222722312232333312" + 87 ) + ; test_case "real line 005" `Quick + (test_highest_joltage + "4483335423223456243275328893923136663523559445363393353333233337733323263852953335475336578422333454" + 99 ) + ; test_case "real line 006" `Quick + (test_highest_joltage + "3242323333312323243333333353413314243333232242233323431332332353323333353333333332443515323433322274" + 74 ) + ; test_case "real line 007" `Quick + (test_highest_joltage + "2333332242424335244422323333113323312234133433323243344324444233324245126142444324422445113241224322" + 65 ) + ; test_case "real line 008" `Quick + (test_highest_joltage + "5154432634655462574764324542474445554653643456344345657233718556457665426344257554246426445655364424" + 87 ) + ; test_case "real line 009" `Quick + (test_highest_joltage + "2333128322322632533324333135293331312321323332214333232434433322473232223344341214372234132223223343" + 97 ) + ; test_case "real line 010" `Quick + (test_highest_joltage + "4334244423552542164444513547496574512523344184674374558444434436645643164524326453333335444434455453" + 98 ) + ; test_case "real line 011" `Quick + (test_highest_joltage + "4132222247423242222232632242222124423421222271231244222212222222554221311221262314222222224253442337" + 77 ) + ; test_case "real line 012" `Quick + (test_highest_joltage + "2224322221122233225272222222442223222222271115422222121223262743222224222322422224123122262323222821" + 82 ) + ; test_case "real line 013" `Quick + (test_highest_joltage + "6723822256923221233223334322457233343332383332523433343323228646232524535432233323314353333227243323" + 98 ) + ; test_case "real line 014" `Quick + (test_highest_joltage + "6115546238253222343552462122324244122555232412242231255242342542222232432524132213523232121212324545" + 86 ) + ; test_case "real line 015" `Quick + (test_highest_joltage + "6463573733747321131532311243414374652372276761664731354417577566137677557472577677776125327145332589" + 89 ) + ; test_case "real line 016" `Quick + (test_highest_joltage + "3754415413732333338423242374334432153868351274433435343563444545253375644356474264434423432633233432" + 88 ) + ; test_case "real line 017" `Quick + (test_highest_joltage + "5444434434444444443644344444454454451442423444454444444344443433464444435453445443443354555334454454" + 66 ) + ; test_case "real line 018" `Quick + (test_highest_joltage + "2322232322322342121231422323212136222323332232223332423222231222323222323213211222124112122223323222" + 64 ) + ; test_case "real line 019" `Quick + (test_highest_joltage + "2123222121233313232324221212131322222312114122122222223331524242222223522421122132314232232221325212" + 55 ) + ; test_case "real line 020" `Quick + (test_highest_joltage + "4395244125441654554734234542525455274423553314354552543244444237453434822334223553453354325435235232" + 98 ) + ; test_case "real line 021" `Quick + (test_highest_joltage + "2253424214422444223313135223232832334333553541324423242333333332422343235234313221222522523223223324" + 85 ) + ; test_case "real line 022" `Quick + (test_highest_joltage + "5235226542352363765362222242266353124241222253326335131251514362226422434463674353246334124656282122" + 82 ) + ; test_case "real line 023" `Quick + (test_highest_joltage + "2232123624442243412224246433221223412221211333362434222314122321222374432142634122244234226424224222" + 76 ) + ; test_case "real line 024" `Quick + (test_highest_joltage + "3422335122727323322362432313322322322223332332223816131326233213327243223323343212331236353226232321" + 87 ) + ; test_case "real line 025" `Quick + (test_highest_joltage + "4331335233332333423433313313173432431333333532233333433343363343333332336433333237323332253433443333" + 77 ) + ; test_case "real line 026" `Quick + (test_highest_joltage + "4447448444443444434444443243543534134744452133424233431341466844433346243413344384231154343384832444" + 88 ) + ; test_case "real line 027" `Quick + (test_highest_joltage + "2425565264165457455876521452237584855214342125728464762555858525255422733375855552537425835156285135" + 88 ) + ; test_case "real line 028" `Quick + (test_highest_joltage + "1331226211234222222312211322123232233513242526221423223222232214322223122232333222282842423352327112" + 88 ) + ; test_case "real line 029" `Quick + (test_highest_joltage + "3223434225632352425252225227722722534655252243423421222422525141365526221342142264132653472224125223" + 77 ) + ; test_case "real line 030" `Quick + (test_highest_joltage + "6737524344344356458523434443443144444264443348734644444644541613334445654733443544253434843553348938" + 98 ) + ; test_case "real line 031" `Quick + (test_highest_joltage + "3354422734545554524252254222935229552422513243152263456254352212241422142433745251122447413225375454" + 99 ) + ; test_case "real line 032" `Quick + (test_highest_joltage + "2264222763222232222612274362233123232631142294332533623322433223214212132333126222163271553323113546" + 97 ) + ; test_case "real line 033" `Quick + (test_highest_joltage + "3224455231554212335327234452322334222224355323335331221333422324235123435122325533513215236323223567" + 77 ) + ; test_case "real line 034" `Quick + (test_highest_joltage + "3364433535332344433338434131353453131351342344343443434434354345544614344346424444464434454542463163" + 86 ) + ; test_case "real line 035" `Quick + (test_highest_joltage + "3334311333353143563633334222343222333252653351532534233523263233265333333263352353553235353353353333" + 66 ) + ; test_case "real line 036" `Quick + (test_highest_joltage + "2341414245234322423322241415434452323345544444442373441644233534541453435433235452441251543332443224" + 76 ) + ; test_case "real line 037" `Quick + (test_highest_joltage + "2334222122362114522212222223212342234225232414234232252812322221321123236322322233153222246262451321" + 86 ) + ; test_case "real line 038" `Quick + (test_highest_joltage + "3222213324143222521232511323442333353115354312453322323233245125121212335352213422325232423442113222" + 55 ) + ; test_case "real line 039" `Quick + (test_highest_joltage + "4242234421454245624314342344231425552454131112356244265233623318251442244258454232422222544212344274" + 88 ) + ; test_case "real line 040" `Quick + (test_highest_joltage + "1212211622212622232222232221122222234222325251233221113223526233333222121362242225251522222122222412" + 66 ) + ; test_case "real line 041" `Quick + (test_highest_joltage + "1224243142354932247624132223442221243261122146133223131475431216123424343342231246215132422542144273" + 97 ) + ; test_case "real line 042" `Quick + (test_highest_joltage + "2492338342383524333242442252453454334643633243343233244463532534334332436821235243322432152236224423" + 98 ) + ; test_case "real line 043" `Quick + (test_highest_joltage + "7676556546555455945548897555565858677556364454655765687644455555553584455358535855555564435524555636" + 99 ) + ; test_case "real line 044" `Quick + (test_highest_joltage + "4325352353133335234532433545335543523225232353214622764538523233325246333312335343542333525343352832" + 88 ) + ; test_case "real line 045" `Quick + (test_highest_joltage + "3645465557845534765556456438425473478555654328555485735454543757533734857854555473533459558165456246" + 98 ) + ; test_case "real line 046" `Quick + (test_highest_joltage + "4112321222222221222222222212221112212222222213112212233222211122121222223233221122122112222222312222" + 43 ) + ; test_case "real line 047" `Quick + (test_highest_joltage + "3443534216134325237234252533391415524526262523352143237242332332332422332332347132554552354213163423" + 97 ) + ; test_case "real line 048" `Quick + (test_highest_joltage + "3233233443433352233334353335313474353433313433333332332445324732323333325354333345123333238333323343" + 84 ) + ; test_case "real line 049" `Quick + (test_highest_joltage + "2133222332312231142332122412332211233373432222221234222233216312322233333313213322233312422332412321" + 76 ) + ; test_case "real line 050" `Quick + (test_highest_joltage + "2323442214442251652225322254455617413464434422211515255422722166366711262534452258439143226431422432" + 96 ) + ; test_case "real line 051" `Quick + (test_highest_joltage + "3112243242223312224423222512422243342333412424122234223224524214241424224424422232231222421343123313" + 55 ) + ; test_case "real line 052" `Quick + (test_highest_joltage + "2524625652353456554546431455532426742414535225832464326233444242524314463569626432264234665412452611" + 96 ) + ; test_case "real line 053" `Quick + (test_highest_joltage + "2623224224152132332532122544116272213711472512222547253263227634222862855322152332253264721222222626" + 88 ) + ; test_case "real line 054" `Quick + (test_highest_joltage + "3413862324452342446454316341334346457442436472446424131344156965335463455345446483132552535546336316" + 98 ) + ; test_case "real line 055" `Quick + (test_highest_joltage + "5354344333214443434343442334124434322442443544435334333334544442334442344432244334534423233345433234" + 55 ) + ; test_case "real line 056" `Quick + (test_highest_joltage + "2322333211233223212213111213123244423222222232323332222322212221323323433322435233232371222222214231" + 74 ) + ; test_case "real line 057" `Quick + (test_highest_joltage + "9453214484824262259522856649224244244222211592233334238214222322284346591381262187242644522629832223" + 99 ) + ; test_case "real line 058" `Quick + (test_highest_joltage + "1121224242322123123163448337222562512232262643222323252252223543512425222422222223142233221426365461" + 87 ) + ; test_case "real line 059" `Quick + (test_highest_joltage + "1815612518244643515355768361121163421324442178622168752374328877665672243587235441472714316317588369" + 89 ) + ; test_case "real line 060" `Quick + (test_highest_joltage + "3655264263765432665494374452246362724532226675513545216322644525426252338564464426261732379225267837" + 99 ) + ; test_case "real line 061" `Quick + (test_highest_joltage + "8334743433235341445622143424833423324734232433333132424333763435142253533364333734887344542326984333" + 98 ) + ; test_case "real line 062" `Quick + (test_highest_joltage + "6333532332533333262332333332623323523436233333331335433333433323333133233344333378336333352223333232" + 86 ) + ; test_case "real line 063" `Quick + (test_highest_joltage + "3366253633632333372333133255934725232323263352222274223213323533141234362324236915143323333136434233" + 99 ) + ; test_case "real line 064" `Quick + (test_highest_joltage + "5322222323283352224135234432224523225232853242442423241544234623253324537257221444443224442542343223" + 88 ) + ; test_case "real line 065" `Quick + (test_highest_joltage + "2322722344168237322333228462433332227336523244323265343321333443434132333343333492223332435724342464" + 97 ) + ; test_case "real line 066" `Quick + (test_highest_joltage + "5554845434744255655687844665866661355767464556478385448756657654567285577634695565556616935646635793" + 99 ) + ; test_case "real line 067" `Quick + (test_highest_joltage + "6212623112233212232222321423231243253343231323432932323232222135322122237222134283242231231725212252" + 98 ) + ; test_case "real line 068" `Quick + (test_highest_joltage + "3224333333132232431414376222443332422422322223342332333335333224256832733233433622542324333442322953" + 95 ) + ; test_case "real line 069" `Quick + (test_highest_joltage + "1495248962734145243214425252551493515624456535246426262254542145634343245522513375341266233457656414" + 99 ) + ; test_case "real line 070" `Quick + (test_highest_joltage + "3333243514122577341633436443431333234233434851333432454335423136533335534834354554332443233443334233" + 88 ) + ; test_case "real line 071" `Quick + (test_highest_joltage + "6775435378454755775937393515454694792745716493599675572454465734647574159354586497336464643987566774" + 99 ) + ; test_case "real line 072" `Quick + (test_highest_joltage + "3343255423334231444544444344434343234333434343544344434524254354244544424452234444333442244454345533" + 55 ) + ; test_case "real line 073" `Quick + (test_highest_joltage + "5644753727566756775567545585754676596645654664776746756665476666666556676466474667695655766564656657" + 99 ) + ; test_case "real line 074" `Quick + (test_highest_joltage + "4644226464352276354314244224452454342423454622134648332554423425336244425432349642233521244367349342" + 99 ) + ; test_case "real line 075" `Quick + (test_highest_joltage + "3433335543445753633424571853934424332643345643544683232351242335433242324425464344622364532352533437" + 98 ) + ; test_case "real line 076" `Quick + (test_highest_joltage + "4332333343143333333343333474334265443333334214354551322433433243313234333232425433333443443433526343" + 76 ) + ; test_case "real line 077" `Quick + (test_highest_joltage + "4353646245233554434324342343533543343355335434535413535233331345445333253655446433354372556332324562" + 76 ) + ; test_case "real line 078" `Quick + (test_highest_joltage + "4325222211212322234523222222252532247122313123373542232242121352322276216822338182262236346922722222" + 97 ) + ; test_case "real line 079" `Quick + (test_highest_joltage + "2322565523552147152522424415733545252325565263526635227432457357155822584542343524555344525552453543" + 88 ) + ; test_case "real line 080" `Quick + (test_highest_joltage + "2536964744575897532331584947348473443439336365524543425393413335486443645364675453424543335446555498" + 99 ) + ; test_case "real line 081" `Quick + (test_highest_joltage + "2222213441222221121213232422422313244252223442212222212522422224224223231124422422243512212213212342" + 55 ) + ; test_case "real line 082" `Quick + (test_highest_joltage + "2226345127525427354274576964233625784361152623467725726752473254364464625478727754525322265247112673" + 98 ) + ; test_case "real line 083" `Quick + (test_highest_joltage + "3333323323439434233332343233242333523333333322324323333432333327112333312433423132233322331432333323" + 97 ) + ; test_case "real line 084" `Quick + (test_highest_joltage + "3333434534333424243331497446456437324756447475342274464232844334833254353445422233546141144533344344" + 98 ) + ; test_case "real line 085" `Quick + (test_highest_joltage + "3696167353668222145864766623627542566388745676275342437244534257633357663535464676463344222466874743" + 98 ) + ; test_case "real line 086" `Quick + (test_highest_joltage + "2221243212226232221214225213222212321222322222222222321222213152222212232613223422294232232522123112" + 95 ) + ; test_case "real line 087" `Quick + (test_highest_joltage + "2322223332222333222222322222224233422221212122124224222442422313242232322322322132221232323232224243" + 44 ) + ; test_case "real line 088" `Quick + (test_highest_joltage + "3222473241422323343212233253252455323361331212425233323243431532523435223231512332242372313333353233" + 77 ) + ; test_case "real line 089" `Quick + (test_highest_joltage + "5423224242232312223222322332344353122123124433226352334261254221453422322421343522321125621432243323" + 66 ) + ; test_case "real line 090" `Quick + (test_highest_joltage + "8363335374334533365422233335351363332233354423323122333333234434223223333335635423442344353624352436" + 87 ) + ; test_case "real line 091" `Quick + (test_highest_joltage + "3363445765667922559186654477737547964555446686459554733657654766566655765548166378754867769354765564" + 99 ) + ; test_case "real line 092" `Quick + (test_highest_joltage + "6533212432223424334451322232232232443253324232723281475224244243554253222475635635422425222222222428" + 88 ) + ; test_case "real line 093" `Quick + (test_highest_joltage + "1223233332437322323134322352329283423333133433331533223722733221342425515333543413253332323254332144" + 98 ) + ; test_case "real line 094" `Quick + (test_highest_joltage + "5264162233623746665261784343112243254221441535263624414528422142852361426326676128424382222772232624" + 88 ) + ; test_case "real line 095" `Quick + (test_highest_joltage + "1222232222322322212232232122232322222331222223323232222322212223221222223122132222222226222311212222" + 63 ) + ; test_case "real line 096" `Quick + (test_highest_joltage + "2447938353242561333434522543524366633338233372322372443172638272362414623239322222214322338432223233" + 99 ) + ; test_case "real line 097" `Quick + (test_highest_joltage + "4884849914558496499986874887474387879218486546454756422444585589669397378725447645287563673434787636" + 99 ) + ; test_case "real line 098" `Quick + (test_highest_joltage + "1532312355432455353342333225253523373245346353222322243344325423634342343333235353462335235134532214" + 76 ) + ; test_case "real line 099" `Quick + (test_highest_joltage + "3633115526253432364354661645232316365446622332424352521423265511535341213222665264163264143532241789" + 89 ) + ; test_case "real line 100" `Quick + (test_highest_joltage + "1333723464325672225363386433359834138466234942326568553333727332463624757374234473543533336465426327" + 99 ) + ; test_case "real line 101" `Quick + (test_highest_joltage + "8325733945242354443883554334669457355644376375344222347347435474443336786843454755374733473222713744" + 99 ) + ; test_case "real line 102" `Quick + (test_highest_joltage + "3836536232345525523424516573323367655557875645136254455455455555237343456565656555565433564652375615" + 88 ) + ; test_case "real line 103" `Quick + (test_highest_joltage + "1222242222222222222221222222222222222222222222214222242212122221223267222222112122222222222221223723" + 77 ) + ; test_case "real line 104" `Quick + (test_highest_joltage + "2122322223222221341225222331152222221222222221222222222212223231322221121224122222222221222322221221" + 55 ) + ; test_case "real line 105" `Quick + (test_highest_joltage + "2623431563346525416242365622564363427642123425242364433596524126242622114536213245235652674623263514" + 97 ) + ; test_case "real line 106" `Quick + (test_highest_joltage + "2232456232323314322243433252432326454256232632225662224221422412232162235555244527334626356269224262" + 96 ) + ; test_case "real line 107" `Quick + (test_highest_joltage + "2223224223121316212222123214122222321222232222214222223322235255232321232421222233223123422332226121" + 66 ) + ; test_case "real line 108" `Quick + (test_highest_joltage + "2232222211641215121113421342225332233222241313112621422223224321232326222212352222222222322222232142" + 66 ) + ; test_case "real line 109" `Quick + (test_highest_joltage + "3622224322123323543363333123532223522232121233221223212255624222224222331222232221222223332412252226" + 66 ) + ; test_case "real line 110" `Quick + (test_highest_joltage + "2133111331232311212423441441423422333433421444124423221341431323413244441112422424242124214412256789" + 89 ) + ; test_case "real line 111" `Quick + (test_highest_joltage + "4324434435333333333334332245243563333343356334334344235334322234145353323234352455374364553333643433" + 76 ) + ; test_case "real line 112" `Quick + (test_highest_joltage + "3644734534756433422432434356624552253427466225337526564429422436344444454366324544253633541443353244" + 96 ) + ; test_case "real line 113" `Quick + (test_highest_joltage + "2464324635364431443472344454334444343433464546454374434442423242443446444423526443551333243353834444" + 84 ) + ; test_case "real line 114" `Quick + (test_highest_joltage + "5281642433245222222262522922521227221222224253247163523362225212277164151212258322522132327215231222" + 98 ) + ; test_case "real line 115" `Quick + (test_highest_joltage + "4235325226444533343235413742346432342321545545344233233255443834433325423232343235231352342354622553" + 86 ) + ; test_case "real line 116" `Quick + (test_highest_joltage + "8733361224233225222222426322442381855225483432261538256252524243235345825178218568965722348222331146" + 98 ) + ; test_case "real line 117" `Quick + (test_highest_joltage + "3464344445243346232343434322224234534583235444432447445343449318343349436245884322482543363363441674" + 99 ) + ; test_case "real line 118" `Quick + (test_highest_joltage + "7343334231474233525283342422364511453342252222452242451442434432112333432342315946241522245542222535" + 96 ) + ; test_case "real line 119" `Quick + (test_highest_joltage + "4446143264553864474334554447535535453446566233244444636634565645448343225453554444454544364443454574" + 88 ) + ; test_case "real line 120" `Quick + (test_highest_joltage + "2422842224224342322263252223333244522315113324152212372221342231322244231232122332222112227625141222" + 87 ) + ; test_case "real line 121" `Quick + (test_highest_joltage + "4142122222422221222412122222212544522231222223211322362222321222222134432222122542122262223523223222" + 66 ) + ; test_case "real line 122" `Quick + (test_highest_joltage + "4572333542142432343332342132323444433243433454242321332323354233213344233432132323534221333344323134" + 75 ) + ; test_case "real line 123" `Quick + (test_highest_joltage + "1333235433322333324332333323332332233112331232333422333333333235333334322322333323333333323333423735" + 75 ) + ; test_case "real line 124" `Quick + (test_highest_joltage + "5563657356454636674393696655654548835664676555834625756663576376694963475267652644487768866556366963" + 99 ) + ; test_case "real line 125" `Quick + (test_highest_joltage + "4563325436233211733422223372423424234244425452243133154466242445642433343224441473324372257264523657" + 77 ) + ; test_case "real line 126" `Quick + (test_highest_joltage + "2323232163222222141214222333122231122223522528112323222223222322213223223322333322221323333333322311" + 83 ) + ; test_case "real line 127" `Quick + (test_highest_joltage + "2122222234242122427222222226222332211222112222222211222421121538222231222313221435322311232222212242" + 85 ) + ; test_case "real line 128" `Quick + (test_highest_joltage + "5334253235515121222383422225322212542617232476242645322652351543223434635325223237112254433523422324" + 87 ) + ; test_case "real line 129" `Quick + (test_highest_joltage + "1221223532146222222223126712218222123621122123217221212222322221232621162221122222222222122561122292" + 92 ) + ; test_case "real line 130" `Quick + (test_highest_joltage + "1253352424255222142243514444444457255254363328422562213135526512123451523416645352425253125322532244" + 86 ) + ; test_case "real line 131" `Quick + (test_highest_joltage + "7463373334646423355538441534433248593244576575237356213443653633273543376463332733333713232643554552" + 97 ) + ; test_case "real line 132" `Quick + (test_highest_joltage + "5752323474332773249344434354245224321731245442414442735261654665534764636524293424214244444723343251" + 99 ) + ; test_case "real line 133" `Quick + (test_highest_joltage + "2629438637852334643554943482775533446535956343762325623453474525365936398476244597355454656623443534" + 99 ) + ; test_case "real line 134" `Quick + (test_highest_joltage + "4535534443563253332234233434455543533235333233223442233533235354443333733435443343553354434434423313" + 75 ) + ; test_case "real line 135" `Quick + (test_highest_joltage + "3432223113423243423323513443123332315562232532523473232212343355364222323234236322343323225332327733" + 77 ) + ; test_case "real line 136" `Quick + (test_highest_joltage + "1425245335243124322324153454554115125214321244121112544441331342553344311545421455324424455134156789" + 89 ) + ; test_case "real line 137" `Quick + (test_highest_joltage + "5132222223252224223252622414436422231243232252214255323634524534432233273222325435334426442424422223" + 76 ) + ; test_case "real line 138" `Quick + (test_highest_joltage + "3466444436255433325573352262554776625733275447321246237454234455234514752545552532447415643534223435" + 77 ) + ; test_case "real line 139" `Quick + (test_highest_joltage + "3421133222332332322343231232212233662233221312321122224431322313262333313232332243223222233232344323" + 66 ) + ; test_case "real line 140" `Quick + (test_highest_joltage + "3333523333232364333363555455431345243544566532455235335641243353444455452225735366346344243452243545" + 76 ) + ; test_case "real line 141" `Quick + (test_highest_joltage + "2152354211122622723432615322212523224222225212363121124451232267516325421252122342152252424221452215" + 77 ) + ; test_case "real line 142" `Quick + (test_highest_joltage + "1336123121333343222232333212232411534322333233223332323322234351332222331333333233332312233311232332" + 65 ) + ; test_case "real line 143" `Quick + (test_highest_joltage + "2132121232233134232221223353223332232233322251223526132523323234323223335333222534212113232222523324" + 65 ) + ; test_case "real line 144" `Quick + (test_highest_joltage + "5642633765534226222523623324323443222227261332774392232227443393254412356235358631224418312923222245" + 99 ) + ; test_case "real line 145" `Quick + (test_highest_joltage + "8253442425732222332238822732425826242143236154326323242443342334348324234372622433223442523324133216" + 88 ) + ; test_case "real line 146" `Quick + (test_highest_joltage + "2232271226257223212222421324222344224222141222322222222222222122224238422414316422323222222322127221" + 87 ) + ; test_case "real line 147" `Quick + (test_highest_joltage + "6446315487414253623366432633565653444341454233666544632655534656536436114486263961512465435546514714" + 97 ) + ; test_case "real line 148" `Quick + (test_highest_joltage + "3452383332535514435334232222355452522134243132532453384334525234222233545425333315332323214145535341" + 88 ) + ; test_case "real line 149" `Quick + (test_highest_joltage + "3232222223322222122123232312331213231232227213222242313323222122223322222322313334222122231232221331" + 74 ) + ; test_case "real line 150" `Quick + (test_highest_joltage + "3424332322222132232233231123121313223223263323323222342313211312222322221122422223222112232122222222" + 64 ) + ; test_case "real line 151" `Quick + (test_highest_joltage + "4688454513726334654565476674454433554682654943685644463375763364523442437445697396743545599524274524" + 99 ) + ; test_case "real line 152" `Quick + (test_highest_joltage + "4161223222271222942312171248122235227112222552223422326624121622234732522212222232212366825223253121" + 98 ) + ; test_case "real line 153" `Quick + (test_highest_joltage + "6332465533333522555433324356221543352235533566333253334365565323363324552432332233335334227623136325" + 76 ) + ; test_case "real line 154" `Quick + (test_highest_joltage + "3222431333842318323144411327346223122314332323123555122522333732335233333352312534852241433483632313" + 88 ) + ; test_case "real line 155" `Quick + (test_highest_joltage + "2112121212253222222943424322422222512112212312422234143313232222122122222122123122132137327212242242" + 97 ) + ; test_case "real line 156" `Quick + (test_highest_joltage + "3222532316332422221212223221522223111252221221322212217222213222221221211241222312222121222133222221" + 74 ) + ; test_case "real line 157" `Quick + (test_highest_joltage + "2323332223344321223143245373113722211223122226227822325314633213361321445632321823462822337223222445" + 88 ) + ; test_case "real line 158" `Quick + (test_highest_joltage + "1828425212144411422233221323218468423217466223581342222211242262211332238228121222224225122212222232" + 88 ) + ; test_case "real line 159" `Quick + (test_highest_joltage + "3556334454665634545845243453255635572675157347763125427524341342644935377436635334453443684644784434" + 98 ) + ; test_case "real line 160" `Quick + (test_highest_joltage + "2645662623422533225421215431512343431254242214262232525122522244211222145253265353435325571322222253" + 75 ) + ; test_case "real line 161" `Quick + (test_highest_joltage + "2224222242122222222232211112222314222222222234412242326322512423222222232623322222122322112321211122" + 66 ) + ; test_case "real line 162" `Quick + (test_highest_joltage + "3323122223222222333232562133233231243322232322321321232232332322335422322313322223123222222334233322" + 65 ) + ; test_case "real line 163" `Quick + (test_highest_joltage + "3221132223233221322222223221222241223222221222212214223422121422222452222224223222122124212323123221" + 54 ) + ; test_case "real line 164" `Quick + (test_highest_joltage + "2221442252281322222422127282212412222622221232731222223242124222212112212222241262321442522221122252" + 88 ) + ; test_case "real line 165" `Quick + (test_highest_joltage + "2133231122331441531433375353572447432322146333423223632556232232334447444433622228242422332343332434" + 84 ) + ; test_case "real line 166" `Quick + (test_highest_joltage + "5563624513351533334437333324353335333533533355223433335233342353363556423235334233344323342524634321" + 76 ) + ; test_case "real line 167" `Quick + (test_highest_joltage + "2332432233323221431231323223332233383122222132424421134132233232323333433435254432332222332323313232" + 85 ) + ; test_case "real line 168" `Quick + (test_highest_joltage + "2423222822122222222412282231122429224143222242222222223223436242424223242432142221433121142143522227" + 97 ) + ; test_case "real line 169" `Quick + (test_highest_joltage + "1368838843358743334345324211363655364233733332443334321243322336412538532344213423253222366323263695" + 95 ) + ; test_case "real line 170" `Quick + (test_highest_joltage + "4744137463363333333332326343314643324446233366376545372648324744635334435237267661327145553246452321" + 87 ) + ; test_case "real line 171" `Quick + (test_highest_joltage + "3831332122233231223233222433232333233133133242242331362323323332235244434223336362233433352322334423" + 86 ) + ; test_case "real line 172" `Quick + (test_highest_joltage + "3214422223143421344222143132231445423222522643233323314323424344434731323334224323324533352334332442" + 75 ) + ; test_case "real line 173" `Quick + (test_highest_joltage + "3543312373344332233323452214433346347334844253532442224222234312433329254329453434283232534223272192" + 99 ) + ; test_case "real line 174" `Quick + (test_highest_joltage + "3433383314338373834351844233646343475553142224453454325446354334653275541333543135354465333263445242" + 88 ) + ; test_case "real line 175" `Quick + (test_highest_joltage + "4432372233684255322525272434647435924424322624541334413224443634326336461361436414475721222543446321" + 97 ) + ; test_case "real line 176" `Quick + (test_highest_joltage + "5433335293334525543224371764333434988333237353113448943411932743514583594643353323325387583814645347" + 99 ) + ; test_case "real line 177" `Quick + (test_highest_joltage + "5444424957443468654656364755567468455486545344445776761776566466566644555557658947447467449455766646" + 99 ) + ; test_case "real line 178" `Quick + (test_highest_joltage + "2523333233343235435222235534333238434254233255342853335554453253236223223533323533522334332433432543" + 88 ) + ; test_case "real line 179" `Quick + (test_highest_joltage + "1138614552342221321221222413621222223231223222323222814623241222446214226442469244351324343452225242" + 95 ) + ; test_case "real line 180" `Quick + (test_highest_joltage + "3235222332131222222123223222332312322323172132141232121324222213353223224233213123222221223322233343" + 75 ) + ; test_case "real line 181" `Quick + (test_highest_joltage + "4255452362323165242342332223332343343253334456143314452223324924424332355355353341343334425553353324" + 95 ) + ; test_case "real line 182" `Quick + (test_highest_joltage + "3544633234326333143434434638432333354444353822424442333225332444532646543132326565233146643454534545" + 88 ) + ; test_case "real line 183" `Quick + (test_highest_joltage + "5533866835333332446339845544234435374366623344443448343635433373544333343634473633534344325836364423" + 98 ) + ; test_case "real line 184" `Quick + (test_highest_joltage + "2125222512312522233222222532263455412421222232122112222113222512244225244211543622122111124332312445" + 66 ) + ; test_case "real line 185" `Quick + (test_highest_joltage + "5765537453753546524944644454354364643373535664333265356565454434334362334636443764473516535345363344" + 97 ) + ; test_case "real line 186" `Quick + (test_highest_joltage + "1242333313323541312121335173323322252262322222223242223232232232334313232372223332333223325322223324" + 77 ) + ; test_case "real line 187" `Quick + (test_highest_joltage + "4945346555344725534444354865365546455455453445376994344537749854443434373543557699323594547559554353" + 99 ) + ; test_case "real line 188" `Quick + (test_highest_joltage + "8441321232536757468212413223835223356252342241323333442736423243742453922327334232628243572272153736" + 98 ) + ; test_case "real line 189" `Quick + (test_highest_joltage + "3233227533534655473331231323541242222614244236242111121212233442265222232434532624232622312564515622" + 77 ) + ; test_case "real line 190" `Quick + (test_highest_joltage + "5231443534475571334243142383154324332422432224554332949237335344432225546623266434742645424452245374" + 99 ) + ; test_case "real line 191" `Quick + (test_highest_joltage + "2222652752542951212254142576134913223231244514325577152312242364152242322223322323734226324612251433" + 99 ) + ; test_case "real line 192" `Quick + (test_highest_joltage + "3344232272222124265242432232223224453423221222354325223634222122333255463333115233242332132134223228" + 78 ) + ; test_case "real line 193" `Quick + (test_highest_joltage + "2423211646221222214223621414422342222242214622426224222474221222222215322142322234542433223122532751" + 77 ) + ; test_case "real line 194" `Quick + (test_highest_joltage + "1222432222227322222223122113222321223327322223241223322232332322222222222222123323434222222223412222" + 77 ) + ; test_case "real line 195" `Quick + (test_highest_joltage + "2231222262245222221524294251251222212942222142532633726213522122212422222232121922212212262322231242" + 99 ) + ; test_case "real line 196" `Quick + (test_highest_joltage + "3436674387463373644336847545353373813336332865333426743634647353343363237627393333883956336933349336" + 99 ) + ; test_case "real line 197" `Quick + (test_highest_joltage + "4234664334533643445662346466345666647426655444553533366634453846446264346586345365444765364634443146" + 88 ) + ; test_case "real line 198" `Quick + (test_highest_joltage + "4141324433423632324233334423343324244241332653544345362343464242132445242521322431444734316223541332" + 76 ) + ; test_case "real line 199" `Quick + (test_highest_joltage + "3535726262552245152262222275133232659846242165522221422176222262567254622235412424626225322313236476" + 98 ) + ; test_case "real line 200" `Quick + (test_highest_joltage + "2222222333136722224325232232111133222432346324431421212343222142244331223225124653223222322121232213" + 76 ) ] ) + ; ( "BatteryBank.highest_joltage regression" + , [ test_case + "second digit promoted when same digit found and greater than \ + first digit" + `Quick + (test_highest_joltage "343400" 44) + ; test_case "second last digit skipped" `Quick + (test_highest_joltage "789" 89) + ; test_case "I have no idea why this is wrong yet" `Quick + (test_highest_joltage "565" 65) + ; test_case "I have no idea why this is right yet" `Quick + (test_highest_joltage "566" 66) + ; test_case "I have no idea why this is right yet" `Quick + (test_highest_joltage "06760" 76) + ; test_case "I have no idea why this is wrong yet" `Quick + (test_highest_joltage "0067600" 76) ] ) + ; ( "BatteryBank property tests" + , List.map + (QCheck_alcotest.to_alcotest ~colors:true) + [largest_digit_always_in_highest_joltage] ) ] diff --git a/ocaml/test/infrastructure/dune b/ocaml/test/infrastructure/dune new file mode 100644 index 0000000..5d493d9 --- /dev/null +++ b/ocaml/test/infrastructure/dune @@ -0,0 +1,8 @@ +(tests + (names + test_example) + (libraries + application + domain + infrastructure + alcotest)) diff --git a/ocaml/test/infrastructure/test_example.ml b/ocaml/test/infrastructure/test_example.ml new file mode 100644 index 0000000..e69de29 diff --git a/ocaml/test/presentation/dune b/ocaml/test/presentation/dune new file mode 100644 index 0000000..e34e56f --- /dev/null +++ b/ocaml/test/presentation/dune @@ -0,0 +1,11 @@ +(tests + (names + test_example) + (libraries + application + domain + infrastructure + presentation + alcotest + qcheck-core + qcheck-alcotest)) diff --git a/ocaml/test/presentation/test_example.ml b/ocaml/test/presentation/test_example.ml new file mode 100644 index 0000000..e69de29 diff --git a/php/2024/.envrc b/php/2024/.envrc new file mode 100644 index 0000000..cffc922 --- /dev/null +++ b/php/2024/.envrc @@ -0,0 +1 @@ +use flake . --impure diff --git a/php/2024/README.md b/php/2024/README.md new file mode 100644 index 0000000..6aeb9d6 --- /dev/null +++ b/php/2024/README.md @@ -0,0 +1,11 @@ +# PHP 2024 + +## Getting Started + +### How to Run + +Run the php script directly, e.g. + +```sh +php day_XX/part_YY.php +``` diff --git a/php/2024/day_04/Grid.php b/php/2024/day_04/Grid.php new file mode 100644 index 0000000..75d531b --- /dev/null +++ b/php/2024/day_04/Grid.php @@ -0,0 +1,69 @@ + array_reduce( + array: $row, + callback: fn (string $acc, string $character): string => $acc . $character, + initial: "", + ); + + return array_reduce( + array: $this->data, + callback: fn (string $acc, array $row): string => $acc . $rowToLine($row) . PHP_EOL, + initial: "", + ); + } + + public function getCharacterAtPosition(Position $position): string + { + if ($this->isInBounds($position)) { + return $this->data[$position->y][$position->x]; + } + + return "."; + } + + /** + * @param Position[] $positions + */ + public function getStringFromPositions(array $positions): string + { + $characters = array_map( + callback: fn (Position $position): string => $this->getCharacterAtPosition($position), + array: $positions, + ); + + $string = array_reduce( + array: $characters, + callback: fn (string $acc, string $character): string => $acc . $character, + initial: "", + ); + + return $string; + } + + public function isInBounds(Position $position): bool + { + if (!array_key_exists($position->y, $this->data)) { + return false; + } + + if (!array_key_exists($position->x, $this->data[$position->y])) { + return false; + } + + return true; + } +} diff --git a/php/2024/day_04/Input.php b/php/2024/day_04/Input.php new file mode 100644 index 0000000..60a16a0 --- /dev/null +++ b/php/2024/day_04/Input.php @@ -0,0 +1,25 @@ +fileName, use_include_path: true); + $this->contents = $contents === false ? "" : $contents; + } + + public function toGrid(): Grid + { + $lines = explode("\n", $this->contents); + $gridArray = array_map( + callback: fn (string $line): array => str_split(trim($line)), + array: $lines + ); + + return new Grid($gridArray); + } +} diff --git a/php/2024/day_04/Position.php b/php/2024/day_04/Position.php new file mode 100644 index 0000000..0dcf96c --- /dev/null +++ b/php/2024/day_04/Position.php @@ -0,0 +1,79 @@ +x}, {$this->y})"; + } + + /** + * Not validated + */ + public function getNorth(): self + { + return new self($this->x, $this->y - 1); + } + + /** + * Not validated + */ + public function getNorthEast(): self + { + return new self($this->x + 1, $this->y - 1); + } + + /** + * Not validated + */ + public function getEast(): self + { + return new self($this->x + 1, $this->y); + } + + /** + * Not validated + */ + public function getSouthEast(): self + { + return new self($this->x + 1, $this->y + 1); + } + + /** + * Not validated + */ + public function getSouth(): self + { + return new self($this->x, $this->y + 1); + } + + /** + * Not validated + */ + public function getSouthWest(): self + { + return new self($this->x - 1, $this->y + 1); + } + + /** + * Not validated + */ + public function getWest(): self + { + return new self($this->x - 1, $this->y); + } + + /** + * Not validated + */ + public function getNorthWest(): self + { + return new self($this->x - 1, $this->y - 1); + } +} diff --git a/php/2024/day_04/part_01.php b/php/2024/day_04/part_01.php new file mode 100644 index 0000000..0faa5b0 --- /dev/null +++ b/php/2024/day_04/part_01.php @@ -0,0 +1,94 @@ + require "{$className}.php"); + +// Assumes executed from workspace root +$input = new Input('../_input/day_04.txt'); +if ($input->contents === "") { + throw new RuntimeException("File not found"); +} + +$grid = $input->toGrid(); + +$matchCount = 0; +foreach ($grid->data as $y => $row) { + foreach ($row as $x => $char) { + if ($char !== 'X') { + continue; + } + + $start = new Position($x, $y); + + $possibleMatches = []; + + $possibleMatches["north"] = $grid->getStringFromPositions([ + $start, + $start->getNorth(), + $start->getNorth()->getNorth(), + $start->getNorth()->getNorth()->getNorth(), + ]); + + $possibleMatches["east"] = $grid->getStringFromPositions([ + $start, + $start->getEast(), + $start->getEast()->getEast(), + $start->getEast()->getEast()->getEast(), + ]); + + $possibleMatches["south"] = $grid->getStringFromPositions([ + $start, + $start->getSouth(), + $start->getSouth()->getSouth(), + $start->getSouth()->getSouth()->getSouth(), + ]); + + $possibleMatches["west"] = $grid->getStringFromPositions([ + $start, + $start->getWest(), + $start->getWest()->getWest(), + $start->getWest()->getWest()->getWest(), + ]); + + $possibleMatches["north-east"] = $grid->getStringFromPositions([ + $start, + $start->getNorthEast(), + $start->getNorthEast()->getNorthEast(), + $start->getNorthEast()->getNorthEast()->getNorthEast(), + ]); + + $possibleMatches["north-west"] = $grid->getStringFromPositions([ + $start, + $start->getNorthWest(), + $start->getNorthWest()->getNorthWest(), + $start->getNorthWest()->getNorthWest()->getNorthWest(), + ]); + + $possibleMatches["south-east"] = $grid->getStringFromPositions([ + $start, + $start->getSouthEast(), + $start->getSouthEast()->getSouthEast(), + $start->getSouthEast()->getSouthEast()->getSouthEast(), + ]); + + $possibleMatches["south-west"] = $grid->getStringFromPositions([ + $start, + $start->getSouthWest(), + $start->getSouthWest()->getSouthWest(), + $start->getSouthWest()->getSouthWest()->getSouthWest(), + ]); + + $matchCount += array_reduce( + array: $possibleMatches, + callback: fn (int $acc, string $possibleMatch): int => match ($possibleMatch) { + "XMAS" => $acc + 1, + default => $acc, + }, + initial: 0, + ); + } +} + +// ANSWER: 2517 +echo $matchCount; diff --git a/php/2024/flake.lock b/php/2024/flake.lock new file mode 100644 index 0000000..b7695d9 --- /dev/null +++ b/php/2024/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1741010256, + "narHash": "sha256-WZNlK/KX7Sni0RyqLSqLPbK8k08Kq7H7RijPJbq9KHM=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "ba487dbc9d04e0634c64e3b1f0d25839a0a68246", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/php/2024/flake.nix b/php/2024/flake.nix new file mode 100644 index 0000000..2be5644 --- /dev/null +++ b/php/2024/flake.nix @@ -0,0 +1,43 @@ +{ + description = "Advent of Code 2024: PHP"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + + outputs = + { nixpkgs, ... }: + let + pkgs = import nixpkgs { + config.allowUnfree = true; + }; + in + { + devShells.x86_64-linux.default = + let + phpVersion = "php84"; + + phpPackage = pkgs.lib.attrByPath [ phpVersion ] null pkgs; + phpPackages = pkgs.lib.attrByPath [ "${phpVersion}Packages" ] null pkgs; + phpExtensions = pkgs.lib.attrByPath [ "${phpVersion}Extensions" ] null pkgs; + in + pkgs.mkShell { + # build tools + nativeBuildInputs = with pkgs; [ + phpPackage + nodejs_18 + ]; + # dependencies + buildInputs = with pkgs; [ + intelephense + phpunit + phpPackages.php-cs-fixer + phpPackages.phpstan + phpExtensions.xdebug + + gh # GitHub CLI + ]; + }; + + }; +} -- cgit v1.2.3