From 4c3a50166bf0f78ce89459c352cc1c6536a8b96d Mon Sep 17 00:00:00 2001 From: DJ O'Leary Date: Sat, 29 Nov 2025 20:23:02 +0100 Subject: chore: make language folders snake_case --- 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 ----- 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/OCaml/.envrc | 1 - 2024/OCaml/.gitignore | 29 -- 2024/OCaml/.ocamlformat | 2 - 2024/OCaml/AoC_2024.opam | 32 -- 2024/OCaml/README.md | 21 -- 2024/OCaml/Taskfile.yml | 56 --- 2024/OCaml/bin/dune | 5 - 2024/OCaml/bin/main.ml | 46 --- 2024/OCaml/dune-project | 28 -- 2024/OCaml/flake.lock | 27 -- 2024/OCaml/flake.nix | 44 --- 2024/OCaml/lib/day_01/part_01.ml | 40 --- 2024/OCaml/lib/day_01/part_02.ml | 44 --- 2024/OCaml/lib/day_02/part_01.ml | 42 --- 2024/OCaml/lib/day_03/part_01.ml | 33 -- 2024/OCaml/lib/day_04/part_01.ml | 85 ----- 2024/OCaml/lib/day_05/part_01.ml | 62 ---- 2024/OCaml/lib/dune | 5 - 2024/OCaml/lib/file/read_input.ml | 11 - 2024/OCaml/lib/grid/cell.ml | 18 - 2024/OCaml/lib/grid/cell.mli | 18 - 2024/OCaml/lib/grid/grid.ml | 14 - 2024/OCaml/lib/grid/grid.mli | 6 - 2024/OCaml/test/day_01_part_01.ml | 27 -- 2024/OCaml/test/day_01_part_02.ml | 17 - 2024/OCaml/test/day_02_part_01.ml | 38 --- 2024/OCaml/test/day_03_part_01.ml | 29 -- 2024/OCaml/test/day_04_part_01.ml | 151 --------- 2024/OCaml/test/day_05_part_01.ml | 4 - 2024/OCaml/test/dune | 10 - 2024/OCaml/test/grid.ml | 5 - 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 --- 2024/ocaml/.envrc | 1 + 2024/ocaml/.gitignore | 29 ++ 2024/ocaml/.ocamlformat | 2 + 2024/ocaml/AoC_2024.opam | 32 ++ 2024/ocaml/README.md | 21 ++ 2024/ocaml/Taskfile.yml | 56 +++ 2024/ocaml/bin/dune | 5 + 2024/ocaml/bin/main.ml | 46 +++ 2024/ocaml/dune-project | 28 ++ 2024/ocaml/flake.lock | 27 ++ 2024/ocaml/flake.nix | 44 +++ 2024/ocaml/lib/day_01/part_01.ml | 40 +++ 2024/ocaml/lib/day_01/part_02.ml | 44 +++ 2024/ocaml/lib/day_02/part_01.ml | 42 +++ 2024/ocaml/lib/day_03/part_01.ml | 33 ++ 2024/ocaml/lib/day_04/part_01.ml | 85 +++++ 2024/ocaml/lib/day_05/part_01.ml | 62 ++++ 2024/ocaml/lib/dune | 5 + 2024/ocaml/lib/file/read_input.ml | 11 + 2024/ocaml/lib/grid/cell.ml | 18 + 2024/ocaml/lib/grid/cell.mli | 18 + 2024/ocaml/lib/grid/grid.ml | 14 + 2024/ocaml/lib/grid/grid.mli | 6 + 2024/ocaml/test/day_01_part_01.ml | 27 ++ 2024/ocaml/test/day_01_part_02.ml | 17 + 2024/ocaml/test/day_02_part_01.ml | 38 +++ 2024/ocaml/test/day_03_part_01.ml | 29 ++ 2024/ocaml/test/day_04_part_01.ml | 151 +++++++++ 2024/ocaml/test/day_05_part_01.ml | 4 + 2024/ocaml/test/dune | 10 + 2024/ocaml/test/grid.ml | 5 + 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 +++ 132 files changed, 3823 insertions(+), 3823 deletions(-) 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 create mode 100644 2023/go/.envrc create mode 100644 2023/go/.gitignore create mode 100644 2023/go/README.md create mode 100644 2023/go/Taskfile.yml create mode 100644 2023/go/flake.lock create mode 100644 2023/go/flake.nix create mode 100644 2023/go/go.mod create mode 100644 2023/go/go.sum create mode 100644 2023/go/internal/day_01/day_01.go create mode 100644 2023/go/internal/day_01/day_01_test.go create mode 100644 2023/go/internal/day_02/part_01/part_01.go create mode 100644 2023/go/internal/day_02/part_01/part_01_test.go create mode 100644 2023/go/internal/day_02/part_02/part_02.go create mode 100644 2023/go/internal/day_02/part_02/part_02_test.go create mode 100644 2023/go/internal/day_03/part_01/part_01.go create mode 100644 2023/go/internal/day_03/part_01/part_01_test.go create mode 100644 2023/go/internal/day_04/part_01/part_01.go create mode 100644 2023/go/internal/day_04/part_01/part_01_test.go create mode 100644 2023/go/internal/day_04/part_02/part_02.go create mode 100644 2023/go/internal/day_04/part_02/part_02_test.go create mode 100644 2023/go/internal/day_06/part_01/part_01.go create mode 100644 2023/go/internal/day_06/part_01/part_01_test.go create mode 100644 2023/go/internal/day_06/part_02/part_02.go create mode 100644 2023/go/internal/day_06/part_02/part_02_test.go create mode 100644 2023/go/internal/day_07/part_01/part_01.go create mode 100644 2023/go/internal/day_07/part_01/part_01_test.go create mode 100644 2023/go/main.go delete mode 100644 2024/OCaml/.envrc delete mode 100644 2024/OCaml/.gitignore delete mode 100644 2024/OCaml/.ocamlformat delete mode 100644 2024/OCaml/AoC_2024.opam delete mode 100644 2024/OCaml/README.md delete mode 100644 2024/OCaml/Taskfile.yml delete mode 100644 2024/OCaml/bin/dune delete mode 100644 2024/OCaml/bin/main.ml delete mode 100644 2024/OCaml/dune-project delete mode 100644 2024/OCaml/flake.lock delete mode 100644 2024/OCaml/flake.nix delete mode 100644 2024/OCaml/lib/day_01/part_01.ml delete mode 100644 2024/OCaml/lib/day_01/part_02.ml delete mode 100644 2024/OCaml/lib/day_02/part_01.ml delete mode 100644 2024/OCaml/lib/day_03/part_01.ml delete mode 100644 2024/OCaml/lib/day_04/part_01.ml delete mode 100644 2024/OCaml/lib/day_05/part_01.ml delete mode 100644 2024/OCaml/lib/dune delete mode 100644 2024/OCaml/lib/file/read_input.ml delete mode 100644 2024/OCaml/lib/grid/cell.ml delete mode 100644 2024/OCaml/lib/grid/cell.mli delete mode 100644 2024/OCaml/lib/grid/grid.ml delete mode 100644 2024/OCaml/lib/grid/grid.mli delete mode 100644 2024/OCaml/test/day_01_part_01.ml delete mode 100644 2024/OCaml/test/day_01_part_02.ml delete mode 100644 2024/OCaml/test/day_02_part_01.ml delete mode 100644 2024/OCaml/test/day_03_part_01.ml delete mode 100644 2024/OCaml/test/day_04_part_01.ml delete mode 100644 2024/OCaml/test/day_05_part_01.ml delete mode 100644 2024/OCaml/test/dune delete mode 100644 2024/OCaml/test/grid.ml 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 create mode 100644 2024/ocaml/.envrc create mode 100644 2024/ocaml/.gitignore create mode 100644 2024/ocaml/.ocamlformat create mode 100644 2024/ocaml/AoC_2024.opam create mode 100644 2024/ocaml/README.md create mode 100644 2024/ocaml/Taskfile.yml create mode 100644 2024/ocaml/bin/dune create mode 100644 2024/ocaml/bin/main.ml create mode 100644 2024/ocaml/dune-project create mode 100644 2024/ocaml/flake.lock create mode 100644 2024/ocaml/flake.nix create mode 100644 2024/ocaml/lib/day_01/part_01.ml create mode 100644 2024/ocaml/lib/day_01/part_02.ml create mode 100644 2024/ocaml/lib/day_02/part_01.ml create mode 100644 2024/ocaml/lib/day_03/part_01.ml create mode 100644 2024/ocaml/lib/day_04/part_01.ml create mode 100644 2024/ocaml/lib/day_05/part_01.ml create mode 100644 2024/ocaml/lib/dune create mode 100644 2024/ocaml/lib/file/read_input.ml create mode 100644 2024/ocaml/lib/grid/cell.ml create mode 100644 2024/ocaml/lib/grid/cell.mli create mode 100644 2024/ocaml/lib/grid/grid.ml create mode 100644 2024/ocaml/lib/grid/grid.mli create mode 100644 2024/ocaml/test/day_01_part_01.ml create mode 100644 2024/ocaml/test/day_01_part_02.ml create mode 100644 2024/ocaml/test/day_02_part_01.ml create mode 100644 2024/ocaml/test/day_03_part_01.ml create mode 100644 2024/ocaml/test/day_04_part_01.ml create mode 100644 2024/ocaml/test/day_05_part_01.ml create mode 100644 2024/ocaml/test/dune create mode 100644 2024/ocaml/test/grid.ml create mode 100644 2024/php/.envrc create mode 100644 2024/php/README.md create mode 100644 2024/php/day_04/Grid.php create mode 100644 2024/php/day_04/Input.php create mode 100644 2024/php/day_04/Position.php create mode 100644 2024/php/day_04/part_01.php create mode 100644 2024/php/flake.lock create mode 100644 2024/php/flake.nix 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/2023/go/.envrc b/2023/go/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/2023/go/.envrc @@ -0,0 +1 @@ +use flake diff --git a/2023/go/.gitignore b/2023/go/.gitignore new file mode 100644 index 0000000..9f4cb5b --- /dev/null +++ b/2023/go/.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/2023/go/README.md b/2023/go/README.md new file mode 100644 index 0000000..9cda93b --- /dev/null +++ b/2023/go/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/2023/go/Taskfile.yml b/2023/go/Taskfile.yml new file mode 100644 index 0000000..a07c24c --- /dev/null +++ b/2023/go/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/2023/go/flake.lock b/2023/go/flake.lock new file mode 100644 index 0000000..b7695d9 --- /dev/null +++ b/2023/go/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/2023/go/flake.nix b/2023/go/flake.nix new file mode 100644 index 0000000..39dda89 --- /dev/null +++ b/2023/go/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/2023/go/go.mod b/2023/go/go.mod new file mode 100644 index 0000000..9fba23c --- /dev/null +++ b/2023/go/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/2023/go/go.sum b/2023/go/go.sum new file mode 100644 index 0000000..3c60b26 --- /dev/null +++ b/2023/go/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/2023/go/internal/day_01/day_01.go b/2023/go/internal/day_01/day_01.go new file mode 100644 index 0000000..9cd0a49 --- /dev/null +++ b/2023/go/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/2023/go/internal/day_01/day_01_test.go b/2023/go/internal/day_01/day_01_test.go new file mode 100644 index 0000000..8063619 --- /dev/null +++ b/2023/go/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/2023/go/internal/day_02/part_01/part_01.go b/2023/go/internal/day_02/part_01/part_01.go new file mode 100644 index 0000000..9efe20f --- /dev/null +++ b/2023/go/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/2023/go/internal/day_02/part_01/part_01_test.go b/2023/go/internal/day_02/part_01/part_01_test.go new file mode 100644 index 0000000..33e5fbf --- /dev/null +++ b/2023/go/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/2023/go/internal/day_02/part_02/part_02.go b/2023/go/internal/day_02/part_02/part_02.go new file mode 100644 index 0000000..ffb9691 --- /dev/null +++ b/2023/go/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/2023/go/internal/day_02/part_02/part_02_test.go b/2023/go/internal/day_02/part_02/part_02_test.go new file mode 100644 index 0000000..6952899 --- /dev/null +++ b/2023/go/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/2023/go/internal/day_03/part_01/part_01.go b/2023/go/internal/day_03/part_01/part_01.go new file mode 100644 index 0000000..49837b3 --- /dev/null +++ b/2023/go/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/2023/go/internal/day_03/part_01/part_01_test.go b/2023/go/internal/day_03/part_01/part_01_test.go new file mode 100644 index 0000000..a077fbd --- /dev/null +++ b/2023/go/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/2023/go/internal/day_04/part_01/part_01.go b/2023/go/internal/day_04/part_01/part_01.go new file mode 100644 index 0000000..1a215b1 --- /dev/null +++ b/2023/go/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/2023/go/internal/day_04/part_01/part_01_test.go b/2023/go/internal/day_04/part_01/part_01_test.go new file mode 100644 index 0000000..f6f1a83 --- /dev/null +++ b/2023/go/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/2023/go/internal/day_04/part_02/part_02.go b/2023/go/internal/day_04/part_02/part_02.go new file mode 100644 index 0000000..a9e3152 --- /dev/null +++ b/2023/go/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/2023/go/internal/day_04/part_02/part_02_test.go b/2023/go/internal/day_04/part_02/part_02_test.go new file mode 100644 index 0000000..3c66a2e --- /dev/null +++ b/2023/go/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/2023/go/internal/day_06/part_01/part_01.go b/2023/go/internal/day_06/part_01/part_01.go new file mode 100644 index 0000000..5735924 --- /dev/null +++ b/2023/go/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/2023/go/internal/day_06/part_01/part_01_test.go b/2023/go/internal/day_06/part_01/part_01_test.go new file mode 100644 index 0000000..7af828c --- /dev/null +++ b/2023/go/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/2023/go/internal/day_06/part_02/part_02.go b/2023/go/internal/day_06/part_02/part_02.go new file mode 100644 index 0000000..316b169 --- /dev/null +++ b/2023/go/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/2023/go/internal/day_06/part_02/part_02_test.go b/2023/go/internal/day_06/part_02/part_02_test.go new file mode 100644 index 0000000..67924dc --- /dev/null +++ b/2023/go/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/2023/go/internal/day_07/part_01/part_01.go b/2023/go/internal/day_07/part_01/part_01.go new file mode 100644 index 0000000..a485dfd --- /dev/null +++ b/2023/go/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/2023/go/internal/day_07/part_01/part_01_test.go b/2023/go/internal/day_07/part_01/part_01_test.go new file mode 100644 index 0000000..6ceabe8 --- /dev/null +++ b/2023/go/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/2023/go/main.go b/2023/go/main.go new file mode 100644 index 0000000..7d71b47 --- /dev/null +++ b/2023/go/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/2024/OCaml/.envrc b/2024/OCaml/.envrc deleted file mode 100644 index 3550a30..0000000 --- a/2024/OCaml/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/2024/OCaml/.gitignore b/2024/OCaml/.gitignore deleted file mode 100644 index a18e084..0000000 --- a/2024/OCaml/.gitignore +++ /dev/null @@ -1,29 +0,0 @@ -*.annot -*.cmo -*.cma -*.cmi -*.a -*.o -*.cmx -*.cmxs -*.cmxa - -# ocamlbuild 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/ diff --git a/2024/OCaml/.ocamlformat b/2024/OCaml/.ocamlformat deleted file mode 100644 index 912bea4..0000000 --- a/2024/OCaml/.ocamlformat +++ /dev/null @@ -1,2 +0,0 @@ -profile = default -version = 0.27.0 diff --git a/2024/OCaml/AoC_2024.opam b/2024/OCaml/AoC_2024.opam deleted file mode 100644 index 6be14e1..0000000 --- a/2024/OCaml/AoC_2024.opam +++ /dev/null @@ -1,32 +0,0 @@ -# This file is generated by dune, edit dune-project instead -opam-version: "2.0" -synopsis: "Advent of Code 2024" -description: "Solutions to the Advent of Code puzzles for 2024" -maintainer: ["D.J. O'Leary"] -authors: ["D.J. O'Leary"] -license: "Unlicense" -tags: ["aoc2024" "Advent of Code 2024"] -homepage: "https://github.com/DJOLEARY/Advent_of_Code" -doc: - "https://github.com/DJOLEARY/Advent_of_Code/blob/main/2024/OCaml/README.md" -bug-reports: "https://github.com/DJOLEARY/Advent_of_Code/issues" -depends: [ - "ocaml" - "dune" {>= "3.16"} - "odoc" {with-doc} -] -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" diff --git a/2024/OCaml/README.md b/2024/OCaml/README.md deleted file mode 100644 index caa88fe..0000000 --- a/2024/OCaml/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# OCaml 2024 - -## Getting Started - -### How to Build - -```sh -task build -``` - -### How to Run - -```sh -task run -``` - -### How to Test - -```sh -task test -``` diff --git a/2024/OCaml/Taskfile.yml b/2024/OCaml/Taskfile.yml deleted file mode 100644 index 1d8a89e..0000000 --- a/2024/OCaml/Taskfile.yml +++ /dev/null @@ -1,56 +0,0 @@ -# https://taskfile.dev - -version: "3" -vars: - PROJECT_NAME: AoC_2024 -tasks: - default: - desc: Prints this message - cmds: - - cmd: task --list-all - build:doc: - desc: Builds documentation using ocamldoc - preconditions: - - which ocamldoc - cmds: - - cmd: ocamldoc -html -d docs bin/main.ml - silent: true - build: - desc: Builds the project - preconditions: - - which dune - cmds: - - cmd: dune build - test: - desc: Runs the tests in the 'tests' directory - ignore_error: true - env: - OUNIT_CI: true - preconditions: - - which dune - deps: - - task: build - cmds: - - cmd: dune test {{.CLI_ARGS}} - test:watch: - desc: Continuously runs the tests in the 'tests' directory - ignore_error: true - sources: - - lib/**/*.ml - - test/**/*.ml - env: - OUNIT_CI: true - preconditions: - - which dune - deps: - - task: build - cmds: - - cmd: dune test --watch {{.CLI_ARGS}} - run: - desc: Runs the project - preconditions: - - which dune - deps: - - task: build - cmds: - - cmd: dune exec {{.PROJECT_NAME}} diff --git a/2024/OCaml/bin/dune b/2024/OCaml/bin/dune deleted file mode 100644 index ee36b46..0000000 --- a/2024/OCaml/bin/dune +++ /dev/null @@ -1,5 +0,0 @@ -(executable - (public_name AoC_2024) - (name main) - (modes byte exe) - (libraries AoC_2024)) diff --git a/2024/OCaml/bin/main.ml b/2024/OCaml/bin/main.ml deleted file mode 100644 index a76e046..0000000 --- a/2024/OCaml/bin/main.ml +++ /dev/null @@ -1,46 +0,0 @@ -open AoC_2024 - -let day_to_path day = - let string_day = string_of_int day in - let formatted_day = if day < 10 then "0" ^ string_day else string_day in - "../_input/day_" ^ formatted_day ^ ".txt" - -let print_solution day_num part_num answer = - print_string "D"; - print_int day_num; - print_string ":P"; - print_int part_num; - print_string " -> "; - print_int answer; - print_newline () - -let day_1 = - let lines = File.Read_input.lines_from_file (day_to_path 1) in - let left, right = Day_01.Part_01.lines_to_lists lines in - let part_01 = Day_01.Part_01.solve left right in - print_solution 1 1 part_01; - let part_02 = Day_01.Part_02.solve left right in - print_solution 1 2 part_02 - -let day_2 = - let lines = File.Read_input.lines_from_file (day_to_path 2) in - let reports = Day_02.Part_01.lines_to_reports lines in - let part_01 = Day_02.Part_01.solve reports in - print_solution 2 1 part_01 - -let day_3 = - let lines = File.Read_input.lines_from_file (day_to_path 3) in - let line = List.fold_left (fun acc line -> acc ^ line) "" lines in - let part_01 = Day_03.Part_01.solve line in - print_solution 3 1 part_01 - -let day_4 = - let lines = File.Read_input.lines_from_file (day_to_path 4) in - let part_01 = Day_04.Part_01.solve lines in - print_solution 4 1 part_01 - -let _ = - day_1; - day_2; - day_3; - day_4 diff --git a/2024/OCaml/dune-project b/2024/OCaml/dune-project deleted file mode 100644 index 3f3a7bc..0000000 --- a/2024/OCaml/dune-project +++ /dev/null @@ -1,28 +0,0 @@ -(lang dune 3.16) - -(name AoC_2024) - -(generate_opam_files true) - -(source - (github DJOLEARY/Advent_of_Code)) - -(authors "D.J. O'Leary") - -(maintainers "D.J. O'Leary") - -(license Unlicense) - -(documentation https://github.com/DJOLEARY/Advent_of_Code/blob/main/2024/OCaml/README.md) - -(map_workspace_root false) - -(package - (name AoC_2024) - (synopsis "Advent of Code 2024") - (description "Solutions to the Advent of Code puzzles for 2024") - (depends ocaml dune) - (tags - (aoc2024 "Advent of Code 2024"))) - -; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html diff --git a/2024/OCaml/flake.lock b/2024/OCaml/flake.lock deleted file mode 100644 index b7695d9..0000000 --- a/2024/OCaml/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/OCaml/flake.nix b/2024/OCaml/flake.nix deleted file mode 100644 index 74a5dc6..0000000 --- a/2024/OCaml/flake.nix +++ /dev/null @@ -1,44 +0,0 @@ -{ - description = "Advent of Code 2024: OCaml"; - - 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 = pkgs.ocaml-ng.ocamlPackages_5_2; - in - pkgs.mkShell { - # build tools - nativeBuildInputs = with ocamlPackages; [ - pkgs.opam - ocaml - findlib - dune_3 - ]; - # dependencies - buildInputs = with ocamlPackages; [ - bisect_ppx - findlib - menhir - ocaml-lsp - earlybird - ocamlformat - ocamlgraph - odoc - ounit2 - re2 - utop - - pkgs.gh # GitHub CLI - ]; - }; - }; -} diff --git a/2024/OCaml/lib/day_01/part_01.ml b/2024/OCaml/lib/day_01/part_01.ml deleted file mode 100644 index 100b12a..0000000 --- a/2024/OCaml/lib/day_01/part_01.ml +++ /dev/null @@ -1,40 +0,0 @@ -(* Day 1: Part 1 *) - -let calculate_distance x y = Stdlib.abs (x - y) - -(** Require: left and right to be the same length *) -let solve left right = - let sorted_left = List.sort Stdlib.compare left in - let sorted_right = List.sort Stdlib.compare right in - let distances = List.map2 calculate_distance sorted_left sorted_right in - List.fold_left ( + ) 0 distances - -let left line = - let pos = 0 in - let len = String.index line ' ' in - String.sub line pos len - -let right line = - let pos = String.rindex line ' ' + 1 in - let len = String.length line - pos in - String.sub line pos len - -let line_to_tuple line = (left line, right line) - -let lines_to_lists lines = - let tuples = List.map line_to_tuple lines in - let left = - List.map - (fun t -> - let l, _ = t in - int_of_string l) - tuples - in - let right = - List.map - (fun t -> - let _, r = t in - int_of_string r) - tuples - in - (left, right) diff --git a/2024/OCaml/lib/day_01/part_02.ml b/2024/OCaml/lib/day_01/part_02.ml deleted file mode 100644 index 25c8b7d..0000000 --- a/2024/OCaml/lib/day_01/part_02.ml +++ /dev/null @@ -1,44 +0,0 @@ -(* Day 1: Part 2 *) - -let rec count_occurrances_help element acc lst = - match lst with - | [] -> acc - | h :: t -> - if h = element then count_occurrances_help element (acc + 1) t - else count_occurrances_help element acc t - -let count_occurrances element lst = count_occurrances_help element 0 lst - -let solve left right = - List.map (fun x -> x * count_occurrances x right) left - |> List.fold_left ( + ) 0 - -let left line = - let pos = 0 in - let len = String.index line ' ' in - String.sub line pos len - -let right line = - let pos = String.rindex line ' ' + 1 in - let len = String.length line - pos in - String.sub line pos len - -let line_to_tuple line = (left line, right line) - -let lines_to_lists lines = - let tuples = List.map line_to_tuple lines in - let left = - List.map - (fun t -> - let l, _ = t in - int_of_string l) - tuples - in - let right = - List.map - (fun t -> - let _, r = t in - int_of_string r) - tuples - in - (left, right) diff --git a/2024/OCaml/lib/day_02/part_01.ml b/2024/OCaml/lib/day_02/part_01.ml deleted file mode 100644 index 32d7ab8..0000000 --- a/2024/OCaml/lib/day_02/part_01.ml +++ /dev/null @@ -1,42 +0,0 @@ -(* Day 2: Part 1 *) - -type level = int -type reports = level list list - -let rec compare_report_levels_helper comparison prev report = - match report with - | [] -> true - | h :: t -> comparison prev h && compare_report_levels_helper comparison h t - -let compare_report_levels comparison report = - match report with - | [] -> true - | h :: t -> compare_report_levels_helper comparison h t - -let all_increasing report = compare_report_levels ( < ) report -let all_decreasing report = compare_report_levels ( > ) report - -let at_least_one report = - compare_report_levels (fun prev curr -> prev <> curr) report - -let at_most_three report = - compare_report_levels (fun prev curr -> abs (prev - curr) <= 3) report - -let check_report_safety report = - if not (all_increasing report || all_decreasing report) then false - else if not (at_least_one report && at_most_three report) then false - else true - -let rec count_safe_reports acc lst = - match lst with - | [] -> acc - | h :: t -> - if h then count_safe_reports (acc + 1) t else count_safe_reports acc t - -let solve reports = - reports |> List.map check_report_safety |> count_safe_reports 0 - -let line_to_report line = - line |> String.split_on_char ' ' |> List.map int_of_string - -let lines_to_reports lines = List.map line_to_report lines diff --git a/2024/OCaml/lib/day_03/part_01.ml b/2024/OCaml/lib/day_03/part_01.ml deleted file mode 100644 index c12b68a..0000000 --- a/2024/OCaml/lib/day_03/part_01.ml +++ /dev/null @@ -1,33 +0,0 @@ -(* Day 3: Part 1 *) - -let find_mul text = - let regex_or_err = Re2.create "mul\\(([0-9]{1,3}),([0-9]{1,3})\\)" in - match regex_or_err with - | Ok regex -> ( - let matches = Re2.get_matches regex text in - match matches with Ok matches -> matches | Error _ -> []) - | Error _ -> [] - -let extract_pairs (matches : Re2.Match.t list) : (int * int) list = - List.map - (fun x -> - let left = Re2.Match.get x ~sub:(`Index 1) in - match left with - | None -> (0, 0) - | Some left -> ( - let left_num = int_of_string left in - let right = Re2.Match.get x ~sub:(`Index 2) in - match right with - | None -> (0, 0) - | Some right -> - let right_num = int_of_string right in - (left_num, right_num))) - matches - -let solve input = - input |> find_mul |> extract_pairs - |> List.fold_left - (fun acc pair -> - let left, right = pair in - acc + (left * right)) - 0 diff --git a/2024/OCaml/lib/day_04/part_01.ml b/2024/OCaml/lib/day_04/part_01.ml deleted file mode 100644 index acb3e3b..0000000 --- a/2024/OCaml/lib/day_04/part_01.ml +++ /dev/null @@ -1,85 +0,0 @@ -(* Day 4: Part 1 *) - -type character = { index : int; character : char } -type direction = N | NE | E | SE | S | SW | W | NW - -let all_directions = [ N; NE; E; SE; S; SW; W; NW ] -let needle = "XMAS" - -(** [calculate_row_offset lines] is the offset used to move between the rows of - the concatenated variant of [lines] as though it were a grid. Requires: - values of [lines] all have the same length *) -let calculate_row_offset lines = - match lines with [] -> 0 | h :: _ -> String.length h - -let join_lines lines = - let concat_trim acc line = acc ^ String.trim line in - List.fold_left concat_trim "" lines - -let explode s = List.init (String.length s) (String.get s) -let is_in_bounds length index = index >= 0 && index < length - -let get_at_index line index = - let length = String.length line in - if not (is_in_bounds length index) then "." - else - let character = String.get line index in - Char.escaped character - -let get_direction_index row_offset direction index = - match direction with - | N -> index - row_offset - | NE -> index - row_offset + 1 - | E -> index + 1 - | SE -> index + row_offset + 1 - | S -> index + row_offset - | SW -> index + row_offset - 1 - | W -> index - 1 - | NW -> index - row_offset - 1 - -let get_direction_string get_char get_offset_index index = - let first = get_char index in - let second_index = get_offset_index index in - let second = get_char second_index in - let third_index = get_offset_index second_index in - let third = get_char third_index in - let fourth_index = get_offset_index third_index in - let fourth = get_char fourth_index in - first ^ second ^ third ^ fourth - -let check_direction get_from_line get_offset_index index direction = - get_direction_string get_from_line (get_offset_index direction) index = needle - -let calculate_num_of_matches line row_offset character = - let get_from_line_at_index = get_at_index line in - let get_offset_direction_index = get_direction_index row_offset in - let check_direction_from_index = - check_direction get_from_line_at_index get_offset_direction_index - character.index - in - all_directions - |> List.map check_direction_from_index - |> List.map (fun x -> match x with true -> 1 | false -> 0) - |> List.fold_left ( + ) 0 - -(* Tried: - 2544 -> too high - 1272 (previous halved) -> too low *) -let solve lines = - let row_offset = calculate_row_offset lines in - let line = join_lines lines in - let chars = explode line in - let characters = List.mapi (fun i c -> { index = i; character = c }) chars in - let xs = - List.filter - (fun c -> match c.character with 'X' -> true | _ -> false) - characters - in - let calculate_num_of_surrounding_matches_in_line = - calculate_num_of_matches line row_offset - in - let num_of_matches = - List.map calculate_num_of_surrounding_matches_in_line xs - |> List.fold_left ( + ) 0 - in - num_of_matches diff --git a/2024/OCaml/lib/day_05/part_01.ml b/2024/OCaml/lib/day_05/part_01.ml deleted file mode 100644 index f32bef1..0000000 --- a/2024/OCaml/lib/day_05/part_01.ml +++ /dev/null @@ -1,62 +0,0 @@ -(* Day 5: Part 1 *) - -module StringMap = Map.Make (String) - -let inverse_page_rule page_rule = - String.split_on_char '|' page_rule |> fun lst -> - match lst with [] -> "" | _ :: _ -> String.concat "|" (List.rev lst) - -(** [parse_aux acc page_rules] is a map of all invalid transitions as defined by - [page_rules] *) -let rec get_invalid_transitions_aux acc page_rules : unit StringMap.t = - match page_rules with - | [] -> acc - | h :: t -> - get_invalid_transitions_aux - (acc |> StringMap.add (inverse_page_rule h) ()) - t - -let get_invalid_transitions page_rules = - get_invalid_transitions_aux StringMap.empty page_rules - -(** [solve page_rules updates] is the solution for this puzzle, given the - [page_rules] and a list of [updates]. - - BELOW DOESN'T WORK: no guarantee that second page comes immediately after - the first - - pass in updates to solve - - make sliding window of 2 elements - - invert element order and concat - - check if key is in map from [parse page_rules], - - if yes, then it is an illegal transition - - if no, then add the list to a list for further processing *) -let solve page_rules _ = - let invalid_transitions = get_invalid_transitions page_rules in - () - -let _ = - solve - [ - "47|53"; - "97|13"; - "97|61"; - "97|47"; - "75|29"; - "61|13"; - "75|53"; - "29|13"; - "97|29"; - "53|29"; - "61|53"; - "97|53"; - "61|29"; - "47|13"; - "75|47"; - "97|75"; - "47|61"; - "75|61"; - "47|29"; - "75|13"; - "53|13"; - ] - [] diff --git a/2024/OCaml/lib/dune b/2024/OCaml/lib/dune deleted file mode 100644 index 84e62ca..0000000 --- a/2024/OCaml/lib/dune +++ /dev/null @@ -1,5 +0,0 @@ -(library - (name AoC_2024) - (libraries re2)) - -(include_subdirs qualified) diff --git a/2024/OCaml/lib/file/read_input.ml b/2024/OCaml/lib/file/read_input.ml deleted file mode 100644 index 0f7470b..0000000 --- a/2024/OCaml/lib/file/read_input.ml +++ /dev/null @@ -1,11 +0,0 @@ -let rec lines_from_channel ic = - try - let line = input_line ic in - line :: lines_from_channel ic - with End_of_file -> [] - -let lines_from_file filename = - let channel = open_in filename in - let lines = lines_from_channel channel in - close_in channel; - lines diff --git a/2024/OCaml/lib/grid/cell.ml b/2024/OCaml/lib/grid/cell.ml deleted file mode 100644 index 186e451..0000000 --- a/2024/OCaml/lib/grid/cell.ml +++ /dev/null @@ -1,18 +0,0 @@ -type t = { - value : string; - up : t option; - down : t option; - left : t option; - right : t option; -} - -let create value = { value; up = None; down = None; left = None; right = None } -let with_up up c = { c with up = Some up } -let with_down down c = { c with down = Some down } -let with_left left c = { c with left = Some left } -let with_right right c = { c with right = Some right } -let move_up co = match co with Some c -> c.up | None -> None -let move_down co = match co with Some c -> c.down | None -> None -let move_left co = match co with Some c -> c.left | None -> None -let move_right co = match co with Some c -> c.right | None -> None -let ( = ) c1 c2 = c1.value = c2.value diff --git a/2024/OCaml/lib/grid/cell.mli b/2024/OCaml/lib/grid/cell.mli deleted file mode 100644 index e61e34a..0000000 --- a/2024/OCaml/lib/grid/cell.mli +++ /dev/null @@ -1,18 +0,0 @@ -type t = { - value : string; - up : t option; - down : t option; - left : t option; - right : t option; -} - -val create : string -> t -val with_up : t -> t -> t -val with_down : t -> t -> t -val with_left : t -> t -> t -val with_right : t -> t -> t -val move_up : t option -> t option -val move_down : t option -> t option -val move_left : t option -> t option -val move_right : t option -> t option -val ( = ) : t -> t -> bool diff --git a/2024/OCaml/lib/grid/grid.ml b/2024/OCaml/lib/grid/grid.ml deleted file mode 100644 index 0d6bc4c..0000000 --- a/2024/OCaml/lib/grid/grid.ml +++ /dev/null @@ -1,14 +0,0 @@ -let rec to_grid_aux tokens max_width acc = - match tokens with - | [] -> acc - | h :: t -> - let new_cell = Cell.create h |> Cell.with_left acc in - let new_acc = acc |> Cell.with_right new_cell in - to_grid_aux t max_width new_acc - -let to_grid tokens max_width : Cell.t option = - match tokens with - | [] -> None - | h :: t -> - let first_cell = Cell.create h in - Some (to_grid_aux t max_width first_cell) diff --git a/2024/OCaml/lib/grid/grid.mli b/2024/OCaml/lib/grid/grid.mli deleted file mode 100644 index a69fcfe..0000000 --- a/2024/OCaml/lib/grid/grid.mli +++ /dev/null @@ -1,6 +0,0 @@ -val to_grid : string list -> int -> Cell.t option -(** The cell in the top left corner of the grid is returned where each element - of [tokens] is converted into rows with [width]. An empty list of [tokens] - returns None. Requires: [width] is greater than 0 **) - -(* val print : Cell.t -> () *) diff --git a/2024/OCaml/test/day_01_part_01.ml b/2024/OCaml/test/day_01_part_01.ml deleted file mode 100644 index 824c95b..0000000 --- a/2024/OCaml/test/day_01_part_01.ml +++ /dev/null @@ -1,27 +0,0 @@ -open OUnit2 -open AoC_2024.Day_01.Part_01 - -let tests = - "test suite for day 01 part 01" - >::: [ - ("empty list is 0" >:: fun _ -> assert_equal 0 (solve [] [])); - ( "distance between 0 and 1 is 1" >:: fun _ -> - assert_equal 1 (solve [ 0 ] [ 1 ]) ); - ( "distance between 1 and 0 is 1" >:: fun _ -> - assert_equal 1 (solve [ 1 ] [ 0 ]) ); - ( "smallest are paired, then next smallest, ..." >:: fun _ -> - assert_equal 0 (solve [ 1; 2; 3 ] [ 3; 2; 1 ]) ); - (* See https://adventofcode.com/2024/day/1 *) - ( "solves example correctly" >:: fun _ -> - assert_equal 11 (solve [ 3; 4; 2; 1; 3; 3 ] [ 4; 3; 5; 3; 9; 3 ]) ); - ( "same number is 0 distance apart" >:: fun _ -> - assert_equal 0 (calculate_distance 0 0) ); - ( "same number is 0 distance apart" >:: fun _ -> - assert_equal 0 (calculate_distance 1 1) ); - ( "distance is equal to abs(x - y)" >:: fun _ -> - assert_equal 1 (calculate_distance 0 1) ); - ( "order of inputs doesn't matter" >:: fun _ -> - assert_equal 1 (calculate_distance 1 0) ); - ] - -let _ = run_test_tt_main tests diff --git a/2024/OCaml/test/day_01_part_02.ml b/2024/OCaml/test/day_01_part_02.ml deleted file mode 100644 index f7340df..0000000 --- a/2024/OCaml/test/day_01_part_02.ml +++ /dev/null @@ -1,17 +0,0 @@ -open OUnit2 -open AoC_2024.Day_01.Part_02 - -let test_solve = - "test suite for day 01 part 02" - >::: [ - ("empty list is 0" >:: fun _ -> assert_equal 0 (solve [] [])); - ( "ten x zero occurrances of ten" >:: fun _ -> - assert_equal 0 (solve [ 10 ] [ 1; 2; 3 ]) ); - ( "two x three occurrances of two" >:: fun _ -> - assert_equal 6 (solve [ 2 ] [ 2; 2; 2 ]) ); - (* See https://adventofcode.com/2024/day/1#part2 *) - ( "solves example correctly" >:: fun _ -> - assert_equal 31 (solve [ 3; 4; 2; 1; 3; 3 ] [ 4; 3; 5; 3; 9; 3 ]) ); - ] - -let _ = run_test_tt_main test_solve diff --git a/2024/OCaml/test/day_02_part_01.ml b/2024/OCaml/test/day_02_part_01.ml deleted file mode 100644 index 36af248..0000000 --- a/2024/OCaml/test/day_02_part_01.ml +++ /dev/null @@ -1,38 +0,0 @@ -open OUnit2 -open AoC_2024.Day_02.Part_01 - -let tests = - "test suite for day 2 part 1" - >::: [ - ( "empty list should have no valid reports" >:: fun _ -> - assert_equal 0 (solve []) ); - ( "valid report should count as one" >:: fun _ -> - assert_equal 1 (solve [ [ 7; 6; 4; 2; 1 ] ]) ); - ( "example correct" >:: fun _ -> - assert_equal 2 - (solve - [ - [ 7; 6; 4; 2; 1 ]; - [ 1; 2; 7; 8; 9 ]; - [ 9; 7; 6; 2; 1 ]; - [ 1; 3; 2; 4; 5 ]; - [ 8; 6; 4; 4; 1 ]; - [ 1; 3; 6; 7; 9 ]; - ]) ); - ( "all increasing" >:: fun _ -> - assert_equal true (check_report_safety [ 1; 2; 3 ]) ); - ( "all decreasing" >:: fun _ -> - assert_equal true (check_report_safety [ 3; 2; 1 ]) ); - ( "increasing and decreasing" >:: fun _ -> - assert_equal false (check_report_safety [ 1; 3; 2 ]) ); - ( "change of at least one" >:: fun _ -> - assert_equal true (check_report_safety [ 1; 2; 3 ]) ); - ( "no change" >:: fun _ -> - assert_equal false (check_report_safety [ 1; 1; 2 ]) ); - ( "change of at most three" >:: fun _ -> - assert_equal true (check_report_safety [ 1; 2; 4; 7 ]) ); - ( "change of at more than three" >:: fun _ -> - assert_equal false (check_report_safety [ 1; 5 ]) ); - ] - -let _ = run_test_tt_main tests diff --git a/2024/OCaml/test/day_03_part_01.ml b/2024/OCaml/test/day_03_part_01.ml deleted file mode 100644 index 0db039a..0000000 --- a/2024/OCaml/test/day_03_part_01.ml +++ /dev/null @@ -1,29 +0,0 @@ -open OUnit2 -open AoC_2024.Day_03.Part_01 - -let example_input = - "xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))" - -let test_find_mul _ = - assert_equal - [ "mul(2,4)"; "mul(5,5)"; "mul(11,8)"; "mul(8,5)" ] - (let result = find_mul example_input in - List.map (fun x -> Re2.Match.get_exn x ~sub:(`Index 0)) result) - -let test_extract_pairs _ = - assert_equal - [ (2, 4); (5, 5); (11, 8); (8, 5) ] - (let matches = find_mul example_input in - extract_pairs matches) - -let test_solve _ = assert_equal ~printer:string_of_int 161 (solve example_input) - -let tests = - "test suite for day 3 part 1" - >::: [ - "muls extracted from example correctly" >:: test_find_mul; - "pairs extracted from matches" >:: test_extract_pairs; - "example correctly solved" >:: test_solve; - ] - -let _ = run_test_tt_main tests diff --git a/2024/OCaml/test/day_04_part_01.ml b/2024/OCaml/test/day_04_part_01.ml deleted file mode 100644 index 9550e5b..0000000 --- a/2024/OCaml/test/day_04_part_01.ml +++ /dev/null @@ -1,151 +0,0 @@ -open OUnit2 -open AoC_2024.Day_04.Part_01 - -let example_input = - [ - "MMMSXXMASM"; - "MSAMXMSMSA"; - "AMXSXMAAMM"; - "MSAMASMSMX"; - "XMASAMXAMM"; - "XXAMMXXAMA"; - "SMSMSASXSS"; - "SAXAMASAAA"; - "MAMMMXMMMM"; - "MXMXAXMASX"; - ] - -let is_in_bounds_tests = - let test_is_in_bounds expected length index _ = - let actual = is_in_bounds length index in - assert_equal ~printer:string_of_bool expected actual - in - [ - "not in bounds if below 0" >:: test_is_in_bounds false 1 (-1); - "in bounds if gte 0 and lt length" >:: test_is_in_bounds true 1 0; - "not in bounds if eq length" >:: test_is_in_bounds false 1 1; - "not in bounds if gt length" >:: test_is_in_bounds false 1 2; - ] - -let calculate_num_of_matches_tests = - let test_calculate_num_of_matches expected line row_offset character _ = - let actual = calculate_num_of_matches line row_offset character in - assert_equal ~printer:string_of_int expected actual - in - [ - "is 0 when no matches present" - >:: test_calculate_num_of_matches 0 "SMMX" 1 { index = 3; character = 'X' }; - "is 1 when only one match present - top to bottom" - >:: - (* - |.|X|.| - |.|M|.| - |.|A|.| - |.|S|.| - *) - test_calculate_num_of_matches 1 ".X..M..A..S." 3 - { index = 1; character = 'X' }; - "is 1 when only one match present - top to bottom edge" - >:: - (* - |X|.|.| - |M|.|.| - |A|.|.| - |S|.|.| - *) - test_calculate_num_of_matches 1 "X..M..A..S.." 3 - { index = 0; character = 'X' }; - "is 1 when only one match present - bottom to top" - >:: - (* - |.|S|.| - |.|A|.| - |.|M|.| - |.|X|.| - *) - test_calculate_num_of_matches 1 ".S..A..M..X." 3 - { index = 10; character = 'X' }; - "is 1 when only one match present - left to right" - >:: - (* - |X|M|A|S| - *) - test_calculate_num_of_matches 1 "XMAS" 4 { index = 0; character = 'X' }; - "is 1 when only one match present - right to left" - >:: - (* - |S|A|M|X| - *) - test_calculate_num_of_matches 1 "SAMX" 4 { index = 3; character = 'X' }; - "is 1 when only one match present - bottom right to top left" - >:: - (* - |S|.|.|.| - |.|A|.|.| - |.|.|M|.| - |.|.|.|X| - *) - test_calculate_num_of_matches 1 "S....A....M....X" 4 - { index = 15; character = 'X' }; - "is 1 when only one match present - top left to bottom right" - >:: - (* - |X|.|.|.| - |.|M|.|.| - |.|.|A|.| - |.|.|.|S| - *) - test_calculate_num_of_matches 1 "X....M....A....S" 4 - { index = 0; character = 'X' }; - "is 1 when only one match present - bottom left to top right" - >:: - (* - |.|.|.|S| - |.|.|A|.| - |.|M|.|.| - |X|.|.|.| - *) - test_calculate_num_of_matches 1 "...S..A..M..X..." 4 - { index = 12; character = 'X' }; - "is 1 when only one match present - top right to bottom left" - >:: - (* - |.|.|.|X| - |.|.|M|.| - |.|A|.|.| - |S|.|.|.| - *) - test_calculate_num_of_matches 1 "...X..M..A..S..." 4 - { index = 3; character = 'X' }; - "is 8 when matches present in all possible directions" - >:: - (* - |S|.|.|S|.|.|S| - |.|A|.|A|.|A|.| - |.|.|M|M|M|.|.| - |S|A|M|X|M|A|S| - |.|.|M|M|M|.|.| - |.|A|.|A|.|A|.| - |S|.|.|S|.|.|S| - *) - test_calculate_num_of_matches 8 - "S..S..S.A.A.A...MMM..SAMXMAS..MMM...A.A.A.S..S..S" 7 - { index = 24; character = 'X' }; - ] - -let solve_tests = - let test_solve expected lines _ = - let actual = solve lines in - assert_equal ~printer:string_of_int expected actual - in - [ - "example is solved correctly" >:: test_solve 18 example_input; - "minimal example is solved correctly" - >:: test_solve 4 [ "..X..."; ".SAMX."; ".A..A."; "XMAS.S"; ".X...." ]; - ] - -let tests = - "test suite for day 4 part 1" - >::: is_in_bounds_tests @ calculate_num_of_matches_tests @ solve_tests - -let _ = run_test_tt_main tests diff --git a/2024/OCaml/test/day_05_part_01.ml b/2024/OCaml/test/day_05_part_01.ml deleted file mode 100644 index 0f50476..0000000 --- a/2024/OCaml/test/day_05_part_01.ml +++ /dev/null @@ -1,4 +0,0 @@ -open OUnit2 - -let tests = "test suite for day n part 1" >::: [] -let _ = run_test_tt_main tests diff --git a/2024/OCaml/test/dune b/2024/OCaml/test/dune deleted file mode 100644 index e19cbe9..0000000 --- a/2024/OCaml/test/dune +++ /dev/null @@ -1,10 +0,0 @@ -(tests - (names - day_01_part_01 - day_01_part_02 - day_02_part_01 - day_03_part_01 - day_04_part_01 - grid) - (modes byte exe) - (libraries AoC_2024 ounit2)) diff --git a/2024/OCaml/test/grid.ml b/2024/OCaml/test/grid.ml deleted file mode 100644 index bd384cd..0000000 --- a/2024/OCaml/test/grid.ml +++ /dev/null @@ -1,5 +0,0 @@ -open OUnit2 -open AoC_2024.Grid - -let tests = "" >::: [] -let _ = run_test_tt_main tests 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/2024/ocaml/.envrc b/2024/ocaml/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/2024/ocaml/.envrc @@ -0,0 +1 @@ +use flake diff --git a/2024/ocaml/.gitignore b/2024/ocaml/.gitignore new file mode 100644 index 0000000..a18e084 --- /dev/null +++ b/2024/ocaml/.gitignore @@ -0,0 +1,29 @@ +*.annot +*.cmo +*.cma +*.cmi +*.a +*.o +*.cmx +*.cmxs +*.cmxa + +# ocamlbuild 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/ diff --git a/2024/ocaml/.ocamlformat b/2024/ocaml/.ocamlformat new file mode 100644 index 0000000..912bea4 --- /dev/null +++ b/2024/ocaml/.ocamlformat @@ -0,0 +1,2 @@ +profile = default +version = 0.27.0 diff --git a/2024/ocaml/AoC_2024.opam b/2024/ocaml/AoC_2024.opam new file mode 100644 index 0000000..6be14e1 --- /dev/null +++ b/2024/ocaml/AoC_2024.opam @@ -0,0 +1,32 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: "Advent of Code 2024" +description: "Solutions to the Advent of Code puzzles for 2024" +maintainer: ["D.J. O'Leary"] +authors: ["D.J. O'Leary"] +license: "Unlicense" +tags: ["aoc2024" "Advent of Code 2024"] +homepage: "https://github.com/DJOLEARY/Advent_of_Code" +doc: + "https://github.com/DJOLEARY/Advent_of_Code/blob/main/2024/OCaml/README.md" +bug-reports: "https://github.com/DJOLEARY/Advent_of_Code/issues" +depends: [ + "ocaml" + "dune" {>= "3.16"} + "odoc" {with-doc} +] +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" diff --git a/2024/ocaml/README.md b/2024/ocaml/README.md new file mode 100644 index 0000000..caa88fe --- /dev/null +++ b/2024/ocaml/README.md @@ -0,0 +1,21 @@ +# OCaml 2024 + +## Getting Started + +### How to Build + +```sh +task build +``` + +### How to Run + +```sh +task run +``` + +### How to Test + +```sh +task test +``` diff --git a/2024/ocaml/Taskfile.yml b/2024/ocaml/Taskfile.yml new file mode 100644 index 0000000..1d8a89e --- /dev/null +++ b/2024/ocaml/Taskfile.yml @@ -0,0 +1,56 @@ +# https://taskfile.dev + +version: "3" +vars: + PROJECT_NAME: AoC_2024 +tasks: + default: + desc: Prints this message + cmds: + - cmd: task --list-all + build:doc: + desc: Builds documentation using ocamldoc + preconditions: + - which ocamldoc + cmds: + - cmd: ocamldoc -html -d docs bin/main.ml + silent: true + build: + desc: Builds the project + preconditions: + - which dune + cmds: + - cmd: dune build + test: + desc: Runs the tests in the 'tests' directory + ignore_error: true + env: + OUNIT_CI: true + preconditions: + - which dune + deps: + - task: build + cmds: + - cmd: dune test {{.CLI_ARGS}} + test:watch: + desc: Continuously runs the tests in the 'tests' directory + ignore_error: true + sources: + - lib/**/*.ml + - test/**/*.ml + env: + OUNIT_CI: true + preconditions: + - which dune + deps: + - task: build + cmds: + - cmd: dune test --watch {{.CLI_ARGS}} + run: + desc: Runs the project + preconditions: + - which dune + deps: + - task: build + cmds: + - cmd: dune exec {{.PROJECT_NAME}} diff --git a/2024/ocaml/bin/dune b/2024/ocaml/bin/dune new file mode 100644 index 0000000..ee36b46 --- /dev/null +++ b/2024/ocaml/bin/dune @@ -0,0 +1,5 @@ +(executable + (public_name AoC_2024) + (name main) + (modes byte exe) + (libraries AoC_2024)) diff --git a/2024/ocaml/bin/main.ml b/2024/ocaml/bin/main.ml new file mode 100644 index 0000000..a76e046 --- /dev/null +++ b/2024/ocaml/bin/main.ml @@ -0,0 +1,46 @@ +open AoC_2024 + +let day_to_path day = + let string_day = string_of_int day in + let formatted_day = if day < 10 then "0" ^ string_day else string_day in + "../_input/day_" ^ formatted_day ^ ".txt" + +let print_solution day_num part_num answer = + print_string "D"; + print_int day_num; + print_string ":P"; + print_int part_num; + print_string " -> "; + print_int answer; + print_newline () + +let day_1 = + let lines = File.Read_input.lines_from_file (day_to_path 1) in + let left, right = Day_01.Part_01.lines_to_lists lines in + let part_01 = Day_01.Part_01.solve left right in + print_solution 1 1 part_01; + let part_02 = Day_01.Part_02.solve left right in + print_solution 1 2 part_02 + +let day_2 = + let lines = File.Read_input.lines_from_file (day_to_path 2) in + let reports = Day_02.Part_01.lines_to_reports lines in + let part_01 = Day_02.Part_01.solve reports in + print_solution 2 1 part_01 + +let day_3 = + let lines = File.Read_input.lines_from_file (day_to_path 3) in + let line = List.fold_left (fun acc line -> acc ^ line) "" lines in + let part_01 = Day_03.Part_01.solve line in + print_solution 3 1 part_01 + +let day_4 = + let lines = File.Read_input.lines_from_file (day_to_path 4) in + let part_01 = Day_04.Part_01.solve lines in + print_solution 4 1 part_01 + +let _ = + day_1; + day_2; + day_3; + day_4 diff --git a/2024/ocaml/dune-project b/2024/ocaml/dune-project new file mode 100644 index 0000000..3f3a7bc --- /dev/null +++ b/2024/ocaml/dune-project @@ -0,0 +1,28 @@ +(lang dune 3.16) + +(name AoC_2024) + +(generate_opam_files true) + +(source + (github DJOLEARY/Advent_of_Code)) + +(authors "D.J. O'Leary") + +(maintainers "D.J. O'Leary") + +(license Unlicense) + +(documentation https://github.com/DJOLEARY/Advent_of_Code/blob/main/2024/OCaml/README.md) + +(map_workspace_root false) + +(package + (name AoC_2024) + (synopsis "Advent of Code 2024") + (description "Solutions to the Advent of Code puzzles for 2024") + (depends ocaml dune) + (tags + (aoc2024 "Advent of Code 2024"))) + +; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html diff --git a/2024/ocaml/flake.lock b/2024/ocaml/flake.lock new file mode 100644 index 0000000..b7695d9 --- /dev/null +++ b/2024/ocaml/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/2024/ocaml/flake.nix b/2024/ocaml/flake.nix new file mode 100644 index 0000000..74a5dc6 --- /dev/null +++ b/2024/ocaml/flake.nix @@ -0,0 +1,44 @@ +{ + description = "Advent of Code 2024: OCaml"; + + 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 = pkgs.ocaml-ng.ocamlPackages_5_2; + in + pkgs.mkShell { + # build tools + nativeBuildInputs = with ocamlPackages; [ + pkgs.opam + ocaml + findlib + dune_3 + ]; + # dependencies + buildInputs = with ocamlPackages; [ + bisect_ppx + findlib + menhir + ocaml-lsp + earlybird + ocamlformat + ocamlgraph + odoc + ounit2 + re2 + utop + + pkgs.gh # GitHub CLI + ]; + }; + }; +} diff --git a/2024/ocaml/lib/day_01/part_01.ml b/2024/ocaml/lib/day_01/part_01.ml new file mode 100644 index 0000000..100b12a --- /dev/null +++ b/2024/ocaml/lib/day_01/part_01.ml @@ -0,0 +1,40 @@ +(* Day 1: Part 1 *) + +let calculate_distance x y = Stdlib.abs (x - y) + +(** Require: left and right to be the same length *) +let solve left right = + let sorted_left = List.sort Stdlib.compare left in + let sorted_right = List.sort Stdlib.compare right in + let distances = List.map2 calculate_distance sorted_left sorted_right in + List.fold_left ( + ) 0 distances + +let left line = + let pos = 0 in + let len = String.index line ' ' in + String.sub line pos len + +let right line = + let pos = String.rindex line ' ' + 1 in + let len = String.length line - pos in + String.sub line pos len + +let line_to_tuple line = (left line, right line) + +let lines_to_lists lines = + let tuples = List.map line_to_tuple lines in + let left = + List.map + (fun t -> + let l, _ = t in + int_of_string l) + tuples + in + let right = + List.map + (fun t -> + let _, r = t in + int_of_string r) + tuples + in + (left, right) diff --git a/2024/ocaml/lib/day_01/part_02.ml b/2024/ocaml/lib/day_01/part_02.ml new file mode 100644 index 0000000..25c8b7d --- /dev/null +++ b/2024/ocaml/lib/day_01/part_02.ml @@ -0,0 +1,44 @@ +(* Day 1: Part 2 *) + +let rec count_occurrances_help element acc lst = + match lst with + | [] -> acc + | h :: t -> + if h = element then count_occurrances_help element (acc + 1) t + else count_occurrances_help element acc t + +let count_occurrances element lst = count_occurrances_help element 0 lst + +let solve left right = + List.map (fun x -> x * count_occurrances x right) left + |> List.fold_left ( + ) 0 + +let left line = + let pos = 0 in + let len = String.index line ' ' in + String.sub line pos len + +let right line = + let pos = String.rindex line ' ' + 1 in + let len = String.length line - pos in + String.sub line pos len + +let line_to_tuple line = (left line, right line) + +let lines_to_lists lines = + let tuples = List.map line_to_tuple lines in + let left = + List.map + (fun t -> + let l, _ = t in + int_of_string l) + tuples + in + let right = + List.map + (fun t -> + let _, r = t in + int_of_string r) + tuples + in + (left, right) diff --git a/2024/ocaml/lib/day_02/part_01.ml b/2024/ocaml/lib/day_02/part_01.ml new file mode 100644 index 0000000..32d7ab8 --- /dev/null +++ b/2024/ocaml/lib/day_02/part_01.ml @@ -0,0 +1,42 @@ +(* Day 2: Part 1 *) + +type level = int +type reports = level list list + +let rec compare_report_levels_helper comparison prev report = + match report with + | [] -> true + | h :: t -> comparison prev h && compare_report_levels_helper comparison h t + +let compare_report_levels comparison report = + match report with + | [] -> true + | h :: t -> compare_report_levels_helper comparison h t + +let all_increasing report = compare_report_levels ( < ) report +let all_decreasing report = compare_report_levels ( > ) report + +let at_least_one report = + compare_report_levels (fun prev curr -> prev <> curr) report + +let at_most_three report = + compare_report_levels (fun prev curr -> abs (prev - curr) <= 3) report + +let check_report_safety report = + if not (all_increasing report || all_decreasing report) then false + else if not (at_least_one report && at_most_three report) then false + else true + +let rec count_safe_reports acc lst = + match lst with + | [] -> acc + | h :: t -> + if h then count_safe_reports (acc + 1) t else count_safe_reports acc t + +let solve reports = + reports |> List.map check_report_safety |> count_safe_reports 0 + +let line_to_report line = + line |> String.split_on_char ' ' |> List.map int_of_string + +let lines_to_reports lines = List.map line_to_report lines diff --git a/2024/ocaml/lib/day_03/part_01.ml b/2024/ocaml/lib/day_03/part_01.ml new file mode 100644 index 0000000..c12b68a --- /dev/null +++ b/2024/ocaml/lib/day_03/part_01.ml @@ -0,0 +1,33 @@ +(* Day 3: Part 1 *) + +let find_mul text = + let regex_or_err = Re2.create "mul\\(([0-9]{1,3}),([0-9]{1,3})\\)" in + match regex_or_err with + | Ok regex -> ( + let matches = Re2.get_matches regex text in + match matches with Ok matches -> matches | Error _ -> []) + | Error _ -> [] + +let extract_pairs (matches : Re2.Match.t list) : (int * int) list = + List.map + (fun x -> + let left = Re2.Match.get x ~sub:(`Index 1) in + match left with + | None -> (0, 0) + | Some left -> ( + let left_num = int_of_string left in + let right = Re2.Match.get x ~sub:(`Index 2) in + match right with + | None -> (0, 0) + | Some right -> + let right_num = int_of_string right in + (left_num, right_num))) + matches + +let solve input = + input |> find_mul |> extract_pairs + |> List.fold_left + (fun acc pair -> + let left, right = pair in + acc + (left * right)) + 0 diff --git a/2024/ocaml/lib/day_04/part_01.ml b/2024/ocaml/lib/day_04/part_01.ml new file mode 100644 index 0000000..acb3e3b --- /dev/null +++ b/2024/ocaml/lib/day_04/part_01.ml @@ -0,0 +1,85 @@ +(* Day 4: Part 1 *) + +type character = { index : int; character : char } +type direction = N | NE | E | SE | S | SW | W | NW + +let all_directions = [ N; NE; E; SE; S; SW; W; NW ] +let needle = "XMAS" + +(** [calculate_row_offset lines] is the offset used to move between the rows of + the concatenated variant of [lines] as though it were a grid. Requires: + values of [lines] all have the same length *) +let calculate_row_offset lines = + match lines with [] -> 0 | h :: _ -> String.length h + +let join_lines lines = + let concat_trim acc line = acc ^ String.trim line in + List.fold_left concat_trim "" lines + +let explode s = List.init (String.length s) (String.get s) +let is_in_bounds length index = index >= 0 && index < length + +let get_at_index line index = + let length = String.length line in + if not (is_in_bounds length index) then "." + else + let character = String.get line index in + Char.escaped character + +let get_direction_index row_offset direction index = + match direction with + | N -> index - row_offset + | NE -> index - row_offset + 1 + | E -> index + 1 + | SE -> index + row_offset + 1 + | S -> index + row_offset + | SW -> index + row_offset - 1 + | W -> index - 1 + | NW -> index - row_offset - 1 + +let get_direction_string get_char get_offset_index index = + let first = get_char index in + let second_index = get_offset_index index in + let second = get_char second_index in + let third_index = get_offset_index second_index in + let third = get_char third_index in + let fourth_index = get_offset_index third_index in + let fourth = get_char fourth_index in + first ^ second ^ third ^ fourth + +let check_direction get_from_line get_offset_index index direction = + get_direction_string get_from_line (get_offset_index direction) index = needle + +let calculate_num_of_matches line row_offset character = + let get_from_line_at_index = get_at_index line in + let get_offset_direction_index = get_direction_index row_offset in + let check_direction_from_index = + check_direction get_from_line_at_index get_offset_direction_index + character.index + in + all_directions + |> List.map check_direction_from_index + |> List.map (fun x -> match x with true -> 1 | false -> 0) + |> List.fold_left ( + ) 0 + +(* Tried: + 2544 -> too high + 1272 (previous halved) -> too low *) +let solve lines = + let row_offset = calculate_row_offset lines in + let line = join_lines lines in + let chars = explode line in + let characters = List.mapi (fun i c -> { index = i; character = c }) chars in + let xs = + List.filter + (fun c -> match c.character with 'X' -> true | _ -> false) + characters + in + let calculate_num_of_surrounding_matches_in_line = + calculate_num_of_matches line row_offset + in + let num_of_matches = + List.map calculate_num_of_surrounding_matches_in_line xs + |> List.fold_left ( + ) 0 + in + num_of_matches diff --git a/2024/ocaml/lib/day_05/part_01.ml b/2024/ocaml/lib/day_05/part_01.ml new file mode 100644 index 0000000..f32bef1 --- /dev/null +++ b/2024/ocaml/lib/day_05/part_01.ml @@ -0,0 +1,62 @@ +(* Day 5: Part 1 *) + +module StringMap = Map.Make (String) + +let inverse_page_rule page_rule = + String.split_on_char '|' page_rule |> fun lst -> + match lst with [] -> "" | _ :: _ -> String.concat "|" (List.rev lst) + +(** [parse_aux acc page_rules] is a map of all invalid transitions as defined by + [page_rules] *) +let rec get_invalid_transitions_aux acc page_rules : unit StringMap.t = + match page_rules with + | [] -> acc + | h :: t -> + get_invalid_transitions_aux + (acc |> StringMap.add (inverse_page_rule h) ()) + t + +let get_invalid_transitions page_rules = + get_invalid_transitions_aux StringMap.empty page_rules + +(** [solve page_rules updates] is the solution for this puzzle, given the + [page_rules] and a list of [updates]. + + BELOW DOESN'T WORK: no guarantee that second page comes immediately after + the first + - pass in updates to solve + - make sliding window of 2 elements + - invert element order and concat + - check if key is in map from [parse page_rules], + - if yes, then it is an illegal transition + - if no, then add the list to a list for further processing *) +let solve page_rules _ = + let invalid_transitions = get_invalid_transitions page_rules in + () + +let _ = + solve + [ + "47|53"; + "97|13"; + "97|61"; + "97|47"; + "75|29"; + "61|13"; + "75|53"; + "29|13"; + "97|29"; + "53|29"; + "61|53"; + "97|53"; + "61|29"; + "47|13"; + "75|47"; + "97|75"; + "47|61"; + "75|61"; + "47|29"; + "75|13"; + "53|13"; + ] + [] diff --git a/2024/ocaml/lib/dune b/2024/ocaml/lib/dune new file mode 100644 index 0000000..84e62ca --- /dev/null +++ b/2024/ocaml/lib/dune @@ -0,0 +1,5 @@ +(library + (name AoC_2024) + (libraries re2)) + +(include_subdirs qualified) diff --git a/2024/ocaml/lib/file/read_input.ml b/2024/ocaml/lib/file/read_input.ml new file mode 100644 index 0000000..0f7470b --- /dev/null +++ b/2024/ocaml/lib/file/read_input.ml @@ -0,0 +1,11 @@ +let rec lines_from_channel ic = + try + let line = input_line ic in + line :: lines_from_channel ic + with End_of_file -> [] + +let lines_from_file filename = + let channel = open_in filename in + let lines = lines_from_channel channel in + close_in channel; + lines diff --git a/2024/ocaml/lib/grid/cell.ml b/2024/ocaml/lib/grid/cell.ml new file mode 100644 index 0000000..186e451 --- /dev/null +++ b/2024/ocaml/lib/grid/cell.ml @@ -0,0 +1,18 @@ +type t = { + value : string; + up : t option; + down : t option; + left : t option; + right : t option; +} + +let create value = { value; up = None; down = None; left = None; right = None } +let with_up up c = { c with up = Some up } +let with_down down c = { c with down = Some down } +let with_left left c = { c with left = Some left } +let with_right right c = { c with right = Some right } +let move_up co = match co with Some c -> c.up | None -> None +let move_down co = match co with Some c -> c.down | None -> None +let move_left co = match co with Some c -> c.left | None -> None +let move_right co = match co with Some c -> c.right | None -> None +let ( = ) c1 c2 = c1.value = c2.value diff --git a/2024/ocaml/lib/grid/cell.mli b/2024/ocaml/lib/grid/cell.mli new file mode 100644 index 0000000..e61e34a --- /dev/null +++ b/2024/ocaml/lib/grid/cell.mli @@ -0,0 +1,18 @@ +type t = { + value : string; + up : t option; + down : t option; + left : t option; + right : t option; +} + +val create : string -> t +val with_up : t -> t -> t +val with_down : t -> t -> t +val with_left : t -> t -> t +val with_right : t -> t -> t +val move_up : t option -> t option +val move_down : t option -> t option +val move_left : t option -> t option +val move_right : t option -> t option +val ( = ) : t -> t -> bool diff --git a/2024/ocaml/lib/grid/grid.ml b/2024/ocaml/lib/grid/grid.ml new file mode 100644 index 0000000..0d6bc4c --- /dev/null +++ b/2024/ocaml/lib/grid/grid.ml @@ -0,0 +1,14 @@ +let rec to_grid_aux tokens max_width acc = + match tokens with + | [] -> acc + | h :: t -> + let new_cell = Cell.create h |> Cell.with_left acc in + let new_acc = acc |> Cell.with_right new_cell in + to_grid_aux t max_width new_acc + +let to_grid tokens max_width : Cell.t option = + match tokens with + | [] -> None + | h :: t -> + let first_cell = Cell.create h in + Some (to_grid_aux t max_width first_cell) diff --git a/2024/ocaml/lib/grid/grid.mli b/2024/ocaml/lib/grid/grid.mli new file mode 100644 index 0000000..a69fcfe --- /dev/null +++ b/2024/ocaml/lib/grid/grid.mli @@ -0,0 +1,6 @@ +val to_grid : string list -> int -> Cell.t option +(** The cell in the top left corner of the grid is returned where each element + of [tokens] is converted into rows with [width]. An empty list of [tokens] + returns None. Requires: [width] is greater than 0 **) + +(* val print : Cell.t -> () *) diff --git a/2024/ocaml/test/day_01_part_01.ml b/2024/ocaml/test/day_01_part_01.ml new file mode 100644 index 0000000..824c95b --- /dev/null +++ b/2024/ocaml/test/day_01_part_01.ml @@ -0,0 +1,27 @@ +open OUnit2 +open AoC_2024.Day_01.Part_01 + +let tests = + "test suite for day 01 part 01" + >::: [ + ("empty list is 0" >:: fun _ -> assert_equal 0 (solve [] [])); + ( "distance between 0 and 1 is 1" >:: fun _ -> + assert_equal 1 (solve [ 0 ] [ 1 ]) ); + ( "distance between 1 and 0 is 1" >:: fun _ -> + assert_equal 1 (solve [ 1 ] [ 0 ]) ); + ( "smallest are paired, then next smallest, ..." >:: fun _ -> + assert_equal 0 (solve [ 1; 2; 3 ] [ 3; 2; 1 ]) ); + (* See https://adventofcode.com/2024/day/1 *) + ( "solves example correctly" >:: fun _ -> + assert_equal 11 (solve [ 3; 4; 2; 1; 3; 3 ] [ 4; 3; 5; 3; 9; 3 ]) ); + ( "same number is 0 distance apart" >:: fun _ -> + assert_equal 0 (calculate_distance 0 0) ); + ( "same number is 0 distance apart" >:: fun _ -> + assert_equal 0 (calculate_distance 1 1) ); + ( "distance is equal to abs(x - y)" >:: fun _ -> + assert_equal 1 (calculate_distance 0 1) ); + ( "order of inputs doesn't matter" >:: fun _ -> + assert_equal 1 (calculate_distance 1 0) ); + ] + +let _ = run_test_tt_main tests diff --git a/2024/ocaml/test/day_01_part_02.ml b/2024/ocaml/test/day_01_part_02.ml new file mode 100644 index 0000000..f7340df --- /dev/null +++ b/2024/ocaml/test/day_01_part_02.ml @@ -0,0 +1,17 @@ +open OUnit2 +open AoC_2024.Day_01.Part_02 + +let test_solve = + "test suite for day 01 part 02" + >::: [ + ("empty list is 0" >:: fun _ -> assert_equal 0 (solve [] [])); + ( "ten x zero occurrances of ten" >:: fun _ -> + assert_equal 0 (solve [ 10 ] [ 1; 2; 3 ]) ); + ( "two x three occurrances of two" >:: fun _ -> + assert_equal 6 (solve [ 2 ] [ 2; 2; 2 ]) ); + (* See https://adventofcode.com/2024/day/1#part2 *) + ( "solves example correctly" >:: fun _ -> + assert_equal 31 (solve [ 3; 4; 2; 1; 3; 3 ] [ 4; 3; 5; 3; 9; 3 ]) ); + ] + +let _ = run_test_tt_main test_solve diff --git a/2024/ocaml/test/day_02_part_01.ml b/2024/ocaml/test/day_02_part_01.ml new file mode 100644 index 0000000..36af248 --- /dev/null +++ b/2024/ocaml/test/day_02_part_01.ml @@ -0,0 +1,38 @@ +open OUnit2 +open AoC_2024.Day_02.Part_01 + +let tests = + "test suite for day 2 part 1" + >::: [ + ( "empty list should have no valid reports" >:: fun _ -> + assert_equal 0 (solve []) ); + ( "valid report should count as one" >:: fun _ -> + assert_equal 1 (solve [ [ 7; 6; 4; 2; 1 ] ]) ); + ( "example correct" >:: fun _ -> + assert_equal 2 + (solve + [ + [ 7; 6; 4; 2; 1 ]; + [ 1; 2; 7; 8; 9 ]; + [ 9; 7; 6; 2; 1 ]; + [ 1; 3; 2; 4; 5 ]; + [ 8; 6; 4; 4; 1 ]; + [ 1; 3; 6; 7; 9 ]; + ]) ); + ( "all increasing" >:: fun _ -> + assert_equal true (check_report_safety [ 1; 2; 3 ]) ); + ( "all decreasing" >:: fun _ -> + assert_equal true (check_report_safety [ 3; 2; 1 ]) ); + ( "increasing and decreasing" >:: fun _ -> + assert_equal false (check_report_safety [ 1; 3; 2 ]) ); + ( "change of at least one" >:: fun _ -> + assert_equal true (check_report_safety [ 1; 2; 3 ]) ); + ( "no change" >:: fun _ -> + assert_equal false (check_report_safety [ 1; 1; 2 ]) ); + ( "change of at most three" >:: fun _ -> + assert_equal true (check_report_safety [ 1; 2; 4; 7 ]) ); + ( "change of at more than three" >:: fun _ -> + assert_equal false (check_report_safety [ 1; 5 ]) ); + ] + +let _ = run_test_tt_main tests diff --git a/2024/ocaml/test/day_03_part_01.ml b/2024/ocaml/test/day_03_part_01.ml new file mode 100644 index 0000000..0db039a --- /dev/null +++ b/2024/ocaml/test/day_03_part_01.ml @@ -0,0 +1,29 @@ +open OUnit2 +open AoC_2024.Day_03.Part_01 + +let example_input = + "xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))" + +let test_find_mul _ = + assert_equal + [ "mul(2,4)"; "mul(5,5)"; "mul(11,8)"; "mul(8,5)" ] + (let result = find_mul example_input in + List.map (fun x -> Re2.Match.get_exn x ~sub:(`Index 0)) result) + +let test_extract_pairs _ = + assert_equal + [ (2, 4); (5, 5); (11, 8); (8, 5) ] + (let matches = find_mul example_input in + extract_pairs matches) + +let test_solve _ = assert_equal ~printer:string_of_int 161 (solve example_input) + +let tests = + "test suite for day 3 part 1" + >::: [ + "muls extracted from example correctly" >:: test_find_mul; + "pairs extracted from matches" >:: test_extract_pairs; + "example correctly solved" >:: test_solve; + ] + +let _ = run_test_tt_main tests diff --git a/2024/ocaml/test/day_04_part_01.ml b/2024/ocaml/test/day_04_part_01.ml new file mode 100644 index 0000000..9550e5b --- /dev/null +++ b/2024/ocaml/test/day_04_part_01.ml @@ -0,0 +1,151 @@ +open OUnit2 +open AoC_2024.Day_04.Part_01 + +let example_input = + [ + "MMMSXXMASM"; + "MSAMXMSMSA"; + "AMXSXMAAMM"; + "MSAMASMSMX"; + "XMASAMXAMM"; + "XXAMMXXAMA"; + "SMSMSASXSS"; + "SAXAMASAAA"; + "MAMMMXMMMM"; + "MXMXAXMASX"; + ] + +let is_in_bounds_tests = + let test_is_in_bounds expected length index _ = + let actual = is_in_bounds length index in + assert_equal ~printer:string_of_bool expected actual + in + [ + "not in bounds if below 0" >:: test_is_in_bounds false 1 (-1); + "in bounds if gte 0 and lt length" >:: test_is_in_bounds true 1 0; + "not in bounds if eq length" >:: test_is_in_bounds false 1 1; + "not in bounds if gt length" >:: test_is_in_bounds false 1 2; + ] + +let calculate_num_of_matches_tests = + let test_calculate_num_of_matches expected line row_offset character _ = + let actual = calculate_num_of_matches line row_offset character in + assert_equal ~printer:string_of_int expected actual + in + [ + "is 0 when no matches present" + >:: test_calculate_num_of_matches 0 "SMMX" 1 { index = 3; character = 'X' }; + "is 1 when only one match present - top to bottom" + >:: + (* + |.|X|.| + |.|M|.| + |.|A|.| + |.|S|.| + *) + test_calculate_num_of_matches 1 ".X..M..A..S." 3 + { index = 1; character = 'X' }; + "is 1 when only one match present - top to bottom edge" + >:: + (* + |X|.|.| + |M|.|.| + |A|.|.| + |S|.|.| + *) + test_calculate_num_of_matches 1 "X..M..A..S.." 3 + { index = 0; character = 'X' }; + "is 1 when only one match present - bottom to top" + >:: + (* + |.|S|.| + |.|A|.| + |.|M|.| + |.|X|.| + *) + test_calculate_num_of_matches 1 ".S..A..M..X." 3 + { index = 10; character = 'X' }; + "is 1 when only one match present - left to right" + >:: + (* + |X|M|A|S| + *) + test_calculate_num_of_matches 1 "XMAS" 4 { index = 0; character = 'X' }; + "is 1 when only one match present - right to left" + >:: + (* + |S|A|M|X| + *) + test_calculate_num_of_matches 1 "SAMX" 4 { index = 3; character = 'X' }; + "is 1 when only one match present - bottom right to top left" + >:: + (* + |S|.|.|.| + |.|A|.|.| + |.|.|M|.| + |.|.|.|X| + *) + test_calculate_num_of_matches 1 "S....A....M....X" 4 + { index = 15; character = 'X' }; + "is 1 when only one match present - top left to bottom right" + >:: + (* + |X|.|.|.| + |.|M|.|.| + |.|.|A|.| + |.|.|.|S| + *) + test_calculate_num_of_matches 1 "X....M....A....S" 4 + { index = 0; character = 'X' }; + "is 1 when only one match present - bottom left to top right" + >:: + (* + |.|.|.|S| + |.|.|A|.| + |.|M|.|.| + |X|.|.|.| + *) + test_calculate_num_of_matches 1 "...S..A..M..X..." 4 + { index = 12; character = 'X' }; + "is 1 when only one match present - top right to bottom left" + >:: + (* + |.|.|.|X| + |.|.|M|.| + |.|A|.|.| + |S|.|.|.| + *) + test_calculate_num_of_matches 1 "...X..M..A..S..." 4 + { index = 3; character = 'X' }; + "is 8 when matches present in all possible directions" + >:: + (* + |S|.|.|S|.|.|S| + |.|A|.|A|.|A|.| + |.|.|M|M|M|.|.| + |S|A|M|X|M|A|S| + |.|.|M|M|M|.|.| + |.|A|.|A|.|A|.| + |S|.|.|S|.|.|S| + *) + test_calculate_num_of_matches 8 + "S..S..S.A.A.A...MMM..SAMXMAS..MMM...A.A.A.S..S..S" 7 + { index = 24; character = 'X' }; + ] + +let solve_tests = + let test_solve expected lines _ = + let actual = solve lines in + assert_equal ~printer:string_of_int expected actual + in + [ + "example is solved correctly" >:: test_solve 18 example_input; + "minimal example is solved correctly" + >:: test_solve 4 [ "..X..."; ".SAMX."; ".A..A."; "XMAS.S"; ".X...." ]; + ] + +let tests = + "test suite for day 4 part 1" + >::: is_in_bounds_tests @ calculate_num_of_matches_tests @ solve_tests + +let _ = run_test_tt_main tests diff --git a/2024/ocaml/test/day_05_part_01.ml b/2024/ocaml/test/day_05_part_01.ml new file mode 100644 index 0000000..0f50476 --- /dev/null +++ b/2024/ocaml/test/day_05_part_01.ml @@ -0,0 +1,4 @@ +open OUnit2 + +let tests = "test suite for day n part 1" >::: [] +let _ = run_test_tt_main tests diff --git a/2024/ocaml/test/dune b/2024/ocaml/test/dune new file mode 100644 index 0000000..e19cbe9 --- /dev/null +++ b/2024/ocaml/test/dune @@ -0,0 +1,10 @@ +(tests + (names + day_01_part_01 + day_01_part_02 + day_02_part_01 + day_03_part_01 + day_04_part_01 + grid) + (modes byte exe) + (libraries AoC_2024 ounit2)) diff --git a/2024/ocaml/test/grid.ml b/2024/ocaml/test/grid.ml new file mode 100644 index 0000000..bd384cd --- /dev/null +++ b/2024/ocaml/test/grid.ml @@ -0,0 +1,5 @@ +open OUnit2 +open AoC_2024.Grid + +let tests = "" >::: [] +let _ = run_test_tt_main tests diff --git a/2024/php/.envrc b/2024/php/.envrc new file mode 100644 index 0000000..cffc922 --- /dev/null +++ b/2024/php/.envrc @@ -0,0 +1 @@ +use flake . --impure diff --git a/2024/php/README.md b/2024/php/README.md new file mode 100644 index 0000000..6aeb9d6 --- /dev/null +++ b/2024/php/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/2024/php/day_04/Grid.php b/2024/php/day_04/Grid.php new file mode 100644 index 0000000..75d531b --- /dev/null +++ b/2024/php/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/2024/php/day_04/Input.php b/2024/php/day_04/Input.php new file mode 100644 index 0000000..60a16a0 --- /dev/null +++ b/2024/php/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/2024/php/day_04/Position.php b/2024/php/day_04/Position.php new file mode 100644 index 0000000..0dcf96c --- /dev/null +++ b/2024/php/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/2024/php/day_04/part_01.php b/2024/php/day_04/part_01.php new file mode 100644 index 0000000..0faa5b0 --- /dev/null +++ b/2024/php/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/2024/php/flake.lock b/2024/php/flake.lock new file mode 100644 index 0000000..b7695d9 --- /dev/null +++ b/2024/php/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/2024/php/flake.nix b/2024/php/flake.nix new file mode 100644 index 0000000..2be5644 --- /dev/null +++ b/2024/php/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