summaryrefslogtreecommitdiff
path: root/ocaml
diff options
context:
space:
mode:
authorDJ O'Leary <dijitol@proton.me>2026-06-26 18:04:09 +0200
committerDJ O'Leary <dijitol@proton.me>2026-06-26 18:04:09 +0200
commit6d8fb621d1e93b83786890d6d9cb6f0d8defa8ae (patch)
tree17c90d3984c114aa023600cc5bec6f6004b02243 /ocaml
parentad4ea5ca4bc9f7540ee1d6a4fa2d881709dc6459 (diff)
refactor!: invert repo directory structure
now is language first
Diffstat (limited to 'ocaml')
-rw-r--r--ocaml/.editorconfig27
-rw-r--r--ocaml/.env.template1
-rw-r--r--ocaml/.envrc3
-rw-r--r--ocaml/.gitattributes7
-rw-r--r--ocaml/.gitignore45
-rw-r--r--ocaml/.ocamlformat3
-rw-r--r--ocaml/CODEOWNERS1
-rw-r--r--ocaml/CONTRIBUTING.md5
-rw-r--r--ocaml/LICENSE25
-rw-r--r--ocaml/README.md54
-rw-r--r--ocaml/Taskfile.yaml103
-rw-r--r--ocaml/advent_of_code.opam42
-rw-r--r--ocaml/bin/dune11
-rw-r--r--ocaml/bin/main.ml16
-rw-r--r--ocaml/docs/Main.html18
-rw-r--r--ocaml/docs/index.html22
-rw-r--r--ocaml/docs/index_attributes.html17
-rw-r--r--ocaml/docs/index_class_types.html17
-rw-r--r--ocaml/docs/index_classes.html17
-rw-r--r--ocaml/docs/index_exceptions.html17
-rw-r--r--ocaml/docs/index_extensions.html17
-rw-r--r--ocaml/docs/index_methods.html17
-rw-r--r--ocaml/docs/index_module_types.html17
-rw-r--r--ocaml/docs/index_modules.html20
-rw-r--r--ocaml/docs/index_types.html17
-rw-r--r--ocaml/docs/index_values.html17
-rw-r--r--ocaml/docs/style.css43
-rw-r--r--ocaml/docs/type_Main.html10
-rw-r--r--ocaml/dune-project40
-rw-r--r--ocaml/flake.lock27
-rw-r--r--ocaml/flake.nix52
-rw-r--r--ocaml/lib/application/d01p01.ml53
-rw-r--r--ocaml/lib/application/d01p01.mli4
-rw-r--r--ocaml/lib/application/d01p02.ml55
-rw-r--r--ocaml/lib/application/d01p02.mli4
-rw-r--r--ocaml/lib/application/d02p01.ml54
-rw-r--r--ocaml/lib/application/d02p01.mli4
-rw-r--r--ocaml/lib/application/d02p02.ml86
-rw-r--r--ocaml/lib/application/d02p02.mli4
-rw-r--r--ocaml/lib/application/d03p01.ml23
-rw-r--r--ocaml/lib/application/d03p01.mli4
-rw-r--r--ocaml/lib/application/d03p02.ml23
-rw-r--r--ocaml/lib/application/d03p02.mli4
-rw-r--r--ocaml/lib/application/d04p01.ml48
-rw-r--r--ocaml/lib/application/d04p01.mli4
-rw-r--r--ocaml/lib/application/d04p02.ml64
-rw-r--r--ocaml/lib/application/d04p02.mli4
-rw-r--r--ocaml/lib/application/d05p01.ml3
-rw-r--r--ocaml/lib/application/d05p01.mli4
-rw-r--r--ocaml/lib/application/d06p01.ml50
-rw-r--r--ocaml/lib/application/d06p01.mli4
-rw-r--r--ocaml/lib/application/d06p02.ml50
-rw-r--r--ocaml/lib/application/d06p02.mli4
-rw-r--r--ocaml/lib/application/dune14
-rw-r--r--ocaml/lib/application/parser.ml36
-rw-r--r--ocaml/lib/domain/d01p01.ml76
-rw-r--r--ocaml/lib/domain/d01p02.ml95
-rw-r--r--ocaml/lib/domain/d02p01.ml33
-rw-r--r--ocaml/lib/domain/d02p01.mli36
-rw-r--r--ocaml/lib/domain/d02p02.ml29
-rw-r--r--ocaml/lib/domain/d02p02.mli30
-rw-r--r--ocaml/lib/domain/d03p01.ml114
-rw-r--r--ocaml/lib/domain/d03p02.ml116
-rw-r--r--ocaml/lib/domain/dune13
-rw-r--r--ocaml/lib/domain/matrix.ml196
-rw-r--r--ocaml/lib/domain/range.ml67
-rw-r--r--ocaml/lib/infrastructure/dune15
-rw-r--r--ocaml/lib/infrastructure/file.ml4
-rw-r--r--ocaml/lib/infrastructure/file.mli3
-rw-r--r--ocaml/lib/presentation/cli.ml62
-rw-r--r--ocaml/lib/presentation/dune16
-rw-r--r--ocaml/test/application/dune9
-rw-r--r--ocaml/test/application/test_d01p01.ml23
-rw-r--r--ocaml/test/application/test_d01p02.ml23
-rw-r--r--ocaml/test/application/test_d04p01.ml24
-rw-r--r--ocaml/test/domain/dune11
-rw-r--r--ocaml/test/domain/test_d01p01.ml29
-rw-r--r--ocaml/test/domain/test_d01p02.ml41
-rw-r--r--ocaml/test/domain/test_d03p01.ml857
-rw-r--r--ocaml/test/infrastructure/dune8
-rw-r--r--ocaml/test/infrastructure/test_example.ml0
-rw-r--r--ocaml/test/presentation/dune11
-rw-r--r--ocaml/test/presentation/test_example.ml0
83 files changed, 3272 insertions, 0 deletions
diff --git a/ocaml/.editorconfig b/ocaml/.editorconfig
new file mode 100644
index 0000000..64b41ca
--- /dev/null
+++ b/ocaml/.editorconfig
@@ -0,0 +1,27 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+
+[*.{ml,mli}]
+indent_style = space
+indent_size = 2
+tab_width = 2
+
+[*.{opam,dune,install,sexp,conf,in}]
+indent_style = space
+indent_size = 4
+
+[*.{md}]
+indent_style = tab
+trim_trailing_whitespace = true
+
+[*.{nix}]
+indent_style = space
+indent_size = 2
+insert_final_newline = false
+trim_trailing_whitespace = true
diff --git a/ocaml/.env.template b/ocaml/.env.template
new file mode 100644
index 0000000..3adee3e
--- /dev/null
+++ b/ocaml/.env.template
@@ -0,0 +1 @@
+LOG_LEVEL=debug
diff --git a/ocaml/.envrc b/ocaml/.envrc
new file mode 100644
index 0000000..cf8df4b
--- /dev/null
+++ b/ocaml/.envrc
@@ -0,0 +1,3 @@
+dotenv
+
+use flake
diff --git a/ocaml/.gitattributes b/ocaml/.gitattributes
new file mode 100644
index 0000000..1bc2e72
--- /dev/null
+++ b/ocaml/.gitattributes
@@ -0,0 +1,7 @@
+# http://git-scm.com/docs/gitattributes#_end_of_line_conversion
+* text=auto
+.* text eol=lf
+
+flake.lock linguist-generated
+ocaml_template.install linguist-generated
+ocaml_template.opam linguist-generated
diff --git a/ocaml/.gitignore b/ocaml/.gitignore
new file mode 100644
index 0000000..5456007
--- /dev/null
+++ b/ocaml/.gitignore
@@ -0,0 +1,45 @@
+*.annot
+*.cmo
+*.cma
+*.cmi
+*.a
+*.o
+*.cmx
+*.cmxs
+*.cmxa
+
+# Files containing detailed information about the compilation (generated
+# by `ocamlc`/`ocamlopt` when invoked using the option `-bin-annot`).
+# These files are typically useful for code inspection tools
+# (e.g. Merlin).
+*.cmt
+*.cmti
+
+# ocamlbuild and Dune default working directory
+_build/
+
+# ocamlbuild targets
+*.byte
+*.native
+
+# oasis generated files
+setup.data
+setup.log
+
+# Merlin configuring file for Vim and Emacs
+.merlin
+
+# Dune generated files
+*.install
+
+# Local OPAM switch
+_opam/
+
+# DirEnv cache
+.direnv/
+
+# Environment variables
+*.env
+
+# Taskfile cache
+.task/
diff --git a/ocaml/.ocamlformat b/ocaml/.ocamlformat
new file mode 100644
index 0000000..6fdcd22
--- /dev/null
+++ b/ocaml/.ocamlformat
@@ -0,0 +1,3 @@
+profile = ocamlformat
+version = 0.28.1
+
diff --git a/ocaml/CODEOWNERS b/ocaml/CODEOWNERS
new file mode 100644
index 0000000..66c50a2
--- /dev/null
+++ b/ocaml/CODEOWNERS
@@ -0,0 +1 @@
+* @djoleary
diff --git a/ocaml/CONTRIBUTING.md b/ocaml/CONTRIBUTING.md
new file mode 100644
index 0000000..832b94b
--- /dev/null
+++ b/ocaml/CONTRIBUTING.md
@@ -0,0 +1,5 @@
+# Contributing
+
+## Commit Messages
+
+We use conventional commits for all commit messages. See <https://www.conventionalcommits.org/en/v1.0.0/>
diff --git a/ocaml/LICENSE b/ocaml/LICENSE
new file mode 100644
index 0000000..1e9858b
--- /dev/null
+++ b/ocaml/LICENSE
@@ -0,0 +1,25 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <https://unlicense.org>
+
diff --git a/ocaml/README.md b/ocaml/README.md
new file mode 100644
index 0000000..772c76b
--- /dev/null
+++ b/ocaml/README.md
@@ -0,0 +1,54 @@
+# OCaml Template
+
+This is a template for OCaml projects.
+
+## Quick Start
+
+1. Fork this repository
+2. Rename the repository to your project name
+3. Replace all instances of `advent_of_code` with your project name, both in files and filenames themselves.
+
+## Project Structure
+
+```txt
+.
+├── _build # build outputs
+├── bin # program entry points
+├── docs # documentation
+├── lib # public modules
+├── test # tests
+├── .editorconfig
+├── .envrc
+├── .gitignore
+├── .ocamlformat # ocamlformat configuration
+├── CODEOWNERS
+├── CONTRIBUTING.md
+├── LICENSE
+├── README.md
+├── Taskfile.yml # task runner configuration similar to make
+├── dune-project # dune project configuration
+├── flake.lock
+├── flake.nix # nix flake configuration
+├── advent_of_code.install # autogenerated install script
+└── advent_of_code.opam # autogenerated project description
+```
+
+## Tools
+
+- [task](https://taskfile.dev/)
+- [dune](https://dune.build/)
+- [opam](https://opam.ocaml.org/)
+- [ocamlformat](https://ocaml.org/p/ocamlformat/latest/doc/index.html)
+- [odoc](https://ocaml.github.io/odoc/odoc/index.html)
+- [alchotest](https://github.com/mirage/alcotest)
+- [bisect_ppx](https://github.com/aantron/bisect_ppx)
+- [ocaml-lsp](https://github.com/ocaml/ocaml-lsp)
+- [earlybird](https://github.com/ocaml-lsp/earlybird)
+- [findlib](https://github.com/ocaml/ocamlfind)
+- [re2](https://github.com/janestreet/re2)
+- [fmt](https://erratique.ch/software/fmt)
+- [cmdliner](https://erratique.ch/software/cmdliner)
+
+## License
+
+This project is licensed under the [Unlicense](https://unlicense.org/).
diff --git a/ocaml/Taskfile.yaml b/ocaml/Taskfile.yaml
new file mode 100644
index 0000000..4a4cbe2
--- /dev/null
+++ b/ocaml/Taskfile.yaml
@@ -0,0 +1,103 @@
+# https://taskfile.dev
+version: "3"
+
+tasks:
+
+ default:
+ desc: Prints this message
+ cmds:
+ - cmd: task --list-all
+
+ docs:
+ desc: Builds documentation
+ preconditions:
+ - command -v ocamldoc
+ cmds:
+ - cmd: ocamldoc -html -d docs bin/main.ml
+ silent: true
+
+ test:
+ desc: Runs the tests
+ preconditions:
+ - command -v dune
+ deps:
+ - task: build
+ cmds:
+ - cmd: dune runtest {{.CLI_ARGS}}
+
+ test:watch:
+ desc: Continuously runs the tests
+ ignore_error: true
+ sources:
+ - lib/**/*.ml
+ - test/**/*.ml
+ preconditions:
+ - command -v dune
+ deps:
+ - task: build
+ cmds:
+ - cmd: dune runtest --watch {{.CLI_ARGS}}
+
+ test:coverage:
+ desc: Runs the tests and reports coverage
+ preconditions:
+ - command -v dune
+ - command -v bisect-ppx-report
+ deps:
+ - task: build
+ cmds:
+ - cmd: rm --recursive --force _coverage
+ - cmd: rm --force _build/default/test/bisect*.coverage
+ - cmd: dune runtest --instrument-with bisect_ppx --force {{.CLI_ARGS}}
+ - cmd: bisect-ppx-report html
+
+ test:suite:
+ desc: Run a singluar test suite, e.g. `task test:suite SUITE=test/domain/d01p01`
+ preconditions:
+ - command -v dune
+ deps:
+ - task: build
+ requires:
+ vars:
+ - SUITE
+ cmds:
+ - cmd: dune exec {{.SUITE}}.exe -- {{.CLI_ARGS}}
+
+ build:
+ desc: Builds the project
+ preconditions:
+ - command -v dune
+ cmds:
+ - cmd: dune build
+
+ build:watch:
+ desc: Builds the project when files change
+ preconditions:
+ - command -v dune
+ cmds:
+ - cmd: dune build --watch {{.CLI_ARGS}}
+
+ build:release:
+ desc: Builds a release version the project
+ preconditions:
+ - command -v dune
+ cmds:
+ - cmd: dune build --release
+
+ run:
+ desc: Runs the project
+ preconditions:
+ - command -v dune
+ deps:
+ - task: build
+ cmds:
+ - cmd: dune exec -- ./bin/main.exe {{.CLI_ARGS}}
+
+ repl:
+ desc: Opens a REPL for the project
+ interactive: true
+ preconditions:
+ - command -v dune
+ - command -v utop
+ cmds:
+ - cmd: dune utop
diff --git a/ocaml/advent_of_code.opam b/ocaml/advent_of_code.opam
new file mode 100644
index 0000000..103f2ce
--- /dev/null
+++ b/ocaml/advent_of_code.opam
@@ -0,0 +1,42 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "advent of code solutions for 2025"
+description:
+ "project aiming to solve the advent of code puzzles released for 2025"
+maintainer: ["DJ O'Leary <dijitol@proton.me>"]
+authors: ["DJ O'Leary <dijitol@proton.me>"]
+license: "Unlicense"
+tags: ["Advent of Code"]
+homepage: "https://github.com/djoleary/advent_of_code"
+doc: "https://github.com/djoleary/advent_of_code/2025/ocaml/docs/index.html"
+bug-reports: "https://github.com/djoleary/advent_of_code/issues"
+depends: [
+ "dune" {>= "3.20"}
+ "ocaml" {>= "5.2"}
+ "ppx_deriving" {build}
+ "alcotest" {with-test}
+ "qcheck" {with-test}
+ "bisect_ppx" {with-test}
+ "ppx_inline_test" {with-test}
+ "ppx_expect" {with-test}
+ "odoc" {with-doc}
+ "cmdliner"
+ "fmt"
+ "re2"
+]
+build: [
+ ["dune" "subst"] {dev}
+ [
+ "dune"
+ "build"
+ "-p"
+ name
+ "-j"
+ jobs
+ "@install"
+ "@runtest" {with-test}
+ "@doc" {with-doc}
+ ]
+]
+dev-repo: "git+https://github.com/djoleary/advent_of_code.git"
+x-maintenance-intent: ["(latest)"]
diff --git a/ocaml/bin/dune b/ocaml/bin/dune
new file mode 100644
index 0000000..0f6768d
--- /dev/null
+++ b/ocaml/bin/dune
@@ -0,0 +1,11 @@
+(executable
+ (public_name advent_of_code)
+ (name main)
+ (libraries
+ domain
+ application
+ infrastructure
+ presentation
+ logs
+ logs.fmt)
+ (modes byte exe))
diff --git a/ocaml/bin/main.ml b/ocaml/bin/main.ml
new file mode 100644
index 0000000..cbfe6d2
--- /dev/null
+++ b/ocaml/bin/main.ml
@@ -0,0 +1,16 @@
+let env_log_level =
+ match Sys.getenv_opt "LOG_LEVEL" with
+ | None ->
+ Some Logs.Warning
+ | Some lvl -> (
+ match Logs.level_of_string lvl with Error _ -> None | Ok v -> v )
+
+let main () =
+ let log_level = env_log_level in
+ Logs.set_level log_level ;
+ Logs.set_reporter (Logs_fmt.reporter ()) ;
+ Logs.info (fun m ->
+ m "Started application with LOG_LEVEL=%s" (Logs.level_to_string log_level) ) ;
+ Presentation.Cli.run ()
+
+let () = if !Sys.interactive then () else exit (main ())
diff --git a/ocaml/docs/Main.html b/ocaml/docs/Main.html
new file mode 100644
index 0000000..11d7257
--- /dev/null
+++ b/ocaml/docs/Main.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link rel="Up" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Main</title>
+</head>
+<body>
+<div class="navbar">&nbsp;<a class="up" href="index.html" title="Index">Up</a>
+&nbsp;</div>
+<h1>Module <a href="type_Main.html">Main</a></h1>
+
+<pre><span id="MODULEMain"><span class="keyword">module</span> Main</span>: <code class="code">sig</code> <a href="Main.html">..</a> <code class="code">end</code></pre><hr width="100%">
+</body></html>
diff --git a/ocaml/docs/index.html b/ocaml/docs/index.html
new file mode 100644
index 0000000..f536f1a
--- /dev/null
+++ b/ocaml/docs/index.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title></title>
+</head>
+<body>
+<div class = "index-list">
+<ul class="indexlist">
+<li><a href="index_modules.html">Index of modules</a></li>
+</ul>
+</div>
+
+<table class="indextable module-list">
+<tr><td class="module"><a href="Main.html">Main</a></td><td></td></tr>
+</table>
+</body>
+</html>
diff --git a/ocaml/docs/index_attributes.html b/ocaml/docs/index_attributes.html
new file mode 100644
index 0000000..3450f40
--- /dev/null
+++ b/ocaml/docs/index_attributes.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Index of class attributes</title>
+</head>
+<body>
+<div class="navbar">&nbsp;<a class="up" href="index.html" title="Index">Up</a>
+&nbsp;</div>
+<h1>Index of class attributes</h1>
+<table>
+</table>
+</body>
+</html>
diff --git a/ocaml/docs/index_class_types.html b/ocaml/docs/index_class_types.html
new file mode 100644
index 0000000..be3ecc7
--- /dev/null
+++ b/ocaml/docs/index_class_types.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Index of class types</title>
+</head>
+<body>
+<div class="navbar">&nbsp;<a class="up" href="index.html" title="Index">Up</a>
+&nbsp;</div>
+<h1>Index of class types</h1>
+<table>
+</table>
+</body>
+</html>
diff --git a/ocaml/docs/index_classes.html b/ocaml/docs/index_classes.html
new file mode 100644
index 0000000..08c083b
--- /dev/null
+++ b/ocaml/docs/index_classes.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Index of classes</title>
+</head>
+<body>
+<div class="navbar">&nbsp;<a class="up" href="index.html" title="Index">Up</a>
+&nbsp;</div>
+<h1>Index of classes</h1>
+<table>
+</table>
+</body>
+</html>
diff --git a/ocaml/docs/index_exceptions.html b/ocaml/docs/index_exceptions.html
new file mode 100644
index 0000000..0767720
--- /dev/null
+++ b/ocaml/docs/index_exceptions.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Index of exceptions</title>
+</head>
+<body>
+<div class="navbar">&nbsp;<a class="up" href="index.html" title="Index">Up</a>
+&nbsp;</div>
+<h1>Index of exceptions</h1>
+<table>
+</table>
+</body>
+</html>
diff --git a/ocaml/docs/index_extensions.html b/ocaml/docs/index_extensions.html
new file mode 100644
index 0000000..38625df
--- /dev/null
+++ b/ocaml/docs/index_extensions.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Index of extensions</title>
+</head>
+<body>
+<div class="navbar">&nbsp;<a class="up" href="index.html" title="Index">Up</a>
+&nbsp;</div>
+<h1>Index of extensions</h1>
+<table>
+</table>
+</body>
+</html>
diff --git a/ocaml/docs/index_methods.html b/ocaml/docs/index_methods.html
new file mode 100644
index 0000000..7debe46
--- /dev/null
+++ b/ocaml/docs/index_methods.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Index of class methods</title>
+</head>
+<body>
+<div class="navbar">&nbsp;<a class="up" href="index.html" title="Index">Up</a>
+&nbsp;</div>
+<h1>Index of class methods</h1>
+<table>
+</table>
+</body>
+</html>
diff --git a/ocaml/docs/index_module_types.html b/ocaml/docs/index_module_types.html
new file mode 100644
index 0000000..0b75a38
--- /dev/null
+++ b/ocaml/docs/index_module_types.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Index of module types</title>
+</head>
+<body>
+<div class="navbar">&nbsp;<a class="up" href="index.html" title="Index">Up</a>
+&nbsp;</div>
+<h1>Index of module types</h1>
+<table>
+</table>
+</body>
+</html>
diff --git a/ocaml/docs/index_modules.html b/ocaml/docs/index_modules.html
new file mode 100644
index 0000000..b6588a3
--- /dev/null
+++ b/ocaml/docs/index_modules.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Index of modules</title>
+</head>
+<body>
+<div class="navbar">&nbsp;<a class="up" href="index.html" title="Index">Up</a>
+&nbsp;</div>
+<h1>Index of modules</h1>
+<table>
+<tr><td align="left"><div>M</div></td></tr>
+<tr><td><a href="Main.html">Main</a> </td>
+<td></td></tr>
+</table>
+</body>
+</html>
diff --git a/ocaml/docs/index_types.html b/ocaml/docs/index_types.html
new file mode 100644
index 0000000..61efcda
--- /dev/null
+++ b/ocaml/docs/index_types.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Index of types</title>
+</head>
+<body>
+<div class="navbar">&nbsp;<a class="up" href="index.html" title="Index">Up</a>
+&nbsp;</div>
+<h1>Index of types</h1>
+<table>
+</table>
+</body>
+</html>
diff --git a/ocaml/docs/index_values.html b/ocaml/docs/index_values.html
new file mode 100644
index 0000000..fc7294b
--- /dev/null
+++ b/ocaml/docs/index_values.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Index of values</title>
+</head>
+<body>
+<div class="navbar">&nbsp;<a class="up" href="index.html" title="Index">Up</a>
+&nbsp;</div>
+<h1>Index of values</h1>
+<table>
+</table>
+</body>
+</html>
diff --git a/ocaml/docs/style.css b/ocaml/docs/style.css
new file mode 100644
index 0000000..259bd49
--- /dev/null
+++ b/ocaml/docs/style.css
@@ -0,0 +1,43 @@
+.keyword { font-weight : bold ; color : Red }
+.keywordsign { color : #C04600 }
+.comment { color : Green }
+.constructor { color : Blue }
+.type { color : #5C6585 }
+.string { color : Maroon }
+.warning { color : Red ; font-weight : bold }
+.info { margin-left : 3em; margin-right: 3em }
+.param_info { margin-top: 4px; margin-left : 3em; margin-right : 3em }
+.code { color : #465F91 ; }
+.typetable { border-style : hidden }
+.paramstable { border-style : hidden ; padding: 5pt 5pt}
+tr { background-color : White }
+td.typefieldcomment { background-color : #FFFFFF ; font-size: smaller ;}
+div.sig_block {margin-left: 2em}
+*:target { background: yellow; }
+body {font: 13px sans-serif; color: black; text-align: left; padding: 5px; margin: 0}
+h1 { font-size : 20pt ; text-align: center; }
+h2 { font-size : 20pt ; text-align: center; }
+h3 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90BDFF ;padding: 2px; }
+h4 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90DDFF ;padding: 2px; }
+h5 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90EDFF ;padding: 2px; }
+h6 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90FDFF ;padding: 2px; }
+div.h7 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #90BDFF ; padding: 2px; }
+div.h8 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #E0FFFF ; padding: 2px; }
+div.h9 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #F0FFFF ; padding: 2px; }
+div.h10 { font-size : 20pt ; border: 1px solid #000000; margin-top: 5px; margin-bottom: 2px;text-align: center; background-color: #FFFFFF ; padding: 2px; }
+a {color: #416DFF; text-decoration: none}
+a:hover {background-color: #ddd; text-decoration: underline}
+pre { margin-bottom: 4px; font-family: monospace; }
+pre.verbatim, pre.codepre { }
+.indextable {border: 1px #ddd solid; border-collapse: collapse}
+.indextable td, .indextable th {border: 1px #ddd solid; min-width: 80px}
+.indextable td.module {background-color: #eee ; padding-left: 2px; padding-right: 2px}
+.indextable td.module a {color: #4E6272; text-decoration: none; display: block; width: 100%}
+.indextable td.module a:hover {text-decoration: underline; background-color: transparent}
+.deprecated {color: #888; font-style: italic}
+.indextable tr td div.info { margin-left: 2px; margin-right: 2px }
+ul.indexlist { margin-left: 0; padding-left: 0;}
+ul.indexlist li { list-style-type: none ; margin-left: 0; padding-left: 0; }
+ul.info-attributes {list-style: none; margin: 0; padding: 0; }
+div.info > p:first-child { margin-top:0; }
+div.info-desc > p:first-child { margin-top:0; margin-bottom:0; } \ No newline at end of file
diff --git a/ocaml/docs/type_Main.html b/ocaml/docs/type_Main.html
new file mode 100644
index 0000000..43bb69c
--- /dev/null
+++ b/ocaml/docs/type_Main.html
@@ -0,0 +1,10 @@
+<html><head>
+<link rel="stylesheet" href="style.css" type="text/css">
+<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="Start" href="index.html">
+<link title="Index of modules" rel=Appendix href="index_modules.html">
+<link title="Main" rel="Chapter" href="Main.html"><title>Main</title>
+</head>
+<body>
+<code class="code"><span class="keyword">sig</span>&nbsp;<span class="keyword">end</span></code></body></html>
diff --git a/ocaml/dune-project b/ocaml/dune-project
new file mode 100644
index 0000000..731f4b5
--- /dev/null
+++ b/ocaml/dune-project
@@ -0,0 +1,40 @@
+(lang dune 3.20)
+
+(name advent_of_code)
+
+(generate_opam_files true)
+
+(source
+ (github djoleary/advent_of_code))
+
+(authors "DJ O'Leary <dijitol@proton.me>")
+
+(maintainers "DJ O'Leary <dijitol@proton.me>")
+
+(license Unlicense)
+
+(documentation https://github.com/djoleary/advent_of_code/2025/ocaml/docs/index.html)
+
+(package
+ (name advent_of_code)
+ (synopsis "advent of code solutions for 2025")
+ (description "project aiming to solve the advent of code puzzles released for 2025")
+ (depends
+ (ocaml (>= 5.2))
+ (ppx_deriving :build)
+ (alcotest :with-test)
+ (qcheck :with-test)
+ (bisect_ppx :with-test)
+ (ppx_inline_test :with-test)
+ (ppx_expect :with-test)
+ (odoc :with-doc)
+ cmdliner
+ fmt
+ re2
+ )
+ (tags
+ ("Advent of Code")))
+
+(map_workspace_root false)
+
+; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html
diff --git a/ocaml/flake.lock b/ocaml/flake.lock
new file mode 100644
index 0000000..3baeb69
--- /dev/null
+++ b/ocaml/flake.lock
@@ -0,0 +1,27 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1763421233,
+ "narHash": "sha256-Stk9ZYRkGrnnpyJ4eqt9eQtdFWRRIvMxpNRf4sIegnw=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "89c2b2330e733d6cdb5eae7b899326930c2c0648",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/ocaml/flake.nix b/ocaml/flake.nix
new file mode 100644
index 0000000..54d7b3f
--- /dev/null
+++ b/ocaml/flake.nix
@@ -0,0 +1,52 @@
+{
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
+ };
+
+ outputs =
+ { nixpkgs, ... }:
+ let
+ pkgs = nixpkgs.legacyPackages.x86_64-linux;
+ in
+ {
+ devShells.x86_64-linux.default =
+ let
+ ocamlPackages = with pkgs.ocaml-ng.ocamlPackages_5_2; [
+ ocaml
+
+ dune_3 # build system
+
+ pkgs.opam # package manager
+ findlib # library manager
+
+ ocaml-lsp # language server
+ earlybird # debug adapter
+ ocamlformat # formatter
+
+ utop # REPL
+
+ odoc # documentation generator
+
+ alcotest # unit test framework
+ bisect_ppx # test coverage
+ qcheck-core # property-based testing
+ qcheck-alcotest # property-based testing
+ janeStreet.ppx_inline_test # inline test library
+ janeStreet.ppx_expect # inline test library
+
+ janeStreet.re2 # regular expression library
+ fmt # pretty printing library
+ cmdliner # command line library
+ ppx_deriving # derivers
+ logs # logging library
+ ];
+ sysTools = with pkgs; [
+ go-task
+ gh
+ ];
+ in
+ pkgs.mkShell {
+ buildInputs = ocamlPackages ++ sysTools;
+ };
+ };
+} \ No newline at end of file
diff --git a/ocaml/lib/application/d01p01.ml b/ocaml/lib/application/d01p01.ml
new file mode 100644
index 0000000..206e757
--- /dev/null
+++ b/ocaml/lib/application/d01p01.ml
@@ -0,0 +1,53 @@
+open Domain.D01p01
+
+let err_empty_input = "input is empty"
+
+let rec input_to_lines input =
+ let lines =
+ String.split_on_char '\n' input |> List.filter (fun l -> l <> "")
+ in
+ pp_lines lines ; lines
+
+and pp_lines = function
+ | [] ->
+ ()
+ | h :: t ->
+ Logs.debug (fun m -> m "Line=%s" h) ;
+ pp_lines t
+
+let parse_line line =
+ Scanf.sscanf line "%c%d" (fun d n ->
+ match d with
+ | 'L' ->
+ Dial.Left n
+ | 'R' ->
+ Dial.Right n
+ | _ ->
+ failwith "invalid direction" )
+
+let lines_to_turns =
+ List.map (fun (l : string) ->
+ Logs.debug (fun m -> m "parsing %s into turn" l) ;
+ let turn = parse_line l in
+ Logs.debug (fun m -> m "%s" (Dial.pp_direction turn)) ;
+ turn )
+
+let rec turn_aux count current = function
+ | [] ->
+ count
+ | h :: t ->
+ let current' = Dial.turn current h in
+ Logs.debug (fun m -> m "dial at %d" (Dial.to_int current')) ;
+ if Dial.to_int current' = 0 then turn_aux (count + 1) current' t
+ else turn_aux count current' t
+
+let turn current = turn_aux 0 current
+
+let solve input () =
+ if input = "" then Error err_empty_input
+ else
+ let lines = input_to_lines input in
+ let turns = lines_to_turns lines in
+ let start = Dial.start in
+ let answer = turn start turns in
+ Ok answer
diff --git a/ocaml/lib/application/d01p01.mli b/ocaml/lib/application/d01p01.mli
new file mode 100644
index 0000000..2eecf19
--- /dev/null
+++ b/ocaml/lib/application/d01p01.mli
@@ -0,0 +1,4 @@
+val solve : string -> unit -> (int, string) result
+(** [solve input ()] is the solution for the first part of the first day of advent of code 2025 for [input].
+ The solution for an empty input is [None].
+ Requires: [input] to be a multiline string using `\n` as it's line separator. *)
diff --git a/ocaml/lib/application/d01p02.ml b/ocaml/lib/application/d01p02.ml
new file mode 100644
index 0000000..7910486
--- /dev/null
+++ b/ocaml/lib/application/d01p02.ml
@@ -0,0 +1,55 @@
+open Domain.D01p02
+
+let err_empty_input = "input is empty"
+
+let rec input_to_lines input =
+ let lines =
+ String.split_on_char '\n' input |> List.filter (fun l -> l <> "")
+ in
+ pp_lines lines ; lines
+
+and pp_lines = function
+ | [] ->
+ ()
+ | h :: t ->
+ Logs.debug (fun m -> m "Line=%s" h) ;
+ pp_lines t
+
+let parse_line line =
+ Scanf.sscanf line "%c%d" (fun d n ->
+ match d with
+ | 'L' ->
+ Dial.Left n
+ | 'R' ->
+ Dial.Right n
+ | _ ->
+ failwith "invalid direction" )
+
+let lines_to_turns =
+ List.map (fun (l : string) ->
+ Logs.debug (fun m -> m "parsing %s into turn" l) ;
+ let turn = parse_line l in
+ Logs.debug (fun m -> m "%s" (Dial.pp_direction turn)) ;
+ turn )
+
+let rec turn_aux count dial = function
+ | [] ->
+ count
+ | h :: t -> (
+ let dial_and_count = Turner.turn dial count h in
+ match dial_and_count with
+ | dial', count' ->
+ Logs.debug (fun m ->
+ m "(dial=%d, count=%d)" (Dial.to_int dial') count' ) ;
+ turn_aux count' dial' t )
+
+let turn current = turn_aux 0 current
+
+let solve input () =
+ if input = "" then Error err_empty_input
+ else
+ let lines = input_to_lines input in
+ let turns = lines_to_turns lines in
+ let start = Dial.start in
+ let answer = turn start turns in
+ Ok answer
diff --git a/ocaml/lib/application/d01p02.mli b/ocaml/lib/application/d01p02.mli
new file mode 100644
index 0000000..9e9a2f2
--- /dev/null
+++ b/ocaml/lib/application/d01p02.mli
@@ -0,0 +1,4 @@
+val solve : string -> unit -> (int, string) result
+(** [solve input ()] is the solution for the second part of the first day of advent of code 2025 for [input].
+ The solution for an empty input is [Error msg].
+ Requires: [input] to be a multiline string using `\n` as it's line separator. *)
diff --git a/ocaml/lib/application/d02p01.ml b/ocaml/lib/application/d02p01.ml
new file mode 100644
index 0000000..f313149
--- /dev/null
+++ b/ocaml/lib/application/d02p01.ml
@@ -0,0 +1,54 @@
+open Domain.D02p01
+
+let err_empty_input = "input is empty"
+
+(** [input_to_lines input] is a list of strings that have been split on ','.
+ Example: [input_to_lines "12-34,56-78,90-100"] becomes the list \[ "12-34"; "56-78"; "90-100" \].
+ Requires: [input] to be a string in the format "12-34,56-78,90-100". *)
+let input_to_lines input = input |> String.trim |> String.split_on_char ','
+
+let pp_lines = Seq.iter (fun l -> Logs.debug (fun m -> m "line=%s" l))
+
+(** [lines_to_ranges line] is a list of [Range]s found on [line]. *)
+let lines_to_ranges =
+ Seq.map (fun line ->
+ Scanf.sscanf line "%d-%d" (fun id1 id2 -> Range.create (id1, id2)) )
+
+let pp_ranges = Seq.iter (fun r -> Logs.debug (fun m -> m "%s" (Range.pp r)))
+
+(** [collect_all ranges] is an unsorted list of [ID]s within the [ranges] (inclusive). *)
+let collect_all =
+ Seq.concat_map (fun range -> List.to_seq (Range.collect range))
+
+let pp_collection = Seq.iter (fun c -> Logs.debug (fun m -> m "%s" (ID.pp c)))
+
+(** [rules] is a list of rules that [ID]s must pass to be considered invalid. *)
+let rules =
+ [ (fun id -> String.length (ID.to_string id) mod 2 = 0)
+ ; (fun id ->
+ let id' = ID.to_string id in
+ let id_len = String.length id' in
+ let left = String.sub id' 0 (id_len / 2) in
+ let right = String.sub id' (id_len / 2) (id_len / 2) in
+ String.equal left right ) ]
+
+(** [is_invalid id] is [true] for an [id] that passes all of the rules and is considered invalid. *)
+let is_invalid = ID.validate rules
+
+(** [passthrough_with_logs logger lst] is [lst] with the side-effects caused by calling [logger] on [lst]. *)
+let passthrough_with_logs logger lst = logger lst ; lst
+
+let solve input () =
+ if input = "" then Error err_empty_input
+ else
+ let answer =
+ input |> input_to_lines |> List.to_seq
+ |> passthrough_with_logs pp_lines
+ |> lines_to_ranges
+ |> passthrough_with_logs pp_ranges
+ |> collect_all
+ |> passthrough_with_logs pp_collection
+ |> Seq.filter is_invalid
+ |> Seq.fold_left (fun acc id -> acc + ID.to_int id) 0
+ in
+ Ok answer
diff --git a/ocaml/lib/application/d02p01.mli b/ocaml/lib/application/d02p01.mli
new file mode 100644
index 0000000..8a8eb32
--- /dev/null
+++ b/ocaml/lib/application/d02p01.mli
@@ -0,0 +1,4 @@
+val solve : string -> unit -> (int, string) result
+(** [solve input ()] is the solution for the first part of the second day of advent of code 2025 for [input].
+ The solution for an empty input is [Error msg].
+ Requires: [input] to be a string. *)
diff --git a/ocaml/lib/application/d02p02.ml b/ocaml/lib/application/d02p02.ml
new file mode 100644
index 0000000..d062f7b
--- /dev/null
+++ b/ocaml/lib/application/d02p02.ml
@@ -0,0 +1,86 @@
+open Domain.D02p02
+
+(* Errors *)
+
+let err_empty_input = "input is empty"
+
+(* Logging *)
+
+(** [passthrough_with_logs logger lst] is [lst] with the side-effects caused by calling [logger] on [lst]. *)
+let passthrough_with_logs logger lst = logger lst ; lst
+
+(** [input_to_lines input] is a list of strings that have been split on ','.
+ Example: [input_to_lines "12-34,56-78,90-100"] becomes the list \[ "12-34"; "56-78"; "90-100" \].
+ Requires: [input] to be a string in the format "12-34,56-78,90-100". *)
+let input_to_lines input = input |> String.trim |> String.split_on_char ','
+
+let pp_lines = Seq.iter (fun l -> Logs.debug (fun m -> m "line=%s" l))
+
+let pp_ranges = Seq.iter (fun r -> Logs.debug (fun m -> m "%s" (Range.pp r)))
+
+let pp_collection = Seq.iter (fun c -> Logs.debug (fun m -> m "%s" (ID.pp c)))
+
+(* Core Logic *)
+
+(** [lines_to_ranges line] is a list of [Range]s found on [line]. *)
+let lines_to_ranges =
+ Seq.map (fun line ->
+ Scanf.sscanf line "%d-%d" (fun id1 id2 -> Range.create (id1, id2)) )
+
+(** [collect_all ranges] is an unsorted list of [ID]s within the [ranges] (inclusive). *)
+let collect_all =
+ Seq.concat_map (fun range -> List.to_seq (Range.collect range))
+
+(** [string_to_segments count string] is a list containing [count] string segments with of equal length.
+ Requires: length of [string] to be divisible by [count]
+ and [count] to be greater than or equal to zero *)
+let rec string_to_segments s =
+ let segment_lengths = possible_segment_lengths s in
+ List.map (fun c -> string_to_segments_aux [] c s |> List.rev) segment_lengths
+
+(** [possible_segment_lengths s] is a list of segment sizes that fit within [s]
+ that must be checked to rule out a repeating pattern. *)
+and possible_segment_lengths s =
+ List.init (String.length s) (fun x -> x + 1)
+ |> List.filter (fun x -> x != 1 && String.length s mod x = 0)
+
+and string_to_segments_aux segments count string =
+ match List.length segments = count with
+ | true ->
+ segments
+ | false ->
+ let segment_start = String.length string / count * List.length segments in
+ let segment_len = String.length string / count in
+ string_to_segments_aux
+ (String.sub string segment_start segment_len :: segments)
+ count string
+
+(** [cmp_segments segments] is true is all segments are equal *)
+let cmp_segments segments =
+ List.length (List.sort_uniq String.compare segments) = 1
+
+(** [is_invalid id] is [true] for an [id] that is considered invalid. *)
+let is_invalid id =
+ Logs.debug (fun m -> m "CHECKING ID=%s" id) ;
+ let repeating_patterns =
+ id |> string_to_segments |> List.filter cmp_segments
+ in
+ List.iter
+ (fun l -> Logs.debug (fun m -> m "HAS SUBPATTERN=%s" (List.hd l)))
+ repeating_patterns ;
+ List.length repeating_patterns > 0
+
+let solve input () =
+ if input = "" then Error err_empty_input
+ else
+ let answer =
+ input |> input_to_lines |> List.to_seq
+ |> passthrough_with_logs pp_lines
+ |> lines_to_ranges
+ |> passthrough_with_logs pp_ranges
+ |> collect_all
+ |> passthrough_with_logs pp_collection
+ |> Seq.map ID.to_string |> Seq.filter is_invalid
+ |> Seq.fold_left (fun acc id -> acc + int_of_string id) 0
+ in
+ Ok answer
diff --git a/ocaml/lib/application/d02p02.mli b/ocaml/lib/application/d02p02.mli
new file mode 100644
index 0000000..8a8eb32
--- /dev/null
+++ b/ocaml/lib/application/d02p02.mli
@@ -0,0 +1,4 @@
+val solve : string -> unit -> (int, string) result
+(** [solve input ()] is the solution for the first part of the second day of advent of code 2025 for [input].
+ The solution for an empty input is [Error msg].
+ Requires: [input] to be a string. *)
diff --git a/ocaml/lib/application/d03p01.ml b/ocaml/lib/application/d03p01.ml
new file mode 100644
index 0000000..522f5d0
--- /dev/null
+++ b/ocaml/lib/application/d03p01.ml
@@ -0,0 +1,23 @@
+open Domain.D03p01
+
+let err_empty_input = "input is empty"
+
+let to_lines s =
+ s |> String.split_on_char '\n' |> List.map String.trim
+ |> List.filter (fun l -> l <> "")
+
+let solve input () =
+ if input = "" then Error err_empty_input
+ else
+ let answer =
+ input |> to_lines |> List.to_seq
+ |> Seq.map BatteryBank.of_string
+ |> Seq.mapi (fun idx bb ->
+ let joltage = BatteryBank.highest_joltage bb in
+ Logs.debug (fun m ->
+ m "%d line=%s highest_joltage=%d" (idx + 1) (BatteryBank.pp bb)
+ joltage ) ;
+ joltage )
+ |> Seq.fold_left ( + ) 0
+ in
+ Ok answer
diff --git a/ocaml/lib/application/d03p01.mli b/ocaml/lib/application/d03p01.mli
new file mode 100644
index 0000000..31dd0cd
--- /dev/null
+++ b/ocaml/lib/application/d03p01.mli
@@ -0,0 +1,4 @@
+val solve : string -> unit -> (int, string) result
+(** [solve input ()] is the solution for the first part of the third day of advent of code 2025 for [input].
+ The solution for an empty input is [Error msg].
+ Requires: [input] to be a string. *)
diff --git a/ocaml/lib/application/d03p02.ml b/ocaml/lib/application/d03p02.ml
new file mode 100644
index 0000000..743c658
--- /dev/null
+++ b/ocaml/lib/application/d03p02.ml
@@ -0,0 +1,23 @@
+open Domain.D03p02
+
+let err_empty_input = "input is empty"
+
+let to_lines s =
+ s |> String.split_on_char '\n' |> List.map String.trim
+ |> List.filter (fun l -> l <> "")
+
+let solve input () =
+ if input = "" then Error err_empty_input
+ else
+ let answer =
+ input |> to_lines |> List.to_seq
+ |> Seq.map BatteryBank.of_string
+ |> Seq.mapi (fun idx bb ->
+ let joltage = BatteryBank.highest_joltage bb in
+ Logs.debug (fun m ->
+ m "%d line=%s highest_joltage=%d" (idx + 1) (BatteryBank.pp bb)
+ joltage ) ;
+ joltage )
+ |> Seq.fold_left ( + ) 0
+ in
+ Ok answer
diff --git a/ocaml/lib/application/d03p02.mli b/ocaml/lib/application/d03p02.mli
new file mode 100644
index 0000000..93d3b98
--- /dev/null
+++ b/ocaml/lib/application/d03p02.mli
@@ -0,0 +1,4 @@
+val solve : string -> unit -> (int, string) result
+(** [solve input ()] is the solution for the second part of the third day of advent of code 2025 for [input].
+ The solution for an empty input is [Error msg].
+ Requires: [input] to be a string. *)
diff --git a/ocaml/lib/application/d04p01.ml b/ocaml/lib/application/d04p01.ml
new file mode 100644
index 0000000..7c69de4
--- /dev/null
+++ b/ocaml/lib/application/d04p01.ml
@@ -0,0 +1,48 @@
+open Domain
+
+let err_empty_input = "input is empty"
+
+let rec solve input () =
+ if input = "" then Error err_empty_input
+ else
+ let matrix = Matrix.M.of_string input in
+ let count_paper lst = List.length @@ List.filter (fun c -> c <> ".") lst in
+ let accessible_rolls_of_paper = ref 0 in
+ for y = 0 to Array.length matrix - 1 do
+ for x = 0 to Array.length matrix.(y) - 1 do
+ if matrix.(y).(x) = "." then ()
+ else
+ let neighbour_coords = Matrix.M.get_valid_neighbours matrix x y in
+ let neighbours =
+ List.map
+ (fun (x', y') -> Matrix.M.get matrix x' y')
+ neighbour_coords
+ in
+ let paper_count = count_paper neighbours in
+ let is_accessible = paper_count < 4 in
+ if is_accessible then
+ accessible_rolls_of_paper := !accessible_rolls_of_paper + 1 ;
+ print_iteration_debug_msg x y neighbour_coords neighbours paper_count
+ is_accessible
+ done
+ done ;
+ Ok !accessible_rolls_of_paper
+
+and print_iteration_debug_msg x y neighbour_coords neighbours paper_count
+ is_accessible =
+ Logs.debug (fun m ->
+ m
+ "coords=(%d,%d) neighbour_coords=[%s] neighbours=[%s] paper=%d \
+ is_accessible=%B"
+ x y
+ (List.fold_left
+ (fun acc (x, y) ->
+ if acc = "" then
+ " (" ^ string_of_int x ^ "," ^ string_of_int y ^ "); "
+ else acc ^ "(" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " )
+ "" neighbour_coords )
+ (List.fold_left
+ (fun acc neighbour ->
+ if acc = "" then " " ^ neighbour ^ "; " else acc ^ neighbour ^ "; " )
+ "" neighbours )
+ paper_count is_accessible )
diff --git a/ocaml/lib/application/d04p01.mli b/ocaml/lib/application/d04p01.mli
new file mode 100644
index 0000000..873acff
--- /dev/null
+++ b/ocaml/lib/application/d04p01.mli
@@ -0,0 +1,4 @@
+val solve : string -> unit -> (int, string) result
+(** [solve input ()] is the solution for the first part of the fourth day of advent of code 2025 for [input].
+ The solution for an empty input is [Error msg].
+ Requires: [input] to be a string. *)
diff --git a/ocaml/lib/application/d04p02.ml b/ocaml/lib/application/d04p02.ml
new file mode 100644
index 0000000..75b1236
--- /dev/null
+++ b/ocaml/lib/application/d04p02.ml
@@ -0,0 +1,64 @@
+open Domain
+
+let err_empty_input = "input is empty"
+
+let rec count_accessible_rolls matrix =
+ let count_paper lst = List.length @@ List.filter (fun c -> c = "@") lst in
+ let accessible_rolls_of_paper = ref [] in
+ let no_change = ref false in
+ while not !no_change do
+ let accessible_rolls_of_paper' = !accessible_rolls_of_paper in
+ count_accessible_rolls_aux matrix count_paper accessible_rolls_of_paper ;
+ if
+ List.length accessible_rolls_of_paper'
+ = List.length !accessible_rolls_of_paper
+ then no_change := true
+ done ;
+ List.length !accessible_rolls_of_paper
+
+and count_accessible_rolls_aux matrix count_paper accessible_rolls_of_paper =
+ for y = 0 to Array.length matrix - 1 do
+ for x = 0 to Array.length matrix.(y) - 1 do
+ if matrix.(y).(x) = "." then ()
+ else
+ let neighbour_coords = Matrix.M.get_valid_neighbours matrix x y in
+ let neighbours =
+ List.map (fun (nx, ny) -> Matrix.M.get matrix nx ny) neighbour_coords
+ in
+ let paper_count = count_paper neighbours in
+ let is_accessible = paper_count < 4 in
+ if is_accessible then
+ accessible_rolls_of_paper := (x, y) :: !accessible_rolls_of_paper ;
+ print_iteration_debug_msg x y neighbour_coords neighbours paper_count
+ is_accessible
+ done
+ done ;
+ List.iter
+ (fun (x', y') -> Matrix.M.set matrix x' y' ".")
+ !accessible_rolls_of_paper
+
+and print_iteration_debug_msg x y neighbour_coords neighbours paper_count
+ is_accessible =
+ Logs.debug (fun m ->
+ m
+ "coords=(%d,%d) neighbour_coords=[%s] neighbours=[%s] paper=%d \
+ is_accessible=%B"
+ x y
+ (List.fold_left
+ (fun acc (x, y) ->
+ if acc = "" then
+ " (" ^ string_of_int x ^ "," ^ string_of_int y ^ "); "
+ else acc ^ "(" ^ string_of_int x ^ "," ^ string_of_int y ^ "); " )
+ "" neighbour_coords )
+ (List.fold_left
+ (fun acc neighbour ->
+ if acc = "" then " " ^ neighbour ^ "; " else acc ^ neighbour ^ "; " )
+ "" neighbours )
+ paper_count is_accessible )
+
+let solve input () =
+ if input = "" then Error err_empty_input
+ else
+ let matrix = Matrix.M.of_string input in
+ let accessible_rolls_of_paper = count_accessible_rolls matrix in
+ Ok accessible_rolls_of_paper
diff --git a/ocaml/lib/application/d04p02.mli b/ocaml/lib/application/d04p02.mli
new file mode 100644
index 0000000..388ec56
--- /dev/null
+++ b/ocaml/lib/application/d04p02.mli
@@ -0,0 +1,4 @@
+val solve : string -> unit -> (int, string) result
+(** [solve input ()] is the solution for the second part of the fourth day of advent of code 2025 for [input].
+ The solution for an empty input is [Error msg].
+ Requires: [input] to be a string. *)
diff --git a/ocaml/lib/application/d05p01.ml b/ocaml/lib/application/d05p01.ml
new file mode 100644
index 0000000..4bf79ab
--- /dev/null
+++ b/ocaml/lib/application/d05p01.ml
@@ -0,0 +1,3 @@
+let err_empty_input = "input is empty"
+
+let solve input () = if input = "" then Error err_empty_input else Ok 0
diff --git a/ocaml/lib/application/d05p01.mli b/ocaml/lib/application/d05p01.mli
new file mode 100644
index 0000000..85c3dd5
--- /dev/null
+++ b/ocaml/lib/application/d05p01.mli
@@ -0,0 +1,4 @@
+val solve : string -> unit -> (int, string) result
+(** [solve input ()] is the solution for the first part of the five day of advent of code 2025 for [input].
+ The solution for an empty input is [Error msg].
+ Requires: [input] to be a string. *)
diff --git a/ocaml/lib/application/d06p01.ml b/ocaml/lib/application/d06p01.ml
new file mode 100644
index 0000000..9425810
--- /dev/null
+++ b/ocaml/lib/application/d06p01.ml
@@ -0,0 +1,50 @@
+let err_empty_input = "input is empty"
+
+let log_matrix matrix =
+ let open Domain.Matrix in
+ Logs.debug (fun m -> m "%s" (M.of_list matrix |> M.pp (fun elt -> elt)))
+
+let log_list lst =
+ Logs.debug (fun m ->
+ m "[%s]" (List.fold_left (fun acc i -> acc ^ i ^ "; ") " " lst) )
+
+type operation = Mult | Add
+
+let solve input () =
+ if input = "" then Error err_empty_input
+ else
+ let input' = Parser.columns_of_string input in
+ assert (input' <> []) ;
+ log_matrix input' ;
+ let nums =
+ List.map (List.filter (fun i -> i <> "*" && i <> "+")) input'
+ |> List.map
+ @@ List.map (fun n ->
+ Logs.debug (fun m -> m "peeking: %s" n) ;
+ int_of_string n )
+ and ops =
+ List.map (List.filter (fun i -> i = "*" || i = "+")) input'
+ |> List.concat
+ |> List.map (fun op ->
+ match op with
+ | "*" ->
+ Mult
+ | "+" ->
+ Add
+ | _ ->
+ failwith "case not covered" )
+ in
+ let answer =
+ List.map2
+ (fun op num_lst ->
+ match op with
+ | Add ->
+ List.fold_left (fun acc i -> acc + i) 0 num_lst
+ | Mult ->
+ List.fold_left (fun acc i -> acc * i) 1 num_lst )
+ ops nums
+ |> fun lst ->
+ log_list @@ List.map string_of_int lst ;
+ lst |> List.fold_left ( + ) 0
+ in
+ Ok answer
diff --git a/ocaml/lib/application/d06p01.mli b/ocaml/lib/application/d06p01.mli
new file mode 100644
index 0000000..b3abc1e
--- /dev/null
+++ b/ocaml/lib/application/d06p01.mli
@@ -0,0 +1,4 @@
+val solve : string -> unit -> (int, string) result
+(** [solve input ()] is the solution for the first part of the sixth day of advent of code 2025 for [input].
+ The solution for an empty input is [Error msg].
+ Requires: [input] to be a string. *)
diff --git a/ocaml/lib/application/d06p02.ml b/ocaml/lib/application/d06p02.ml
new file mode 100644
index 0000000..9425810
--- /dev/null
+++ b/ocaml/lib/application/d06p02.ml
@@ -0,0 +1,50 @@
+let err_empty_input = "input is empty"
+
+let log_matrix matrix =
+ let open Domain.Matrix in
+ Logs.debug (fun m -> m "%s" (M.of_list matrix |> M.pp (fun elt -> elt)))
+
+let log_list lst =
+ Logs.debug (fun m ->
+ m "[%s]" (List.fold_left (fun acc i -> acc ^ i ^ "; ") " " lst) )
+
+type operation = Mult | Add
+
+let solve input () =
+ if input = "" then Error err_empty_input
+ else
+ let input' = Parser.columns_of_string input in
+ assert (input' <> []) ;
+ log_matrix input' ;
+ let nums =
+ List.map (List.filter (fun i -> i <> "*" && i <> "+")) input'
+ |> List.map
+ @@ List.map (fun n ->
+ Logs.debug (fun m -> m "peeking: %s" n) ;
+ int_of_string n )
+ and ops =
+ List.map (List.filter (fun i -> i = "*" || i = "+")) input'
+ |> List.concat
+ |> List.map (fun op ->
+ match op with
+ | "*" ->
+ Mult
+ | "+" ->
+ Add
+ | _ ->
+ failwith "case not covered" )
+ in
+ let answer =
+ List.map2
+ (fun op num_lst ->
+ match op with
+ | Add ->
+ List.fold_left (fun acc i -> acc + i) 0 num_lst
+ | Mult ->
+ List.fold_left (fun acc i -> acc * i) 1 num_lst )
+ ops nums
+ |> fun lst ->
+ log_list @@ List.map string_of_int lst ;
+ lst |> List.fold_left ( + ) 0
+ in
+ Ok answer
diff --git a/ocaml/lib/application/d06p02.mli b/ocaml/lib/application/d06p02.mli
new file mode 100644
index 0000000..49444de
--- /dev/null
+++ b/ocaml/lib/application/d06p02.mli
@@ -0,0 +1,4 @@
+val solve : string -> unit -> (int, string) result
+(** [solve input ()] is the solution for the second part of the sixth day of advent of code 2025 for [input].
+ The solution for an empty input is [Error msg].
+ Requires: [input] to be a string. *)
diff --git a/ocaml/lib/application/dune b/ocaml/lib/application/dune
new file mode 100644
index 0000000..b78613a
--- /dev/null
+++ b/ocaml/lib/application/dune
@@ -0,0 +1,14 @@
+(library
+ (name application)
+ (libraries
+ domain
+ fmt
+ logs)
+ (inline_tests)
+ (preprocess
+ (pps
+ ppx_deriving.show
+ ppx_deriving.eq
+ ppx_deriving.ord
+ ppx_inline_test
+ ppx_expect)))
diff --git a/ocaml/lib/application/parser.ml b/ocaml/lib/application/parser.ml
new file mode 100644
index 0000000..099900b
--- /dev/null
+++ b/ocaml/lib/application/parser.ml
@@ -0,0 +1,36 @@
+(** [rows_of_string s] is a list of strings where each element is a row of [s]. *)
+let rows_of_string s =
+ s |> String.split_on_char '\n'
+ |> List.filter_map (fun l ->
+ let trimmed = String.trim l in
+ if trimmed <> "" then Some trimmed else None )
+
+(** [columns_of_string ?sep s] is a list of strings where each element is
+ a column of [s] where each column is separated by [sep]. *)
+let rec columns_of_string ?(sep = ' ') s =
+ rows_of_string s
+ |> List.map (fun l ->
+ l |> String.split_on_char sep |> List.filter (fun l -> l <> "") )
+ |> transpose
+
+(** [group_columns lst] is a list where the first element of each
+ sublist of [lst] is grouped in [acc] and so on for the rest of the elements.
+ Example: [group_columns [] \[\["abc"; "def"\]; \["ghi"; "jkl"\]\]] is the list [\[\["abc";"ghi"\];\["def";"jkl"\]\]]. *)
+and transpose lst =
+ assert (lst <> []) ;
+ if List.mem [] lst then []
+ else List.map List.hd lst :: transpose (List.map List.tl lst)
+
+let%expect_test "rows correctly parsed" =
+ let have = {| abc def
+ ghi jkl |} in
+ let rows = rows_of_string have in
+ List.iter (fun c -> print_string (c ^ " ")) rows ;
+ [%expect {| abc def ghi jkl |}]
+
+let%expect_test "columns correctly parsed" =
+ let have = {| abc def
+ ghi jkl |} in
+ let cols = columns_of_string have in
+ List.iter (fun row -> List.iter (fun c -> print_string (c ^ " ")) row) cols ;
+ [%expect {| abc ghi def jkl |}]
diff --git a/ocaml/lib/domain/d01p01.ml b/ocaml/lib/domain/d01p01.ml
new file mode 100644
index 0000000..6b531f4
--- /dev/null
+++ b/ocaml/lib/domain/d01p01.ml
@@ -0,0 +1,76 @@
+module Dial : sig
+ (** [t] is a dial between 0 and 99 inclusive *)
+ type t
+
+ (** [direction] is a direction the dial can be turned *)
+ type direction = Left of int | Right of int
+
+ val start : t
+ (** [start] is the initial state of the dial *)
+
+ val dial_out_of_range : string
+ (** [dial_out_of_range] is the error message when the dial is out of range *)
+
+ val turn : t -> direction -> t
+ (** [turn current direction] is the new state of the dial after turning [direction] from [current] *)
+
+ val to_string : t -> string
+ (** [to_string t] is the string representation of [t] *)
+
+ val to_int : t -> int
+ (** [to_int t] is the integer representation of [t] *)
+
+ val pp_direction : direction -> string
+end = struct
+ (** [t] is an int between 0 and 99 inclusive *)
+ type t = int
+
+ type direction = Left of int | Right of int
+
+ let start = 50
+
+ let dial_out_of_range = "dial out of range"
+
+ (** [next n] is the new state of the dial after turning [Right].
+ Raises: [dial_out_of_range] if [n] is greater than 99. *)
+ let next = function
+ | n when n < 99 ->
+ n + 1
+ | 99 ->
+ 0
+ | _ ->
+ failwith dial_out_of_range
+
+ (** [prev n] is the new state of the dial after turning [Left].
+ Raises: [dial_out_of_range] if [n] is less than 0. *)
+ let prev = function
+ | n when n > 0 ->
+ n - 1
+ | 0 ->
+ 99
+ | _ ->
+ failwith dial_out_of_range
+
+ (** [turn current direction] is the new state of the dial after turning [direction] from [current].
+ Raises: [dial_out_of_range] if the dial is moved to an invalid state. *)
+ let rec turn current direction =
+ match direction with
+ | Left 0 ->
+ current
+ | Left n ->
+ turn (prev current) (Left (n - 1))
+ | Right 0 ->
+ current
+ | Right n ->
+ turn (next current) (Right (n - 1))
+
+ let to_string = string_of_int
+
+ let to_int t = t
+
+ let pp_direction = function
+ | Left n ->
+ "Left " ^ string_of_int n
+ | Right n ->
+ "Right " ^ string_of_int n
+end
diff --git a/ocaml/lib/domain/d01p02.ml b/ocaml/lib/domain/d01p02.ml
new file mode 100644
index 0000000..f71e965
--- /dev/null
+++ b/ocaml/lib/domain/d01p02.ml
@@ -0,0 +1,95 @@
+module Dial : sig
+ (** [t] is a dial between 0 and 99 inclusive *)
+ type t
+
+ (** [direction] is a direction the dial can be turned *)
+ type direction = Left of int | Right of int
+
+ val start : t
+ (** [start] is the initial state of the dial *)
+
+ val dial_out_of_range : string
+ (** [dial_out_of_range] is the error message when the dial is out of range *)
+
+ val prev : t -> t
+
+ val next : t -> t
+
+ val is_zero : t -> bool
+
+ val to_string : t -> string
+ (** [to_string t] is the string representation of [t] *)
+
+ val to_int : t -> int
+ (** [to_int t] is the integer representation of [t] *)
+
+ val pp_direction : direction -> string
+end = struct
+ (** [t] is an int between 0 and 99 inclusive *)
+ type t = int
+
+ type direction = Left of int | Right of int
+
+ let start = 50
+
+ let dial_out_of_range = "dial out of range"
+
+ (** [next n] is the new state of the dial after turning [Right].
+ Raises: [dial_out_of_range] if [n] is greater than 99. *)
+ let next = function
+ | n when n < 99 ->
+ n + 1
+ | 99 ->
+ 0
+ | _ ->
+ failwith dial_out_of_range
+
+ (** [prev n] is the new state of the dial after turning [Left].
+ Raises: [dial_out_of_range] if [n] is less than 0. *)
+ let prev = function
+ | n when n > 0 ->
+ n - 1
+ | 0 ->
+ 99
+ | _ ->
+ failwith dial_out_of_range
+
+ let is_zero = function 0 -> true | _ -> false
+
+ let to_string = string_of_int
+
+ let to_int t = t
+
+ let pp_direction = function
+ | Left n ->
+ "Left " ^ string_of_int n
+ | Right n ->
+ "Right " ^ string_of_int n
+end
+
+module Turner : sig
+ val turn : Dial.t -> int -> Dial.direction -> Dial.t * int
+ (** [turn dial direction] is the new state of the dial after turning [direction] on [dial] *)
+end = struct
+ (** [turn dial direction] is the new state of the dial after turning [direction] on [dial].
+ Raises: [dial_out_of_range] if the dial is moved to an invalid state. *)
+ let rec turn dial zero_count direction =
+ let open Dial in
+ match direction with
+ | Left 0 ->
+ (dial, zero_count)
+ | Left n ->
+ let dial' = prev dial in
+ let zero_count' =
+ if Dial.is_zero dial' then zero_count + 1 else zero_count
+ in
+ turn dial' zero_count' (Left (n - 1))
+ | Right 0 ->
+ (dial, zero_count)
+ | Right n ->
+ let dial' = next dial in
+ let zero_count' =
+ if Dial.is_zero dial' then zero_count + 1 else zero_count
+ in
+ turn dial' zero_count' (Right (n - 1))
+end
diff --git a/ocaml/lib/domain/d02p01.ml b/ocaml/lib/domain/d02p01.ml
new file mode 100644
index 0000000..725d4b1
--- /dev/null
+++ b/ocaml/lib/domain/d02p01.ml
@@ -0,0 +1,33 @@
+module ID = struct
+ (** *)
+ type t = int
+
+ let create num = num
+
+ let validate rules id = List.fold_left (fun acc r -> acc && r id) true rules
+
+ let incr = ( + ) 1
+
+ let to_int id = id
+
+ let to_string = string_of_int
+
+ let pp id = "ID=" ^ string_of_int id
+end
+
+module Range = struct
+ type t = ID.t * ID.t
+
+ let create (id1, id2) = (ID.create id1, ID.create id2)
+
+ let rec collect range = collect_range_aux [] range
+
+ and collect_range_aux lst (id1, id2) =
+ match id1 <= id2 with
+ | false ->
+ lst
+ | true ->
+ collect_range_aux (id1 :: lst) (ID.incr id1, id2)
+
+ let pp (id1, id2) = "(L" ^ ID.pp id1 ^ ", R" ^ ID.pp id2 ^ ")"
+end
diff --git a/ocaml/lib/domain/d02p01.mli b/ocaml/lib/domain/d02p01.mli
new file mode 100644
index 0000000..55665fe
--- /dev/null
+++ b/ocaml/lib/domain/d02p01.mli
@@ -0,0 +1,36 @@
+module ID : sig
+ (** [t] is a ID within a range *)
+ type t
+
+ val create : int -> t
+ (** [create num] is an ID [t] *)
+
+ val validate : (t -> bool) list -> t -> bool
+ (** [validate rules id] is [true] if the [id] passes all rule checks *)
+
+ val incr : t -> t
+ (** [incr id] is the next [id] in the sequence *)
+
+ val to_int : t -> int
+ (** [to_int id] is an integer representation of [id] *)
+
+ val to_string : t -> string
+ (** [to_string id] is an string representation of [id] *)
+
+ val pp : t -> string
+ (** [pp id] is a pretty string representing [id] *)
+end
+
+module Range : sig
+ (** [t] is a range of values *)
+ type t
+
+ val create : int * int -> t
+ (** [create (s, e)] is a range starting at [s] and ending at [e]. *)
+
+ val collect : t -> ID.t list
+ (** [collect r] is a list containing all elements within the range (inclusive). *)
+
+ val pp : t -> string
+ (** [pp r] is a pretty string representing [r] *)
+end
diff --git a/ocaml/lib/domain/d02p02.ml b/ocaml/lib/domain/d02p02.ml
new file mode 100644
index 0000000..b2b55f1
--- /dev/null
+++ b/ocaml/lib/domain/d02p02.ml
@@ -0,0 +1,29 @@
+module ID = struct
+ (** *)
+ type t = int
+
+ let create num = num
+
+ let incr = ( + ) 1
+
+ let to_string = string_of_int
+
+ let pp id = "ID=" ^ string_of_int id
+end
+
+module Range = struct
+ type t = ID.t * ID.t
+
+ let create (id1, id2) = (ID.create id1, ID.create id2)
+
+ let rec collect range = collect_range_aux [] range
+
+ and collect_range_aux lst (id1, id2) =
+ match id1 <= id2 with
+ | false ->
+ lst
+ | true ->
+ collect_range_aux (id1 :: lst) (ID.incr id1, id2)
+
+ let pp (id1, id2) = "(L" ^ ID.pp id1 ^ ", R" ^ ID.pp id2 ^ ")"
+end
diff --git a/ocaml/lib/domain/d02p02.mli b/ocaml/lib/domain/d02p02.mli
new file mode 100644
index 0000000..f918ae1
--- /dev/null
+++ b/ocaml/lib/domain/d02p02.mli
@@ -0,0 +1,30 @@
+module ID : sig
+ (** [t] is a ID within a range *)
+ type t
+
+ val create : int -> t
+ (** [create num] is an ID [t] *)
+
+ val incr : t -> t
+ (** [incr id] is the next [id] in the sequence *)
+
+ val to_string : t -> string
+ (** [to_string id] is an string representation of [id] *)
+
+ val pp : t -> string
+ (** [pp id] is a pretty string representing [id] *)
+end
+
+module Range : sig
+ (** [t] is a range of values *)
+ type t
+
+ val create : int * int -> t
+ (** [create (s, e)] is a range starting at [s] and ending at [e]. *)
+
+ val collect : t -> ID.t list
+ (** [collect r] is a list containing all elements within the range (inclusive). *)
+
+ val pp : t -> string
+ (** [pp r] is a pretty string representing [r] *)
+end
diff --git a/ocaml/lib/domain/d03p01.ml b/ocaml/lib/domain/d03p01.ml
new file mode 100644
index 0000000..1367712
--- /dev/null
+++ b/ocaml/lib/domain/d03p01.ml
@@ -0,0 +1,114 @@
+module Battery : sig
+ type t
+
+ val of_int : int -> t
+
+ val to_int : t -> int
+
+ val to_string : t -> string
+
+ val compare : t -> t -> int
+
+ val pp : t -> string
+end = struct
+ type t = int
+
+ let of_int i = i
+
+ let to_int b = b
+
+ let to_string = string_of_int
+
+ let compare = Int.compare
+
+ let pp = string_of_int
+end
+
+module BatteryBank : sig
+ type t
+
+ val highest_joltage : t -> int
+ (** [highest_joltage bb] is an integer created from the two highest
+ digits in [bb].
+ Raises: [Assert_failure] if [bb] is empty. *)
+
+ val of_string : string -> t
+
+ val of_list : Battery.t list -> t
+
+ val to_list : t -> Battery.t list
+
+ val pp : t -> string
+end = struct
+ type t = Battery.t list
+
+ let rec highest_joltage bb =
+ assert (List.length bb > 0) ;
+ match highest_joltage_aux (None, None) bb with
+ | None, None ->
+ 0
+ | Some x, Some y ->
+ int_of_string (Battery.to_string x ^ Battery.to_string y)
+ | _ ->
+ failwith "highest_joltage must be two digits"
+
+ and highest_joltage_aux acc = function
+ | [] ->
+ acc
+ | [elt] -> (
+ match acc with
+ | None, None ->
+ (Some (Battery.of_int 0), Some elt)
+ | Some first, None ->
+ (Some first, Some elt)
+ | None, Some second ->
+ (Some second, Some elt)
+ | Some first, Some second ->
+ if elt > second then (Some first, Some elt)
+ else (Some first, Some second) )
+ | h :: t ->
+ highest_joltage_aux
+ ( match acc with
+ | None, None ->
+ (Some h, None)
+ | Some first, None ->
+ let second = Battery.of_int 0 in
+ swap_if_higher first second h
+ | Some first, Some second ->
+ swap_if_higher first second h
+ | _ ->
+ failwith "this branch should be impossible" )
+ t
+
+ and swap_if_higher first second considering =
+ assert (first >= second) ;
+ let c_gt_f = Battery.compare considering first = 1 in
+ let c_gt_s = Battery.compare considering second = 1 in
+ match (c_gt_f, c_gt_s) with
+ | true, true ->
+ (Some considering, None)
+ | false, true ->
+ (Some first, Some considering)
+ | true, false ->
+ failwith "[second] should never be greater than [first]"
+ | false, false ->
+ (Some first, Some second)
+
+ let of_string s =
+ s |> String.to_seq
+ |> Seq.map (fun c -> Char.escaped c |> int_of_string |> Battery.of_int)
+ |> List.of_seq
+
+ let of_list lst = lst
+
+ let to_list bb = bb
+
+ let pp bb =
+ "[ "
+ ^ ( bb |> List.map Battery.pp
+ |> List.fold_left
+ (fun acc b ->
+ if acc = "" then b ^ ";" else Printf.sprintf "%s %s;" acc b )
+ "" )
+ ^ " ]"
+end
diff --git a/ocaml/lib/domain/d03p02.ml b/ocaml/lib/domain/d03p02.ml
new file mode 100644
index 0000000..88257d0
--- /dev/null
+++ b/ocaml/lib/domain/d03p02.ml
@@ -0,0 +1,116 @@
+module Battery : sig
+ type t
+
+ val of_int : int -> t
+
+ val to_int : t -> int
+
+ val to_string : t -> string
+
+ val compare : t -> t -> int
+
+ val pp : t -> string
+end = struct
+ type t = int
+
+ let of_int i = i
+
+ let to_int b = b
+
+ let to_string = string_of_int
+
+ let compare = Int.compare
+
+ let pp = string_of_int
+end
+
+module BatteryBank : sig
+ type t
+
+ val highest_joltage : t -> int
+ (** [highest_joltage bb] is an integer created from the two highest
+ digits in [bb].
+ Raises: [Assert_failure] if [bb] is empty. *)
+
+ val of_string : string -> t
+
+ val of_list : Battery.t list -> t
+
+ val to_list : t -> Battery.t list
+
+ val pp : t -> string
+end = struct
+ type t = Battery.t list
+
+ type highest = {one: Battery.t option; two: Battery.t option}
+
+ let rec highest_joltage bb =
+ assert (List.length bb > 0) ;
+ match highest_joltage_aux {one= None; two= None} bb with
+ | {one= None; two= None} ->
+ 0
+ | {one= Some x; two= Some y} ->
+ int_of_string (Battery.to_string x ^ Battery.to_string y)
+ | _ ->
+ failwith "highest_joltage must be two digits"
+
+ and highest_joltage_aux acc = function
+ | [] ->
+ acc
+ | [elt] -> (
+ match acc with
+ | {one= None; two= None} ->
+ {one= Some (Battery.of_int 0); two= Some elt}
+ | {one= Some first; two= None} ->
+ {one= Some first; two= Some elt}
+ | {one= None; two= Some second} ->
+ {one= Some second; two= Some elt}
+ | {one= Some first; two= Some second} ->
+ if elt > second then {one= Some first; two= Some elt}
+ else {one= Some first; two= Some second} )
+ | h :: t ->
+ highest_joltage_aux
+ ( match acc with
+ | {one= None; two= None} ->
+ {one= Some h; two= None}
+ | {one= Some first; two= None} ->
+ let second = Battery.of_int 0 in
+ swap_if_higher first second h
+ | {one= Some first; two= Some second} ->
+ swap_if_higher first second h
+ | _ ->
+ failwith "this branch should be impossible" )
+ t
+
+ and swap_if_higher first second considering =
+ assert (first >= second) ;
+ let c_gt_f = Battery.compare considering first = 1 in
+ let c_gt_s = Battery.compare considering second = 1 in
+ match (c_gt_f, c_gt_s) with
+ | true, true ->
+ {one= Some considering; two= None}
+ | false, true ->
+ {one= Some first; two= Some considering}
+ | true, false ->
+ failwith "[second] should never be greater than [first]"
+ | false, false ->
+ {one= Some first; two= Some second}
+
+ let of_string s =
+ s |> String.to_seq
+ |> Seq.map (fun c -> Char.escaped c |> int_of_string |> Battery.of_int)
+ |> List.of_seq
+
+ let of_list lst = lst
+
+ let to_list bb = bb
+
+ let pp bb =
+ "[ "
+ ^ ( bb |> List.map Battery.pp
+ |> List.fold_left
+ (fun acc b ->
+ if acc = "" then b ^ ";" else Printf.sprintf "%s %s;" acc b )
+ "" )
+ ^ " ]"
+end
diff --git a/ocaml/lib/domain/dune b/ocaml/lib/domain/dune
new file mode 100644
index 0000000..5b13d31
--- /dev/null
+++ b/ocaml/lib/domain/dune
@@ -0,0 +1,13 @@
+(library
+ (name domain)
+ (inline_tests)
+ (libraries
+ fmt
+ logs)
+ (preprocess
+ (pps
+ ppx_deriving.show
+ ppx_deriving.eq
+ ppx_deriving.ord
+ ppx_inline_test
+ ppx_expect)))
diff --git a/ocaml/lib/domain/matrix.ml b/ocaml/lib/domain/matrix.ml
new file mode 100644
index 0000000..fdf476f
--- /dev/null
+++ b/ocaml/lib/domain/matrix.ml
@@ -0,0 +1,196 @@
+module M : sig
+ type 'a t = 'a array array
+
+ val empty : int -> int -> 'a -> 'a t
+ (** [empty rows columns initial] is an empty 2D array of size [rows] * [columns]
+ where each element in the array is initialized to [initial]. *)
+
+ val of_list : 'a list list -> 'a t
+ (** [of_list lst] is a 2D array where every element in the array is an
+ element corresponds to an element of the [lst]. *)
+
+ val to_list : 'a t -> 'a list list
+ (** [to_list matrix] is a 2D list where every element in the list is an
+ element corresponds to an element of the [matrix]. *)
+
+ val of_string : string -> string t
+ (** [of_string str] is a 2D array where every character is an element. *)
+
+ val get : 'a t -> int -> int -> 'a
+ (** [get matrix x y] is an alias for [matrix.(y).(x)].
+ Requires: [x] and [y] are positive integers or 0. *)
+
+ val set : 'a t -> int -> int -> 'a -> unit
+ (** [set matrix x y v] is an alias for [matrix.(y).(x) <- v].
+ Requires: [x] and [y] are positive integers or 0. *)
+
+ val get_valid_neighbours : 'a t -> int -> int -> (int * int) list
+ (** [get_valid_neighbours matrix x y] is a list of pairs in the format
+ (x * y) that represent the valid surrounding positions of [matrix.(y).(x)].
+ Requires: [x] and [y] are positive integers or 0. *)
+
+ val pp : ('a -> string) -> 'a t -> string
+ (** [pp matrix string_of_elt] is the string representation of the [matrix]
+ where [string_of_elt] is called on each element. *)
+end = struct
+ type 'a t = 'a array array
+
+ let empty cols rows initial = Array.make_matrix cols rows initial
+
+ let of_list lst = Array.of_list @@ List.map Array.of_list lst
+
+ let to_list matrix = Array.to_list @@ Array.map Array.to_list matrix
+
+ let of_string str =
+ let lines =
+ str |> String.split_on_char '\n'
+ |> List.filter_map (fun line ->
+ let trimmed = String.trim line in
+ if trimmed <> "" then Some trimmed else None )
+ in
+ let char_lst =
+ List.map
+ (fun s -> s |> String.to_seq |> List.of_seq |> List.map Char.escaped)
+ lines
+ in
+ of_list char_lst
+
+ let get matrix x y = matrix.(y).(x)
+
+ let set matrix x y v = matrix.(y).(x) <- v
+
+ let get_valid_neighbours matrix x y =
+ let potential_neighbours =
+ [ (x - 1, y - 1)
+ ; (x, y - 1)
+ ; (x + 1, y - 1)
+ ; (x - 1, y)
+ ; (x + 1, y)
+ ; (x - 1, y + 1)
+ ; (x, y + 1)
+ ; (x + 1, y + 1) ]
+ in
+ let min_x = 0 in
+ let max_x = Array.length matrix.(y) - 1 in
+ let min_y = 0 in
+ let max_y = Array.length matrix - 1 in
+ List.filter
+ (fun (x, y) -> min_x <= x && x <= max_x && min_y <= y && y <= max_y)
+ potential_neighbours
+
+ let pp string_of_elt matrix =
+ "["
+ ^ Array.fold_left
+ (fun col_acc arr ->
+ let row =
+ "["
+ ^ Array.fold_left
+ (fun row_acc elt -> row_acc ^ string_of_elt elt ^ "; ")
+ " " arr
+ ^ "]"
+ in
+ col_acc ^ " " ^ row ^ "\n" )
+ "\n" matrix
+ ^ "]"
+end
+
+let%expect_test "matrix created from input is pretty-printed as the same matrix"
+ =
+ let example =
+ {|..@@.@@@@.
+@@@.@.@.@@
+@@@@@.@.@@
+@.@@@@..@.
+@@.@@@@.@@
+.@@@@@@@.@
+.@.@.@.@@@
+@.@@@.@@@@
+.@@@@@@@@.
+@.@.@@@.@.|}
+ in
+ print_string @@ M.pp (fun s -> s) @@ M.of_string example ;
+ [%expect
+ {|
+ [
+ [ .; .; @; @; .; @; @; @; @; .; ]
+ [ @; @; @; .; @; .; @; .; @; @; ]
+ [ @; @; @; @; @; .; @; .; @; @; ]
+ [ @; .; @; @; @; @; .; .; @; .; ]
+ [ @; @; .; @; @; @; @; .; @; @; ]
+ [ .; @; @; @; @; @; @; @; .; @; ]
+ [ .; @; .; @; .; @; .; @; @; @; ]
+ [ @; .; @; @; @; .; @; @; @; @; ]
+ [ .; @; @; @; @; @; @; @; @; .; ]
+ [ @; .; @; .; @; @; @; .; @; .; ]
+ ]
+ |}]
+
+let%expect_test "direct access of the matrix is in form matrix.(y).(x)" =
+ let example =
+ {|..@@.@@@@.
+@@@.@.@.@@
+@@@@@.@.@@
+@.@@@@..@.
+@@.@@@@.@@
+.@@@@@@@.@
+.@.@.@.@@@
+@.@@@.@@@@
+.@@@@@@@@.
+@.@.@@@.@.|}
+ in
+ let matrix = M.of_string example in
+ print_string matrix.(0).(0) ;
+ [%expect "."] ;
+ print_string matrix.(0).(1) ;
+ [%expect "."] ;
+ print_string matrix.(0).(2) ;
+ [%expect "@"] ;
+ print_string matrix.(0).(3) ;
+ [%expect "@"] ;
+ print_string matrix.(0).(4) ;
+ [%expect "."] ;
+ print_string matrix.(0).(5) ;
+ [%expect "@"] ;
+ print_string matrix.(0).(6) ;
+ [%expect "@"] ;
+ print_string matrix.(0).(7) ;
+ [%expect "@"] ;
+ print_string matrix.(0).(8) ;
+ [%expect "@"] ;
+ print_string matrix.(0).(9) ;
+ [%expect "."]
+
+let%expect_test "[M.get matrix x y] is an alias for [matrix.(y).(x)]" =
+ let example =
+ {|..@@.@@@@.
+@@@.@.@.@@
+@@@@@.@.@@
+@.@@@@..@.
+@@.@@@@.@@
+.@@@@@@@.@
+.@.@.@.@@@
+@.@@@.@@@@
+.@@@@@@@@.
+@.@.@@@.@.|}
+ in
+ let matrix = M.of_string example in
+ print_string @@ M.get matrix 0 0 ;
+ [%expect "."] ;
+ print_string @@ M.get matrix 1 0 ;
+ [%expect "."] ;
+ print_string @@ M.get matrix 2 0 ;
+ [%expect "@"] ;
+ print_string @@ M.get matrix 3 0 ;
+ [%expect "@"] ;
+ print_string @@ M.get matrix 4 0 ;
+ [%expect "."] ;
+ print_string @@ M.get matrix 5 0 ;
+ [%expect "@"] ;
+ print_string @@ M.get matrix 6 0 ;
+ [%expect "@"] ;
+ print_string @@ M.get matrix 7 0 ;
+ [%expect "@"] ;
+ print_string @@ M.get matrix 8 0 ;
+ [%expect "@"] ;
+ print_string @@ M.get matrix 9 0 ;
+ [%expect "."]
diff --git a/ocaml/lib/domain/range.ml b/ocaml/lib/domain/range.ml
new file mode 100644
index 0000000..a1bdc00
--- /dev/null
+++ b/ocaml/lib/domain/range.ml
@@ -0,0 +1,67 @@
+module M : sig
+ type t
+
+ val of_string : string -> t option
+ (** [of_string s] is a range of integers if [s] is a string in the form ["%d-%d"]
+ or [None] otherwise. *)
+
+ val collect : t -> int list
+ (** [collect range] is a fully realised range as a list.
+ Example: ["1-5" |> of_string |> collect] is the list [\[1;2;3;4;5\]].
+ Example: ["5-1" |> of_string |> collect] is the list [\[5;4;3;2;1\]].*)
+
+ val pp : t -> string
+ (** [pp range] is a pretty-printed version of the range. *)
+end = struct
+ type t = int * int
+
+ let of_string s =
+ Scanf.sscanf_opt s "%d-%d" (fun start finish -> (start, finish))
+
+ let rec collect (start, finish) =
+ let collected =
+ match Int.compare start finish with
+ | -1 ->
+ collect_range_aux Int.succ ( > ) [] (start, finish)
+ | 0 ->
+ [start]
+ | 1 ->
+ collect_range_aux Int.pred ( < ) [] (start, finish)
+ | _ ->
+ assert false
+ in
+ List.rev collected
+
+ and collect_range_aux fn cmp lst (start, finish) =
+ if cmp start finish then lst
+ else collect_range_aux fn cmp (start :: lst) (fn start, finish)
+
+ let pp (start, finish) = string_of_int start ^ "-" ^ string_of_int finish
+end
+
+let%expect_test "incrementing range" =
+ match M.of_string "1-5" with
+ | None ->
+ [%expect.unreachable]
+ | Some range ->
+ let collected = M.collect range in
+ List.iter print_int collected ;
+ [%expect "12345"]
+
+let%expect_test "decrementing range" =
+ match M.of_string "5-1" with
+ | None ->
+ [%expect.unreachable]
+ | Some range ->
+ let collected = M.collect range in
+ List.iter print_int collected ;
+ [%expect "54321"]
+
+let%expect_test "one element range" =
+ match M.of_string "1-1" with
+ | None ->
+ [%expect.unreachable]
+ | Some range ->
+ let collected = M.collect range in
+ List.iter print_int collected ;
+ [%expect "1"]
diff --git a/ocaml/lib/infrastructure/dune b/ocaml/lib/infrastructure/dune
new file mode 100644
index 0000000..b9d250f
--- /dev/null
+++ b/ocaml/lib/infrastructure/dune
@@ -0,0 +1,15 @@
+(library
+ (name infrastructure)
+ (libraries
+ application
+ domain
+ fmt
+ logs)
+ (inline_tests)
+ (preprocess
+ (pps
+ ppx_deriving.show
+ ppx_deriving.eq
+ ppx_deriving.ord
+ ppx_inline_test
+ ppx_expect)))
diff --git a/ocaml/lib/infrastructure/file.ml b/ocaml/lib/infrastructure/file.ml
new file mode 100644
index 0000000..3b49c01
--- /dev/null
+++ b/ocaml/lib/infrastructure/file.ml
@@ -0,0 +1,4 @@
+let read filename =
+ let chan = In_channel.open_text filename in
+ let content = In_channel.input_all chan in
+ In_channel.close chan ; content
diff --git a/ocaml/lib/infrastructure/file.mli b/ocaml/lib/infrastructure/file.mli
new file mode 100644
index 0000000..17d4495
--- /dev/null
+++ b/ocaml/lib/infrastructure/file.mli
@@ -0,0 +1,3 @@
+val read : string -> string
+(** [read filename] is the contents of [filename] as a string.
+ The empty string is returned if [filename] does not exist. *)
diff --git a/ocaml/lib/presentation/cli.ml b/ocaml/lib/presentation/cli.ml
new file mode 100644
index 0000000..7864e7f
--- /dev/null
+++ b/ocaml/lib/presentation/cli.ml
@@ -0,0 +1,62 @@
+(* Business logic behind CLI *)
+
+let run_solver name solver filepath =
+ let content = Infrastructure.File.read filepath in
+ match solver content () with
+ | Error msg ->
+ Logs.app (fun m -> m "[%s] Err=%s" name msg)
+ | Ok answer ->
+ Logs.app (fun m -> m "[%s] ANSWER=%d" name answer)
+
+let run_day _ = function
+ | 1 ->
+ let filepath = "../_input/day_01.txt" in
+ run_solver "D01P01" Application.D01p01.solve filepath ;
+ run_solver "D01P02" Application.D01p02.solve filepath ;
+ 0
+ | 2 ->
+ let filepath = "../_input/day_02.txt" in
+ run_solver "D02P01" Application.D02p01.solve filepath ;
+ run_solver "D02P02" Application.D02p02.solve filepath ;
+ 0
+ | 3 ->
+ let filepath = "../_input/day_03.txt" in
+ run_solver "D03P01" Application.D03p01.solve filepath ;
+ run_solver "D03P02" Application.D03p02.solve filepath ;
+ 0
+ | 4 ->
+ let filepath = "../_input/day_04.txt" in
+ run_solver "D04P01" Application.D04p01.solve filepath ;
+ run_solver "D04P02" Application.D04p02.solve filepath ;
+ 0
+ | 5 ->
+ let filepath = "../_input/day_05.txt" in
+ run_solver "D05P01" Application.D05p01.solve filepath ;
+ 0
+ | 6 ->
+ let filepath = "../_input/day_06.txt" in
+ run_solver "D06P01" Application.D06p01.solve filepath ;
+ run_solver "D06P02" Application.D06p02.solve filepath ;
+ 0
+ | _ ->
+ failwith "day not implemented"
+
+(* Command line interface *)
+
+open Cmdliner
+
+let setup_log style_renderer = Fmt_tty.setup_std_outputs ?style_renderer ()
+
+let setup_log_t = Term.(const setup_log $ Fmt_cli.style_renderer ())
+
+let day_arg =
+ let doc = "Day to run. MUST be in range of 1-12." in
+ Arg.(value & pos 0 int 1 & info [] ~docv:"DAY" ~doc)
+
+let day_t = Term.(const run_day $ setup_log_t $ day_arg)
+
+let cmd =
+ let info = Cmd.info "aoc" ~version:"0.1.0" in
+ Cmd.v info day_t
+
+let run () = Cmd.eval' cmd
diff --git a/ocaml/lib/presentation/dune b/ocaml/lib/presentation/dune
new file mode 100644
index 0000000..ae1fcb1
--- /dev/null
+++ b/ocaml/lib/presentation/dune
@@ -0,0 +1,16 @@
+(library
+ (name presentation)
+ (libraries
+ application
+ domain
+ infrastructure
+ cmdliner
+ fmt.cli
+ fmt.tty)
+ (inline_tests)
+ (preprocess
+ (pps
+ ppx_deriving.show
+ ppx_deriving.eq
+ ppx_deriving.ord
+ ppx_expect)))
diff --git a/ocaml/test/application/dune b/ocaml/test/application/dune
new file mode 100644
index 0000000..7a90f81
--- /dev/null
+++ b/ocaml/test/application/dune
@@ -0,0 +1,9 @@
+(tests
+ (names
+ test_d01p01
+ test_d01p02
+ test_d04p01)
+ (libraries
+ application
+ domain
+ alcotest))
diff --git a/ocaml/test/application/test_d01p01.ml b/ocaml/test/application/test_d01p01.ml
new file mode 100644
index 0000000..e57db01
--- /dev/null
+++ b/ocaml/test/application/test_d01p01.ml
@@ -0,0 +1,23 @@
+open Application.D01p01
+
+let example_data = {|L68
+L30
+R48
+L5
+R60
+L55
+L1
+L99
+R14
+L82|}
+
+let test_full_given_example () =
+ Alcotest.(check int)
+ "full given example" 3
+ (match solve example_data () with Error _ -> 0 | Ok ans -> ans)
+
+let () =
+ let open Alcotest in
+ run "D01p01"
+ [ ( "given-example"
+ , [test_case "full given example" `Quick test_full_given_example] ) ]
diff --git a/ocaml/test/application/test_d01p02.ml b/ocaml/test/application/test_d01p02.ml
new file mode 100644
index 0000000..bbda035
--- /dev/null
+++ b/ocaml/test/application/test_d01p02.ml
@@ -0,0 +1,23 @@
+open Application.D01p02
+
+let example_data = {|L68
+L30
+R48
+L5
+R60
+L55
+L1
+L99
+R14
+L82|}
+
+let test_full_given_example () =
+ Alcotest.(check int)
+ "full given example" 6
+ (match solve example_data () with Error _ -> 0 | Ok ans -> ans)
+
+let () =
+ let open Alcotest in
+ run "D01p02"
+ [ ( "given-example"
+ , [test_case "full given example" `Quick test_full_given_example] ) ]
diff --git a/ocaml/test/application/test_d04p01.ml b/ocaml/test/application/test_d04p01.ml
new file mode 100644
index 0000000..ffbe102
--- /dev/null
+++ b/ocaml/test/application/test_d04p01.ml
@@ -0,0 +1,24 @@
+open Application.D04p01
+
+let example =
+ {|..@@.@@@@.
+@@@.@.@.@@
+@@@@@.@.@@
+@.@@@@..@.
+@@.@@@@.@@
+.@@@@@@@.@
+.@.@.@.@@@
+@.@@@.@@@@
+.@@@@@@@@.
+@.@.@@@.@.|}
+
+let test_input_to_matrix have want () =
+ Alcotest.(check int) "number of rolls of paper that are accessible" want
+ @@ match solve have () with Error _ -> 0 | Ok ans -> ans
+
+let () =
+ let open Alcotest in
+ run "D04p01"
+ [ ( "given-example"
+ , [test_case "full example" `Quick @@ test_input_to_matrix example 13] )
+ ]
diff --git a/ocaml/test/domain/dune b/ocaml/test/domain/dune
new file mode 100644
index 0000000..1364288
--- /dev/null
+++ b/ocaml/test/domain/dune
@@ -0,0 +1,11 @@
+(tests
+ (names
+ test_d01p01
+ test_d01p02
+ test_d03p01)
+ (libraries
+ domain
+ alcotest
+ qcheck-core
+ qcheck-alcotest)
+ (modes byte exe))
diff --git a/ocaml/test/domain/test_d01p01.ml b/ocaml/test/domain/test_d01p01.ml
new file mode 100644
index 0000000..23404f6
--- /dev/null
+++ b/ocaml/test/domain/test_d01p01.ml
@@ -0,0 +1,29 @@
+open Domain.D01p01
+
+let test_start () =
+ Alcotest.(check int) "incorrect starting state" 50 (Dial.to_int Dial.start)
+
+let test_turn desc exp dir =
+ Alcotest.(check int) desc exp (Dial.to_int (Dial.turn Dial.start dir))
+
+let test_turn_left_one () =
+ test_turn "incorrect result when turning left one step" 49 (Dial.Left 1)
+
+let test_turn_left_100 () =
+ test_turn "incorrect result when turning left 100 steps" 50 (Dial.Left 100)
+
+let test_turn_right_one () =
+ test_turn "incorrect result when turning right one step" 51 (Dial.Right 1)
+
+let test_turn_right_100 () =
+ test_turn "incorrect result when turning right 100 steps" 50 (Dial.Right 100)
+
+let () =
+ let open Alcotest in
+ run "D01p01"
+ [ ("initial-state", [test_case "starting state" `Quick test_start])
+ ; ( "turning-dial"
+ , [ test_case "turning left one step" `Quick test_turn_left_one
+ ; test_case "turning right one step" `Quick test_turn_right_one
+ ; test_case "turning left 100 steps" `Quick test_turn_left_100
+ ; test_case "turning right 100 steps" `Quick test_turn_right_100 ] ) ]
diff --git a/ocaml/test/domain/test_d01p02.ml b/ocaml/test/domain/test_d01p02.ml
new file mode 100644
index 0000000..98c06c3
--- /dev/null
+++ b/ocaml/test/domain/test_d01p02.ml
@@ -0,0 +1,41 @@
+open Domain.D01p02
+
+let test_start () =
+ Alcotest.(check int) "incorrect starting state" 50 (Dial.to_int Dial.start)
+
+let test_turn errmsg exp dir =
+ Alcotest.(check int)
+ errmsg exp
+ (Dial.to_int (fst (Turner.turn Dial.start 0 dir)))
+
+let test_turn_left_one () =
+ test_turn "incorrect result when turning left one step" 49 (Dial.Left 1)
+
+let test_turn_left_100 () =
+ test_turn "incorrect result when turning left 100 steps" 50 (Dial.Left 100)
+
+let test_turn_right_one () =
+ test_turn "incorrect result when turning right one step" 51 (Dial.Right 1)
+
+let test_turn_right_100 () =
+ test_turn "incorrect result when turning right 100 steps" 50 (Dial.Right 100)
+
+let test_zero_count errmsg exp dir =
+ Alcotest.(check int) errmsg exp (snd (Turner.turn Dial.start 0 dir))
+
+let test_zero_count_right_1000 () =
+ test_zero_count "zero MUST be counted each time it is passed" 10
+ (Dial.Right 1000)
+
+let () =
+ let open Alcotest in
+ run "D01p02"
+ [ ("initial-state", [test_case "starting state" `Quick test_start])
+ ; ( "turning-dial-regression"
+ , [ test_case "turning left one step" `Quick test_turn_left_one
+ ; test_case "turning right one step" `Quick test_turn_right_one
+ ; test_case "turning left 100 steps" `Quick test_turn_left_100
+ ; test_case "turning right 100 steps" `Quick test_turn_right_100 ] )
+ ; ( "zero-count"
+ , [ test_case "zero count after turning right 1000 steps" `Quick
+ test_zero_count_right_1000 ] ) ]
diff --git a/ocaml/test/domain/test_d03p01.ml b/ocaml/test/domain/test_d03p01.ml
new file mode 100644
index 0000000..6a2b3cf
--- /dev/null
+++ b/ocaml/test/domain/test_d03p01.ml
@@ -0,0 +1,857 @@
+open Domain.D03p01
+
+let test_highest_joltage have want () =
+ Alcotest.(check int)
+ "highest joltage in battery bank" want
+ (BatteryBank.of_string have |> BatteryBank.highest_joltage)
+
+let largest_digit_always_in_highest_joltage =
+ QCheck2.Test.make ~count:1000
+ ~name:"highest joltage always has contains the largest digit"
+ ~print:(fun s -> s)
+ QCheck2.Gen.(string_of @@ char_range '1' '9')
+ (fun have ->
+ QCheck2.assume (String.length have > 2) ;
+ let got = BatteryBank.of_string have |> BatteryBank.highest_joltage in
+ let first = got / 10 and second = got mod 10 in
+ let want =
+ String.fold_left
+ (fun acc chr -> if acc < chr then chr else acc)
+ '0' have
+ |> Char.escaped |> int_of_string
+ in
+ first = want || second = want )
+
+let () =
+ let open Alcotest in
+ run "D03p01"
+ [ ( "example BatteryBank.highest_joltage"
+ , [ test_case "example line 1" `Quick
+ (test_highest_joltage "987654321111111" 98)
+ ; test_case "example line 2" `Quick
+ (test_highest_joltage "811111111111119" 89)
+ ; test_case "example line 3" `Quick
+ (test_highest_joltage "234234234234278" 78)
+ ; test_case "example line 4" `Quick
+ (test_highest_joltage "818181911112111" 92) ] )
+ ; ( "real BatteryBank.highest_joltage"
+ , [ test_case "real line 001" `Quick
+ (test_highest_joltage
+ "3434845634454364546334335333448443354324533545443235414334477424442444346844344244444434445333344314"
+ 88 )
+ ; test_case "real line 002" `Quick
+ (test_highest_joltage
+ "4644333464433434855275434364342435544972433324444444432243354936362863356444434344443333433425344432"
+ 99 )
+ ; test_case "real line 003" `Quick
+ (test_highest_joltage
+ "3475855376536553445244645449948436834663673457444335454425154463544745554434526565475244677445573464"
+ 99 )
+ ; test_case "real line 004" `Quick
+ (test_highest_joltage
+ "2281212713222421222222213222212222221223122222212125222722232132222251212212322222222722312232333312"
+ 87 )
+ ; test_case "real line 005" `Quick
+ (test_highest_joltage
+ "4483335423223456243275328893923136663523559445363393353333233337733323263852953335475336578422333454"
+ 99 )
+ ; test_case "real line 006" `Quick
+ (test_highest_joltage
+ "3242323333312323243333333353413314243333232242233323431332332353323333353333333332443515323433322274"
+ 74 )
+ ; test_case "real line 007" `Quick
+ (test_highest_joltage
+ "2333332242424335244422323333113323312234133433323243344324444233324245126142444324422445113241224322"
+ 65 )
+ ; test_case "real line 008" `Quick
+ (test_highest_joltage
+ "5154432634655462574764324542474445554653643456344345657233718556457665426344257554246426445655364424"
+ 87 )
+ ; test_case "real line 009" `Quick
+ (test_highest_joltage
+ "2333128322322632533324333135293331312321323332214333232434433322473232223344341214372234132223223343"
+ 97 )
+ ; test_case "real line 010" `Quick
+ (test_highest_joltage
+ "4334244423552542164444513547496574512523344184674374558444434436645643164524326453333335444434455453"
+ 98 )
+ ; test_case "real line 011" `Quick
+ (test_highest_joltage
+ "4132222247423242222232632242222124423421222271231244222212222222554221311221262314222222224253442337"
+ 77 )
+ ; test_case "real line 012" `Quick
+ (test_highest_joltage
+ "2224322221122233225272222222442223222222271115422222121223262743222224222322422224123122262323222821"
+ 82 )
+ ; test_case "real line 013" `Quick
+ (test_highest_joltage
+ "6723822256923221233223334322457233343332383332523433343323228646232524535432233323314353333227243323"
+ 98 )
+ ; test_case "real line 014" `Quick
+ (test_highest_joltage
+ "6115546238253222343552462122324244122555232412242231255242342542222232432524132213523232121212324545"
+ 86 )
+ ; test_case "real line 015" `Quick
+ (test_highest_joltage
+ "6463573733747321131532311243414374652372276761664731354417577566137677557472577677776125327145332589"
+ 89 )
+ ; test_case "real line 016" `Quick
+ (test_highest_joltage
+ "3754415413732333338423242374334432153868351274433435343563444545253375644356474264434423432633233432"
+ 88 )
+ ; test_case "real line 017" `Quick
+ (test_highest_joltage
+ "5444434434444444443644344444454454451442423444454444444344443433464444435453445443443354555334454454"
+ 66 )
+ ; test_case "real line 018" `Quick
+ (test_highest_joltage
+ "2322232322322342121231422323212136222323332232223332423222231222323222323213211222124112122223323222"
+ 64 )
+ ; test_case "real line 019" `Quick
+ (test_highest_joltage
+ "2123222121233313232324221212131322222312114122122222223331524242222223522421122132314232232221325212"
+ 55 )
+ ; test_case "real line 020" `Quick
+ (test_highest_joltage
+ "4395244125441654554734234542525455274423553314354552543244444237453434822334223553453354325435235232"
+ 98 )
+ ; test_case "real line 021" `Quick
+ (test_highest_joltage
+ "2253424214422444223313135223232832334333553541324423242333333332422343235234313221222522523223223324"
+ 85 )
+ ; test_case "real line 022" `Quick
+ (test_highest_joltage
+ "5235226542352363765362222242266353124241222253326335131251514362226422434463674353246334124656282122"
+ 82 )
+ ; test_case "real line 023" `Quick
+ (test_highest_joltage
+ "2232123624442243412224246433221223412221211333362434222314122321222374432142634122244234226424224222"
+ 76 )
+ ; test_case "real line 024" `Quick
+ (test_highest_joltage
+ "3422335122727323322362432313322322322223332332223816131326233213327243223323343212331236353226232321"
+ 87 )
+ ; test_case "real line 025" `Quick
+ (test_highest_joltage
+ "4331335233332333423433313313173432431333333532233333433343363343333332336433333237323332253433443333"
+ 77 )
+ ; test_case "real line 026" `Quick
+ (test_highest_joltage
+ "4447448444443444434444443243543534134744452133424233431341466844433346243413344384231154343384832444"
+ 88 )
+ ; test_case "real line 027" `Quick
+ (test_highest_joltage
+ "2425565264165457455876521452237584855214342125728464762555858525255422733375855552537425835156285135"
+ 88 )
+ ; test_case "real line 028" `Quick
+ (test_highest_joltage
+ "1331226211234222222312211322123232233513242526221423223222232214322223122232333222282842423352327112"
+ 88 )
+ ; test_case "real line 029" `Quick
+ (test_highest_joltage
+ "3223434225632352425252225227722722534655252243423421222422525141365526221342142264132653472224125223"
+ 77 )
+ ; test_case "real line 030" `Quick
+ (test_highest_joltage
+ "6737524344344356458523434443443144444264443348734644444644541613334445654733443544253434843553348938"
+ 98 )
+ ; test_case "real line 031" `Quick
+ (test_highest_joltage
+ "3354422734545554524252254222935229552422513243152263456254352212241422142433745251122447413225375454"
+ 99 )
+ ; test_case "real line 032" `Quick
+ (test_highest_joltage
+ "2264222763222232222612274362233123232631142294332533623322433223214212132333126222163271553323113546"
+ 97 )
+ ; test_case "real line 033" `Quick
+ (test_highest_joltage
+ "3224455231554212335327234452322334222224355323335331221333422324235123435122325533513215236323223567"
+ 77 )
+ ; test_case "real line 034" `Quick
+ (test_highest_joltage
+ "3364433535332344433338434131353453131351342344343443434434354345544614344346424444464434454542463163"
+ 86 )
+ ; test_case "real line 035" `Quick
+ (test_highest_joltage
+ "3334311333353143563633334222343222333252653351532534233523263233265333333263352353553235353353353333"
+ 66 )
+ ; test_case "real line 036" `Quick
+ (test_highest_joltage
+ "2341414245234322423322241415434452323345544444442373441644233534541453435433235452441251543332443224"
+ 76 )
+ ; test_case "real line 037" `Quick
+ (test_highest_joltage
+ "2334222122362114522212222223212342234225232414234232252812322221321123236322322233153222246262451321"
+ 86 )
+ ; test_case "real line 038" `Quick
+ (test_highest_joltage
+ "3222213324143222521232511323442333353115354312453322323233245125121212335352213422325232423442113222"
+ 55 )
+ ; test_case "real line 039" `Quick
+ (test_highest_joltage
+ "4242234421454245624314342344231425552454131112356244265233623318251442244258454232422222544212344274"
+ 88 )
+ ; test_case "real line 040" `Quick
+ (test_highest_joltage
+ "1212211622212622232222232221122222234222325251233221113223526233333222121362242225251522222122222412"
+ 66 )
+ ; test_case "real line 041" `Quick
+ (test_highest_joltage
+ "1224243142354932247624132223442221243261122146133223131475431216123424343342231246215132422542144273"
+ 97 )
+ ; test_case "real line 042" `Quick
+ (test_highest_joltage
+ "2492338342383524333242442252453454334643633243343233244463532534334332436821235243322432152236224423"
+ 98 )
+ ; test_case "real line 043" `Quick
+ (test_highest_joltage
+ "7676556546555455945548897555565858677556364454655765687644455555553584455358535855555564435524555636"
+ 99 )
+ ; test_case "real line 044" `Quick
+ (test_highest_joltage
+ "4325352353133335234532433545335543523225232353214622764538523233325246333312335343542333525343352832"
+ 88 )
+ ; test_case "real line 045" `Quick
+ (test_highest_joltage
+ "3645465557845534765556456438425473478555654328555485735454543757533734857854555473533459558165456246"
+ 98 )
+ ; test_case "real line 046" `Quick
+ (test_highest_joltage
+ "4112321222222221222222222212221112212222222213112212233222211122121222223233221122122112222222312222"
+ 43 )
+ ; test_case "real line 047" `Quick
+ (test_highest_joltage
+ "3443534216134325237234252533391415524526262523352143237242332332332422332332347132554552354213163423"
+ 97 )
+ ; test_case "real line 048" `Quick
+ (test_highest_joltage
+ "3233233443433352233334353335313474353433313433333332332445324732323333325354333345123333238333323343"
+ 84 )
+ ; test_case "real line 049" `Quick
+ (test_highest_joltage
+ "2133222332312231142332122412332211233373432222221234222233216312322233333313213322233312422332412321"
+ 76 )
+ ; test_case "real line 050" `Quick
+ (test_highest_joltage
+ "2323442214442251652225322254455617413464434422211515255422722166366711262534452258439143226431422432"
+ 96 )
+ ; test_case "real line 051" `Quick
+ (test_highest_joltage
+ "3112243242223312224423222512422243342333412424122234223224524214241424224424422232231222421343123313"
+ 55 )
+ ; test_case "real line 052" `Quick
+ (test_highest_joltage
+ "2524625652353456554546431455532426742414535225832464326233444242524314463569626432264234665412452611"
+ 96 )
+ ; test_case "real line 053" `Quick
+ (test_highest_joltage
+ "2623224224152132332532122544116272213711472512222547253263227634222862855322152332253264721222222626"
+ 88 )
+ ; test_case "real line 054" `Quick
+ (test_highest_joltage
+ "3413862324452342446454316341334346457442436472446424131344156965335463455345446483132552535546336316"
+ 98 )
+ ; test_case "real line 055" `Quick
+ (test_highest_joltage
+ "5354344333214443434343442334124434322442443544435334333334544442334442344432244334534423233345433234"
+ 55 )
+ ; test_case "real line 056" `Quick
+ (test_highest_joltage
+ "2322333211233223212213111213123244423222222232323332222322212221323323433322435233232371222222214231"
+ 74 )
+ ; test_case "real line 057" `Quick
+ (test_highest_joltage
+ "9453214484824262259522856649224244244222211592233334238214222322284346591381262187242644522629832223"
+ 99 )
+ ; test_case "real line 058" `Quick
+ (test_highest_joltage
+ "1121224242322123123163448337222562512232262643222323252252223543512425222422222223142233221426365461"
+ 87 )
+ ; test_case "real line 059" `Quick
+ (test_highest_joltage
+ "1815612518244643515355768361121163421324442178622168752374328877665672243587235441472714316317588369"
+ 89 )
+ ; test_case "real line 060" `Quick
+ (test_highest_joltage
+ "3655264263765432665494374452246362724532226675513545216322644525426252338564464426261732379225267837"
+ 99 )
+ ; test_case "real line 061" `Quick
+ (test_highest_joltage
+ "8334743433235341445622143424833423324734232433333132424333763435142253533364333734887344542326984333"
+ 98 )
+ ; test_case "real line 062" `Quick
+ (test_highest_joltage
+ "6333532332533333262332333332623323523436233333331335433333433323333133233344333378336333352223333232"
+ 86 )
+ ; test_case "real line 063" `Quick
+ (test_highest_joltage
+ "3366253633632333372333133255934725232323263352222274223213323533141234362324236915143323333136434233"
+ 99 )
+ ; test_case "real line 064" `Quick
+ (test_highest_joltage
+ "5322222323283352224135234432224523225232853242442423241544234623253324537257221444443224442542343223"
+ 88 )
+ ; test_case "real line 065" `Quick
+ (test_highest_joltage
+ "2322722344168237322333228462433332227336523244323265343321333443434132333343333492223332435724342464"
+ 97 )
+ ; test_case "real line 066" `Quick
+ (test_highest_joltage
+ "5554845434744255655687844665866661355767464556478385448756657654567285577634695565556616935646635793"
+ 99 )
+ ; test_case "real line 067" `Quick
+ (test_highest_joltage
+ "6212623112233212232222321423231243253343231323432932323232222135322122237222134283242231231725212252"
+ 98 )
+ ; test_case "real line 068" `Quick
+ (test_highest_joltage
+ "3224333333132232431414376222443332422422322223342332333335333224256832733233433622542324333442322953"
+ 95 )
+ ; test_case "real line 069" `Quick
+ (test_highest_joltage
+ "1495248962734145243214425252551493515624456535246426262254542145634343245522513375341266233457656414"
+ 99 )
+ ; test_case "real line 070" `Quick
+ (test_highest_joltage
+ "3333243514122577341633436443431333234233434851333432454335423136533335534834354554332443233443334233"
+ 88 )
+ ; test_case "real line 071" `Quick
+ (test_highest_joltage
+ "6775435378454755775937393515454694792745716493599675572454465734647574159354586497336464643987566774"
+ 99 )
+ ; test_case "real line 072" `Quick
+ (test_highest_joltage
+ "3343255423334231444544444344434343234333434343544344434524254354244544424452234444333442244454345533"
+ 55 )
+ ; test_case "real line 073" `Quick
+ (test_highest_joltage
+ "5644753727566756775567545585754676596645654664776746756665476666666556676466474667695655766564656657"
+ 99 )
+ ; test_case "real line 074" `Quick
+ (test_highest_joltage
+ "4644226464352276354314244224452454342423454622134648332554423425336244425432349642233521244367349342"
+ 99 )
+ ; test_case "real line 075" `Quick
+ (test_highest_joltage
+ "3433335543445753633424571853934424332643345643544683232351242335433242324425464344622364532352533437"
+ 98 )
+ ; test_case "real line 076" `Quick
+ (test_highest_joltage
+ "4332333343143333333343333474334265443333334214354551322433433243313234333232425433333443443433526343"
+ 76 )
+ ; test_case "real line 077" `Quick
+ (test_highest_joltage
+ "4353646245233554434324342343533543343355335434535413535233331345445333253655446433354372556332324562"
+ 76 )
+ ; test_case "real line 078" `Quick
+ (test_highest_joltage
+ "4325222211212322234523222222252532247122313123373542232242121352322276216822338182262236346922722222"
+ 97 )
+ ; test_case "real line 079" `Quick
+ (test_highest_joltage
+ "2322565523552147152522424415733545252325565263526635227432457357155822584542343524555344525552453543"
+ 88 )
+ ; test_case "real line 080" `Quick
+ (test_highest_joltage
+ "2536964744575897532331584947348473443439336365524543425393413335486443645364675453424543335446555498"
+ 99 )
+ ; test_case "real line 081" `Quick
+ (test_highest_joltage
+ "2222213441222221121213232422422313244252223442212222212522422224224223231124422422243512212213212342"
+ 55 )
+ ; test_case "real line 082" `Quick
+ (test_highest_joltage
+ "2226345127525427354274576964233625784361152623467725726752473254364464625478727754525322265247112673"
+ 98 )
+ ; test_case "real line 083" `Quick
+ (test_highest_joltage
+ "3333323323439434233332343233242333523333333322324323333432333327112333312433423132233322331432333323"
+ 97 )
+ ; test_case "real line 084" `Quick
+ (test_highest_joltage
+ "3333434534333424243331497446456437324756447475342274464232844334833254353445422233546141144533344344"
+ 98 )
+ ; test_case "real line 085" `Quick
+ (test_highest_joltage
+ "3696167353668222145864766623627542566388745676275342437244534257633357663535464676463344222466874743"
+ 98 )
+ ; test_case "real line 086" `Quick
+ (test_highest_joltage
+ "2221243212226232221214225213222212321222322222222222321222213152222212232613223422294232232522123112"
+ 95 )
+ ; test_case "real line 087" `Quick
+ (test_highest_joltage
+ "2322223332222333222222322222224233422221212122124224222442422313242232322322322132221232323232224243"
+ 44 )
+ ; test_case "real line 088" `Quick
+ (test_highest_joltage
+ "3222473241422323343212233253252455323361331212425233323243431532523435223231512332242372313333353233"
+ 77 )
+ ; test_case "real line 089" `Quick
+ (test_highest_joltage
+ "5423224242232312223222322332344353122123124433226352334261254221453422322421343522321125621432243323"
+ 66 )
+ ; test_case "real line 090" `Quick
+ (test_highest_joltage
+ "8363335374334533365422233335351363332233354423323122333333234434223223333335635423442344353624352436"
+ 87 )
+ ; test_case "real line 091" `Quick
+ (test_highest_joltage
+ "3363445765667922559186654477737547964555446686459554733657654766566655765548166378754867769354765564"
+ 99 )
+ ; test_case "real line 092" `Quick
+ (test_highest_joltage
+ "6533212432223424334451322232232232443253324232723281475224244243554253222475635635422425222222222428"
+ 88 )
+ ; test_case "real line 093" `Quick
+ (test_highest_joltage
+ "1223233332437322323134322352329283423333133433331533223722733221342425515333543413253332323254332144"
+ 98 )
+ ; test_case "real line 094" `Quick
+ (test_highest_joltage
+ "5264162233623746665261784343112243254221441535263624414528422142852361426326676128424382222772232624"
+ 88 )
+ ; test_case "real line 095" `Quick
+ (test_highest_joltage
+ "1222232222322322212232232122232322222331222223323232222322212223221222223122132222222226222311212222"
+ 63 )
+ ; test_case "real line 096" `Quick
+ (test_highest_joltage
+ "2447938353242561333434522543524366633338233372322372443172638272362414623239322222214322338432223233"
+ 99 )
+ ; test_case "real line 097" `Quick
+ (test_highest_joltage
+ "4884849914558496499986874887474387879218486546454756422444585589669397378725447645287563673434787636"
+ 99 )
+ ; test_case "real line 098" `Quick
+ (test_highest_joltage
+ "1532312355432455353342333225253523373245346353222322243344325423634342343333235353462335235134532214"
+ 76 )
+ ; test_case "real line 099" `Quick
+ (test_highest_joltage
+ "3633115526253432364354661645232316365446622332424352521423265511535341213222665264163264143532241789"
+ 89 )
+ ; test_case "real line 100" `Quick
+ (test_highest_joltage
+ "1333723464325672225363386433359834138466234942326568553333727332463624757374234473543533336465426327"
+ 99 )
+ ; test_case "real line 101" `Quick
+ (test_highest_joltage
+ "8325733945242354443883554334669457355644376375344222347347435474443336786843454755374733473222713744"
+ 99 )
+ ; test_case "real line 102" `Quick
+ (test_highest_joltage
+ "3836536232345525523424516573323367655557875645136254455455455555237343456565656555565433564652375615"
+ 88 )
+ ; test_case "real line 103" `Quick
+ (test_highest_joltage
+ "1222242222222222222221222222222222222222222222214222242212122221223267222222112122222222222221223723"
+ 77 )
+ ; test_case "real line 104" `Quick
+ (test_highest_joltage
+ "2122322223222221341225222331152222221222222221222222222212223231322221121224122222222221222322221221"
+ 55 )
+ ; test_case "real line 105" `Quick
+ (test_highest_joltage
+ "2623431563346525416242365622564363427642123425242364433596524126242622114536213245235652674623263514"
+ 97 )
+ ; test_case "real line 106" `Quick
+ (test_highest_joltage
+ "2232456232323314322243433252432326454256232632225662224221422412232162235555244527334626356269224262"
+ 96 )
+ ; test_case "real line 107" `Quick
+ (test_highest_joltage
+ "2223224223121316212222123214122222321222232222214222223322235255232321232421222233223123422332226121"
+ 66 )
+ ; test_case "real line 108" `Quick
+ (test_highest_joltage
+ "2232222211641215121113421342225332233222241313112621422223224321232326222212352222222222322222232142"
+ 66 )
+ ; test_case "real line 109" `Quick
+ (test_highest_joltage
+ "3622224322123323543363333123532223522232121233221223212255624222224222331222232221222223332412252226"
+ 66 )
+ ; test_case "real line 110" `Quick
+ (test_highest_joltage
+ "2133111331232311212423441441423422333433421444124423221341431323413244441112422424242124214412256789"
+ 89 )
+ ; test_case "real line 111" `Quick
+ (test_highest_joltage
+ "4324434435333333333334332245243563333343356334334344235334322234145353323234352455374364553333643433"
+ 76 )
+ ; test_case "real line 112" `Quick
+ (test_highest_joltage
+ "3644734534756433422432434356624552253427466225337526564429422436344444454366324544253633541443353244"
+ 96 )
+ ; test_case "real line 113" `Quick
+ (test_highest_joltage
+ "2464324635364431443472344454334444343433464546454374434442423242443446444423526443551333243353834444"
+ 84 )
+ ; test_case "real line 114" `Quick
+ (test_highest_joltage
+ "5281642433245222222262522922521227221222224253247163523362225212277164151212258322522132327215231222"
+ 98 )
+ ; test_case "real line 115" `Quick
+ (test_highest_joltage
+ "4235325226444533343235413742346432342321545545344233233255443834433325423232343235231352342354622553"
+ 86 )
+ ; test_case "real line 116" `Quick
+ (test_highest_joltage
+ "8733361224233225222222426322442381855225483432261538256252524243235345825178218568965722348222331146"
+ 98 )
+ ; test_case "real line 117" `Quick
+ (test_highest_joltage
+ "3464344445243346232343434322224234534583235444432447445343449318343349436245884322482543363363441674"
+ 99 )
+ ; test_case "real line 118" `Quick
+ (test_highest_joltage
+ "7343334231474233525283342422364511453342252222452242451442434432112333432342315946241522245542222535"
+ 96 )
+ ; test_case "real line 119" `Quick
+ (test_highest_joltage
+ "4446143264553864474334554447535535453446566233244444636634565645448343225453554444454544364443454574"
+ 88 )
+ ; test_case "real line 120" `Quick
+ (test_highest_joltage
+ "2422842224224342322263252223333244522315113324152212372221342231322244231232122332222112227625141222"
+ 87 )
+ ; test_case "real line 121" `Quick
+ (test_highest_joltage
+ "4142122222422221222412122222212544522231222223211322362222321222222134432222122542122262223523223222"
+ 66 )
+ ; test_case "real line 122" `Quick
+ (test_highest_joltage
+ "4572333542142432343332342132323444433243433454242321332323354233213344233432132323534221333344323134"
+ 75 )
+ ; test_case "real line 123" `Quick
+ (test_highest_joltage
+ "1333235433322333324332333323332332233112331232333422333333333235333334322322333323333333323333423735"
+ 75 )
+ ; test_case "real line 124" `Quick
+ (test_highest_joltage
+ "5563657356454636674393696655654548835664676555834625756663576376694963475267652644487768866556366963"
+ 99 )
+ ; test_case "real line 125" `Quick
+ (test_highest_joltage
+ "4563325436233211733422223372423424234244425452243133154466242445642433343224441473324372257264523657"
+ 77 )
+ ; test_case "real line 126" `Quick
+ (test_highest_joltage
+ "2323232163222222141214222333122231122223522528112323222223222322213223223322333322221323333333322311"
+ 83 )
+ ; test_case "real line 127" `Quick
+ (test_highest_joltage
+ "2122222234242122427222222226222332211222112222222211222421121538222231222313221435322311232222212242"
+ 85 )
+ ; test_case "real line 128" `Quick
+ (test_highest_joltage
+ "5334253235515121222383422225322212542617232476242645322652351543223434635325223237112254433523422324"
+ 87 )
+ ; test_case "real line 129" `Quick
+ (test_highest_joltage
+ "1221223532146222222223126712218222123621122123217221212222322221232621162221122222222222122561122292"
+ 92 )
+ ; test_case "real line 130" `Quick
+ (test_highest_joltage
+ "1253352424255222142243514444444457255254363328422562213135526512123451523416645352425253125322532244"
+ 86 )
+ ; test_case "real line 131" `Quick
+ (test_highest_joltage
+ "7463373334646423355538441534433248593244576575237356213443653633273543376463332733333713232643554552"
+ 97 )
+ ; test_case "real line 132" `Quick
+ (test_highest_joltage
+ "5752323474332773249344434354245224321731245442414442735261654665534764636524293424214244444723343251"
+ 99 )
+ ; test_case "real line 133" `Quick
+ (test_highest_joltage
+ "2629438637852334643554943482775533446535956343762325623453474525365936398476244597355454656623443534"
+ 99 )
+ ; test_case "real line 134" `Quick
+ (test_highest_joltage
+ "4535534443563253332234233434455543533235333233223442233533235354443333733435443343553354434434423313"
+ 75 )
+ ; test_case "real line 135" `Quick
+ (test_highest_joltage
+ "3432223113423243423323513443123332315562232532523473232212343355364222323234236322343323225332327733"
+ 77 )
+ ; test_case "real line 136" `Quick
+ (test_highest_joltage
+ "1425245335243124322324153454554115125214321244121112544441331342553344311545421455324424455134156789"
+ 89 )
+ ; test_case "real line 137" `Quick
+ (test_highest_joltage
+ "5132222223252224223252622414436422231243232252214255323634524534432233273222325435334426442424422223"
+ 76 )
+ ; test_case "real line 138" `Quick
+ (test_highest_joltage
+ "3466444436255433325573352262554776625733275447321246237454234455234514752545552532447415643534223435"
+ 77 )
+ ; test_case "real line 139" `Quick
+ (test_highest_joltage
+ "3421133222332332322343231232212233662233221312321122224431322313262333313232332243223222233232344323"
+ 66 )
+ ; test_case "real line 140" `Quick
+ (test_highest_joltage
+ "3333523333232364333363555455431345243544566532455235335641243353444455452225735366346344243452243545"
+ 76 )
+ ; test_case "real line 141" `Quick
+ (test_highest_joltage
+ "2152354211122622723432615322212523224222225212363121124451232267516325421252122342152252424221452215"
+ 77 )
+ ; test_case "real line 142" `Quick
+ (test_highest_joltage
+ "1336123121333343222232333212232411534322333233223332323322234351332222331333333233332312233311232332"
+ 65 )
+ ; test_case "real line 143" `Quick
+ (test_highest_joltage
+ "2132121232233134232221223353223332232233322251223526132523323234323223335333222534212113232222523324"
+ 65 )
+ ; test_case "real line 144" `Quick
+ (test_highest_joltage
+ "5642633765534226222523623324323443222227261332774392232227443393254412356235358631224418312923222245"
+ 99 )
+ ; test_case "real line 145" `Quick
+ (test_highest_joltage
+ "8253442425732222332238822732425826242143236154326323242443342334348324234372622433223442523324133216"
+ 88 )
+ ; test_case "real line 146" `Quick
+ (test_highest_joltage
+ "2232271226257223212222421324222344224222141222322222222222222122224238422414316422323222222322127221"
+ 87 )
+ ; test_case "real line 147" `Quick
+ (test_highest_joltage
+ "6446315487414253623366432633565653444341454233666544632655534656536436114486263961512465435546514714"
+ 97 )
+ ; test_case "real line 148" `Quick
+ (test_highest_joltage
+ "3452383332535514435334232222355452522134243132532453384334525234222233545425333315332323214145535341"
+ 88 )
+ ; test_case "real line 149" `Quick
+ (test_highest_joltage
+ "3232222223322222122123232312331213231232227213222242313323222122223322222322313334222122231232221331"
+ 74 )
+ ; test_case "real line 150" `Quick
+ (test_highest_joltage
+ "3424332322222132232233231123121313223223263323323222342313211312222322221122422223222112232122222222"
+ 64 )
+ ; test_case "real line 151" `Quick
+ (test_highest_joltage
+ "4688454513726334654565476674454433554682654943685644463375763364523442437445697396743545599524274524"
+ 99 )
+ ; test_case "real line 152" `Quick
+ (test_highest_joltage
+ "4161223222271222942312171248122235227112222552223422326624121622234732522212222232212366825223253121"
+ 98 )
+ ; test_case "real line 153" `Quick
+ (test_highest_joltage
+ "6332465533333522555433324356221543352235533566333253334365565323363324552432332233335334227623136325"
+ 76 )
+ ; test_case "real line 154" `Quick
+ (test_highest_joltage
+ "3222431333842318323144411327346223122314332323123555122522333732335233333352312534852241433483632313"
+ 88 )
+ ; test_case "real line 155" `Quick
+ (test_highest_joltage
+ "2112121212253222222943424322422222512112212312422234143313232222122122222122123122132137327212242242"
+ 97 )
+ ; test_case "real line 156" `Quick
+ (test_highest_joltage
+ "3222532316332422221212223221522223111252221221322212217222213222221221211241222312222121222133222221"
+ 74 )
+ ; test_case "real line 157" `Quick
+ (test_highest_joltage
+ "2323332223344321223143245373113722211223122226227822325314633213361321445632321823462822337223222445"
+ 88 )
+ ; test_case "real line 158" `Quick
+ (test_highest_joltage
+ "1828425212144411422233221323218468423217466223581342222211242262211332238228121222224225122212222232"
+ 88 )
+ ; test_case "real line 159" `Quick
+ (test_highest_joltage
+ "3556334454665634545845243453255635572675157347763125427524341342644935377436635334453443684644784434"
+ 98 )
+ ; test_case "real line 160" `Quick
+ (test_highest_joltage
+ "2645662623422533225421215431512343431254242214262232525122522244211222145253265353435325571322222253"
+ 75 )
+ ; test_case "real line 161" `Quick
+ (test_highest_joltage
+ "2224222242122222222232211112222314222222222234412242326322512423222222232623322222122322112321211122"
+ 66 )
+ ; test_case "real line 162" `Quick
+ (test_highest_joltage
+ "3323122223222222333232562133233231243322232322321321232232332322335422322313322223123222222334233322"
+ 65 )
+ ; test_case "real line 163" `Quick
+ (test_highest_joltage
+ "3221132223233221322222223221222241223222221222212214223422121422222452222224223222122124212323123221"
+ 54 )
+ ; test_case "real line 164" `Quick
+ (test_highest_joltage
+ "2221442252281322222422127282212412222622221232731222223242124222212112212222241262321442522221122252"
+ 88 )
+ ; test_case "real line 165" `Quick
+ (test_highest_joltage
+ "2133231122331441531433375353572447432322146333423223632556232232334447444433622228242422332343332434"
+ 84 )
+ ; test_case "real line 166" `Quick
+ (test_highest_joltage
+ "5563624513351533334437333324353335333533533355223433335233342353363556423235334233344323342524634321"
+ 76 )
+ ; test_case "real line 167" `Quick
+ (test_highest_joltage
+ "2332432233323221431231323223332233383122222132424421134132233232323333433435254432332222332323313232"
+ 85 )
+ ; test_case "real line 168" `Quick
+ (test_highest_joltage
+ "2423222822122222222412282231122429224143222242222222223223436242424223242432142221433121142143522227"
+ 97 )
+ ; test_case "real line 169" `Quick
+ (test_highest_joltage
+ "1368838843358743334345324211363655364233733332443334321243322336412538532344213423253222366323263695"
+ 95 )
+ ; test_case "real line 170" `Quick
+ (test_highest_joltage
+ "4744137463363333333332326343314643324446233366376545372648324744635334435237267661327145553246452321"
+ 87 )
+ ; test_case "real line 171" `Quick
+ (test_highest_joltage
+ "3831332122233231223233222433232333233133133242242331362323323332235244434223336362233433352322334423"
+ 86 )
+ ; test_case "real line 172" `Quick
+ (test_highest_joltage
+ "3214422223143421344222143132231445423222522643233323314323424344434731323334224323324533352334332442"
+ 75 )
+ ; test_case "real line 173" `Quick
+ (test_highest_joltage
+ "3543312373344332233323452214433346347334844253532442224222234312433329254329453434283232534223272192"
+ 99 )
+ ; test_case "real line 174" `Quick
+ (test_highest_joltage
+ "3433383314338373834351844233646343475553142224453454325446354334653275541333543135354465333263445242"
+ 88 )
+ ; test_case "real line 175" `Quick
+ (test_highest_joltage
+ "4432372233684255322525272434647435924424322624541334413224443634326336461361436414475721222543446321"
+ 97 )
+ ; test_case "real line 176" `Quick
+ (test_highest_joltage
+ "5433335293334525543224371764333434988333237353113448943411932743514583594643353323325387583814645347"
+ 99 )
+ ; test_case "real line 177" `Quick
+ (test_highest_joltage
+ "5444424957443468654656364755567468455486545344445776761776566466566644555557658947447467449455766646"
+ 99 )
+ ; test_case "real line 178" `Quick
+ (test_highest_joltage
+ "2523333233343235435222235534333238434254233255342853335554453253236223223533323533522334332433432543"
+ 88 )
+ ; test_case "real line 179" `Quick
+ (test_highest_joltage
+ "1138614552342221321221222413621222223231223222323222814623241222446214226442469244351324343452225242"
+ 95 )
+ ; test_case "real line 180" `Quick
+ (test_highest_joltage
+ "3235222332131222222123223222332312322323172132141232121324222213353223224233213123222221223322233343"
+ 75 )
+ ; test_case "real line 181" `Quick
+ (test_highest_joltage
+ "4255452362323165242342332223332343343253334456143314452223324924424332355355353341343334425553353324"
+ 95 )
+ ; test_case "real line 182" `Quick
+ (test_highest_joltage
+ "3544633234326333143434434638432333354444353822424442333225332444532646543132326565233146643454534545"
+ 88 )
+ ; test_case "real line 183" `Quick
+ (test_highest_joltage
+ "5533866835333332446339845544234435374366623344443448343635433373544333343634473633534344325836364423"
+ 98 )
+ ; test_case "real line 184" `Quick
+ (test_highest_joltage
+ "2125222512312522233222222532263455412421222232122112222113222512244225244211543622122111124332312445"
+ 66 )
+ ; test_case "real line 185" `Quick
+ (test_highest_joltage
+ "5765537453753546524944644454354364643373535664333265356565454434334362334636443764473516535345363344"
+ 97 )
+ ; test_case "real line 186" `Quick
+ (test_highest_joltage
+ "1242333313323541312121335173323322252262322222223242223232232232334313232372223332333223325322223324"
+ 77 )
+ ; test_case "real line 187" `Quick
+ (test_highest_joltage
+ "4945346555344725534444354865365546455455453445376994344537749854443434373543557699323594547559554353"
+ 99 )
+ ; test_case "real line 188" `Quick
+ (test_highest_joltage
+ "8441321232536757468212413223835223356252342241323333442736423243742453922327334232628243572272153736"
+ 98 )
+ ; test_case "real line 189" `Quick
+ (test_highest_joltage
+ "3233227533534655473331231323541242222614244236242111121212233442265222232434532624232622312564515622"
+ 77 )
+ ; test_case "real line 190" `Quick
+ (test_highest_joltage
+ "5231443534475571334243142383154324332422432224554332949237335344432225546623266434742645424452245374"
+ 99 )
+ ; test_case "real line 191" `Quick
+ (test_highest_joltage
+ "2222652752542951212254142576134913223231244514325577152312242364152242322223322323734226324612251433"
+ 99 )
+ ; test_case "real line 192" `Quick
+ (test_highest_joltage
+ "3344232272222124265242432232223224453423221222354325223634222122333255463333115233242332132134223228"
+ 78 )
+ ; test_case "real line 193" `Quick
+ (test_highest_joltage
+ "2423211646221222214223621414422342222242214622426224222474221222222215322142322234542433223122532751"
+ 77 )
+ ; test_case "real line 194" `Quick
+ (test_highest_joltage
+ "1222432222227322222223122113222321223327322223241223322232332322222222222222123323434222222223412222"
+ 77 )
+ ; test_case "real line 195" `Quick
+ (test_highest_joltage
+ "2231222262245222221524294251251222212942222142532633726213522122212422222232121922212212262322231242"
+ 99 )
+ ; test_case "real line 196" `Quick
+ (test_highest_joltage
+ "3436674387463373644336847545353373813336332865333426743634647353343363237627393333883956336933349336"
+ 99 )
+ ; test_case "real line 197" `Quick
+ (test_highest_joltage
+ "4234664334533643445662346466345666647426655444553533366634453846446264346586345365444765364634443146"
+ 88 )
+ ; test_case "real line 198" `Quick
+ (test_highest_joltage
+ "4141324433423632324233334423343324244241332653544345362343464242132445242521322431444734316223541332"
+ 76 )
+ ; test_case "real line 199" `Quick
+ (test_highest_joltage
+ "3535726262552245152262222275133232659846242165522221422176222262567254622235412424626225322313236476"
+ 98 )
+ ; test_case "real line 200" `Quick
+ (test_highest_joltage
+ "2222222333136722224325232232111133222432346324431421212343222142244331223225124653223222322121232213"
+ 76 ) ] )
+ ; ( "BatteryBank.highest_joltage regression"
+ , [ test_case
+ "second digit promoted when same digit found and greater than \
+ first digit"
+ `Quick
+ (test_highest_joltage "343400" 44)
+ ; test_case "second last digit skipped" `Quick
+ (test_highest_joltage "789" 89)
+ ; test_case "I have no idea why this is wrong yet" `Quick
+ (test_highest_joltage "565" 65)
+ ; test_case "I have no idea why this is right yet" `Quick
+ (test_highest_joltage "566" 66)
+ ; test_case "I have no idea why this is right yet" `Quick
+ (test_highest_joltage "06760" 76)
+ ; test_case "I have no idea why this is wrong yet" `Quick
+ (test_highest_joltage "0067600" 76) ] )
+ ; ( "BatteryBank property tests"
+ , List.map
+ (QCheck_alcotest.to_alcotest ~colors:true)
+ [largest_digit_always_in_highest_joltage] ) ]
diff --git a/ocaml/test/infrastructure/dune b/ocaml/test/infrastructure/dune
new file mode 100644
index 0000000..5d493d9
--- /dev/null
+++ b/ocaml/test/infrastructure/dune
@@ -0,0 +1,8 @@
+(tests
+ (names
+ test_example)
+ (libraries
+ application
+ domain
+ infrastructure
+ alcotest))
diff --git a/ocaml/test/infrastructure/test_example.ml b/ocaml/test/infrastructure/test_example.ml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ocaml/test/infrastructure/test_example.ml
diff --git a/ocaml/test/presentation/dune b/ocaml/test/presentation/dune
new file mode 100644
index 0000000..e34e56f
--- /dev/null
+++ b/ocaml/test/presentation/dune
@@ -0,0 +1,11 @@
+(tests
+ (names
+ test_example)
+ (libraries
+ application
+ domain
+ infrastructure
+ presentation
+ alcotest
+ qcheck-core
+ qcheck-alcotest))
diff --git a/ocaml/test/presentation/test_example.ml b/ocaml/test/presentation/test_example.ml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ocaml/test/presentation/test_example.ml