summaryrefslogtreecommitdiff
path: root/ocaml
diff options
context:
space:
mode:
authorDJ O'Leary <dijitol@proton.me>2026-06-27 04:28:48 +0200
committerDJ O'Leary <dijitol@proton.me>2026-06-27 04:28:48 +0200
commitbc8b9edc0860f4804dfd0ea868ee7beaa564e62a (patch)
treee1123d11627b4829586da3b097015b1dbb3d0285 /ocaml
parent976048a85403fbb0751c34fa42703a3c47f6e918 (diff)
test: fix ocaml tests 2024 {WIP}HEADmain
Diffstat (limited to 'ocaml')
-rw-r--r--ocaml/README.md20
-rw-r--r--ocaml/flake.nix3
-rw-r--r--ocaml/lib/year2024/day01/part01.ml40
-rw-r--r--ocaml/lib/year2024/day01/part02.ml44
-rw-r--r--ocaml/lib/year2024/day02/part01.ml42
-rw-r--r--ocaml/lib/year2024/day03/part01.ml33
-rw-r--r--ocaml/lib/year2024/day04/part01.ml85
-rw-r--r--ocaml/lib/year2024/day05/part01.ml64
-rw-r--r--ocaml/lib/year2024/dune5
-rw-r--r--ocaml/test/year2024/day01/dune11
-rw-r--r--ocaml/test/year2024/day01/test_part01.ml30
-rw-r--r--ocaml/test/year2024/day01/test_part02.ml16
-rw-r--r--ocaml/test/year2024/day02/dune10
-rw-r--r--ocaml/test/year2024/day02/test_part01.ml41
-rw-r--r--ocaml/test/year2024/day03/dune10
-rw-r--r--ocaml/test/year2024/day03/test_part01.ml29
-rw-r--r--ocaml/test/year2024/day04/dune10
-rw-r--r--ocaml/test/year2024/day04/test_part01.ml141
18 files changed, 633 insertions, 1 deletions
diff --git a/ocaml/README.md b/ocaml/README.md
index 7d64eb8..a2a6b4f 100644
--- a/ocaml/README.md
+++ b/ocaml/README.md
@@ -1 +1,21 @@
# Advent of Code: OCaml
+
+## Getting Started
+
+### How to Build
+
+```sh
+task build
+```
+
+### How to Run
+
+```sh
+task run
+```
+
+### How to Test
+
+```sh
+task test
+```
diff --git a/ocaml/flake.nix b/ocaml/flake.nix
index 0176014..7b267a1 100644
--- a/ocaml/flake.nix
+++ b/ocaml/flake.nix
@@ -39,6 +39,7 @@
cmdliner # command line library
ppx_deriving # derivers
logs # logging library
+ menhir # parsing library
];
sysTools = with pkgs; [
go-task
@@ -48,4 +49,4 @@
buildInputs = ocamlPackages ++ sysTools;
};
};
-}
+} \ No newline at end of file
diff --git a/ocaml/lib/year2024/day01/part01.ml b/ocaml/lib/year2024/day01/part01.ml
new file mode 100644
index 0000000..100b12a
--- /dev/null
+++ b/ocaml/lib/year2024/day01/part01.ml
@@ -0,0 +1,40 @@
+(* Day 1: Part 1 *)
+
+let calculate_distance x y = Stdlib.abs (x - y)
+
+(** Require: left and right to be the same length *)
+let solve left right =
+ let sorted_left = List.sort Stdlib.compare left in
+ let sorted_right = List.sort Stdlib.compare right in
+ let distances = List.map2 calculate_distance sorted_left sorted_right in
+ List.fold_left ( + ) 0 distances
+
+let left line =
+ let pos = 0 in
+ let len = String.index line ' ' in
+ String.sub line pos len
+
+let right line =
+ let pos = String.rindex line ' ' + 1 in
+ let len = String.length line - pos in
+ String.sub line pos len
+
+let line_to_tuple line = (left line, right line)
+
+let lines_to_lists lines =
+ let tuples = List.map line_to_tuple lines in
+ let left =
+ List.map
+ (fun t ->
+ let l, _ = t in
+ int_of_string l)
+ tuples
+ in
+ let right =
+ List.map
+ (fun t ->
+ let _, r = t in
+ int_of_string r)
+ tuples
+ in
+ (left, right)
diff --git a/ocaml/lib/year2024/day01/part02.ml b/ocaml/lib/year2024/day01/part02.ml
new file mode 100644
index 0000000..25c8b7d
--- /dev/null
+++ b/ocaml/lib/year2024/day01/part02.ml
@@ -0,0 +1,44 @@
+(* Day 1: Part 2 *)
+
+let rec count_occurrances_help element acc lst =
+ match lst with
+ | [] -> acc
+ | h :: t ->
+ if h = element then count_occurrances_help element (acc + 1) t
+ else count_occurrances_help element acc t
+
+let count_occurrances element lst = count_occurrances_help element 0 lst
+
+let solve left right =
+ List.map (fun x -> x * count_occurrances x right) left
+ |> List.fold_left ( + ) 0
+
+let left line =
+ let pos = 0 in
+ let len = String.index line ' ' in
+ String.sub line pos len
+
+let right line =
+ let pos = String.rindex line ' ' + 1 in
+ let len = String.length line - pos in
+ String.sub line pos len
+
+let line_to_tuple line = (left line, right line)
+
+let lines_to_lists lines =
+ let tuples = List.map line_to_tuple lines in
+ let left =
+ List.map
+ (fun t ->
+ let l, _ = t in
+ int_of_string l)
+ tuples
+ in
+ let right =
+ List.map
+ (fun t ->
+ let _, r = t in
+ int_of_string r)
+ tuples
+ in
+ (left, right)
diff --git a/ocaml/lib/year2024/day02/part01.ml b/ocaml/lib/year2024/day02/part01.ml
new file mode 100644
index 0000000..32d7ab8
--- /dev/null
+++ b/ocaml/lib/year2024/day02/part01.ml
@@ -0,0 +1,42 @@
+(* Day 2: Part 1 *)
+
+type level = int
+type reports = level list list
+
+let rec compare_report_levels_helper comparison prev report =
+ match report with
+ | [] -> true
+ | h :: t -> comparison prev h && compare_report_levels_helper comparison h t
+
+let compare_report_levels comparison report =
+ match report with
+ | [] -> true
+ | h :: t -> compare_report_levels_helper comparison h t
+
+let all_increasing report = compare_report_levels ( < ) report
+let all_decreasing report = compare_report_levels ( > ) report
+
+let at_least_one report =
+ compare_report_levels (fun prev curr -> prev <> curr) report
+
+let at_most_three report =
+ compare_report_levels (fun prev curr -> abs (prev - curr) <= 3) report
+
+let check_report_safety report =
+ if not (all_increasing report || all_decreasing report) then false
+ else if not (at_least_one report && at_most_three report) then false
+ else true
+
+let rec count_safe_reports acc lst =
+ match lst with
+ | [] -> acc
+ | h :: t ->
+ if h then count_safe_reports (acc + 1) t else count_safe_reports acc t
+
+let solve reports =
+ reports |> List.map check_report_safety |> count_safe_reports 0
+
+let line_to_report line =
+ line |> String.split_on_char ' ' |> List.map int_of_string
+
+let lines_to_reports lines = List.map line_to_report lines
diff --git a/ocaml/lib/year2024/day03/part01.ml b/ocaml/lib/year2024/day03/part01.ml
new file mode 100644
index 0000000..c12b68a
--- /dev/null
+++ b/ocaml/lib/year2024/day03/part01.ml
@@ -0,0 +1,33 @@
+(* Day 3: Part 1 *)
+
+let find_mul text =
+ let regex_or_err = Re2.create "mul\\(([0-9]{1,3}),([0-9]{1,3})\\)" in
+ match regex_or_err with
+ | Ok regex -> (
+ let matches = Re2.get_matches regex text in
+ match matches with Ok matches -> matches | Error _ -> [])
+ | Error _ -> []
+
+let extract_pairs (matches : Re2.Match.t list) : (int * int) list =
+ List.map
+ (fun x ->
+ let left = Re2.Match.get x ~sub:(`Index 1) in
+ match left with
+ | None -> (0, 0)
+ | Some left -> (
+ let left_num = int_of_string left in
+ let right = Re2.Match.get x ~sub:(`Index 2) in
+ match right with
+ | None -> (0, 0)
+ | Some right ->
+ let right_num = int_of_string right in
+ (left_num, right_num)))
+ matches
+
+let solve input =
+ input |> find_mul |> extract_pairs
+ |> List.fold_left
+ (fun acc pair ->
+ let left, right = pair in
+ acc + (left * right))
+ 0
diff --git a/ocaml/lib/year2024/day04/part01.ml b/ocaml/lib/year2024/day04/part01.ml
new file mode 100644
index 0000000..acb3e3b
--- /dev/null
+++ b/ocaml/lib/year2024/day04/part01.ml
@@ -0,0 +1,85 @@
+(* Day 4: Part 1 *)
+
+type character = { index : int; character : char }
+type direction = N | NE | E | SE | S | SW | W | NW
+
+let all_directions = [ N; NE; E; SE; S; SW; W; NW ]
+let needle = "XMAS"
+
+(** [calculate_row_offset lines] is the offset used to move between the rows of
+ the concatenated variant of [lines] as though it were a grid. Requires:
+ values of [lines] all have the same length *)
+let calculate_row_offset lines =
+ match lines with [] -> 0 | h :: _ -> String.length h
+
+let join_lines lines =
+ let concat_trim acc line = acc ^ String.trim line in
+ List.fold_left concat_trim "" lines
+
+let explode s = List.init (String.length s) (String.get s)
+let is_in_bounds length index = index >= 0 && index < length
+
+let get_at_index line index =
+ let length = String.length line in
+ if not (is_in_bounds length index) then "."
+ else
+ let character = String.get line index in
+ Char.escaped character
+
+let get_direction_index row_offset direction index =
+ match direction with
+ | N -> index - row_offset
+ | NE -> index - row_offset + 1
+ | E -> index + 1
+ | SE -> index + row_offset + 1
+ | S -> index + row_offset
+ | SW -> index + row_offset - 1
+ | W -> index - 1
+ | NW -> index - row_offset - 1
+
+let get_direction_string get_char get_offset_index index =
+ let first = get_char index in
+ let second_index = get_offset_index index in
+ let second = get_char second_index in
+ let third_index = get_offset_index second_index in
+ let third = get_char third_index in
+ let fourth_index = get_offset_index third_index in
+ let fourth = get_char fourth_index in
+ first ^ second ^ third ^ fourth
+
+let check_direction get_from_line get_offset_index index direction =
+ get_direction_string get_from_line (get_offset_index direction) index = needle
+
+let calculate_num_of_matches line row_offset character =
+ let get_from_line_at_index = get_at_index line in
+ let get_offset_direction_index = get_direction_index row_offset in
+ let check_direction_from_index =
+ check_direction get_from_line_at_index get_offset_direction_index
+ character.index
+ in
+ all_directions
+ |> List.map check_direction_from_index
+ |> List.map (fun x -> match x with true -> 1 | false -> 0)
+ |> List.fold_left ( + ) 0
+
+(* Tried:
+ 2544 -> too high
+ 1272 (previous halved) -> too low *)
+let solve lines =
+ let row_offset = calculate_row_offset lines in
+ let line = join_lines lines in
+ let chars = explode line in
+ let characters = List.mapi (fun i c -> { index = i; character = c }) chars in
+ let xs =
+ List.filter
+ (fun c -> match c.character with 'X' -> true | _ -> false)
+ characters
+ in
+ let calculate_num_of_surrounding_matches_in_line =
+ calculate_num_of_matches line row_offset
+ in
+ let num_of_matches =
+ List.map calculate_num_of_surrounding_matches_in_line xs
+ |> List.fold_left ( + ) 0
+ in
+ num_of_matches
diff --git a/ocaml/lib/year2024/day05/part01.ml b/ocaml/lib/year2024/day05/part01.ml
new file mode 100644
index 0000000..96ff486
--- /dev/null
+++ b/ocaml/lib/year2024/day05/part01.ml
@@ -0,0 +1,64 @@
+(* Day 5: Part 1 *)
+
+module StringMap = Map.Make (String)
+
+let inverse_page_rule page_rule =
+ String.split_on_char '|' page_rule
+ |> fun lst ->
+ match lst with [] -> "" | _ :: _ -> String.concat "|" (List.rev lst)
+
+(** [parse_aux acc page_rules] is a map of all invalid transitions as defined by
+ [page_rules] *)
+let rec get_invalid_transitions_aux acc page_rules : unit StringMap.t =
+ match page_rules with
+ | [] ->
+ acc
+ | h :: t ->
+ get_invalid_transitions_aux
+ (acc |> StringMap.add (inverse_page_rule h) ())
+ t
+
+let get_invalid_transitions page_rules =
+ get_invalid_transitions_aux StringMap.empty page_rules
+
+(** [solve page_rules updates] is the solution for this puzzle, given the
+ [page_rules] and a list of [updates].
+
+ BELOW DOESN'T WORK: no guarantee that second page comes immediately after
+ the first
+ - pass in updates to solve
+ - make sliding window of 2 elements
+ - invert element order and concat
+ - check if key is in map from [parse page_rules],
+ - if yes, then it is an illegal transition
+ - if no, then add the list to a list for further processing *)
+let solve page_rules _ =
+ let[@warning "-26"] invalid_transitions =
+ get_invalid_transitions page_rules
+ in
+ ()
+
+let _ =
+ solve
+ [ "47|53"
+ ; "97|13"
+ ; "97|61"
+ ; "97|47"
+ ; "75|29"
+ ; "61|13"
+ ; "75|53"
+ ; "29|13"
+ ; "97|29"
+ ; "53|29"
+ ; "61|53"
+ ; "97|53"
+ ; "61|29"
+ ; "47|13"
+ ; "75|47"
+ ; "97|75"
+ ; "47|61"
+ ; "75|61"
+ ; "47|29"
+ ; "75|13"
+ ; "53|13" ]
+ []
diff --git a/ocaml/lib/year2024/dune b/ocaml/lib/year2024/dune
new file mode 100644
index 0000000..63bf2a1
--- /dev/null
+++ b/ocaml/lib/year2024/dune
@@ -0,0 +1,5 @@
+(include_subdirs qualified)
+
+(library
+ (name year2024)
+ (libraries re2))
diff --git a/ocaml/test/year2024/day01/dune b/ocaml/test/year2024/day01/dune
new file mode 100644
index 0000000..8891ccf
--- /dev/null
+++ b/ocaml/test/year2024/day01/dune
@@ -0,0 +1,11 @@
+(tests
+ (names
+ test_part01
+ test_part02)
+ (libraries
+ year2024
+ alcotest
+ qcheck-core
+ qcheck-alcotest)
+ (action (run %{test} -e))
+ (modes byte exe))
diff --git a/ocaml/test/year2024/day01/test_part01.ml b/ocaml/test/year2024/day01/test_part01.ml
new file mode 100644
index 0000000..706284d
--- /dev/null
+++ b/ocaml/test/year2024/day01/test_part01.ml
@@ -0,0 +1,30 @@
+open Year2024.Day01.Part01
+
+let test_solve haveLeft haveRight want () =
+ Alcotest.(check int) "incorrect solution" want @@ solve haveLeft haveRight
+
+let test_distance_calculation haveX haveY want () =
+ Alcotest.(check int) "incorrect solution" want
+ @@ calculate_distance haveX haveY
+
+let () =
+ let open Alcotest in
+ run "test suite for day 01 part 01"
+ [ ( "test solutions"
+ , [ test_case "empty list is 0" `Quick @@ (test_solve [] []) 0
+ ; test_case "distance between 0 and 1 is 1" `Quick
+ @@ (test_solve [0] [1]) 1
+ ; test_case "distance between 1 and 0 is 1" `Quick
+ @@ (test_solve [1] [0]) 1
+ ; test_case "smallest are paired, then next smallest, ..." `Quick
+ @@ (test_solve [1; 2; 3] [3; 2; 1]) 0
+ ; test_case "solves example correctly" `Quick
+ @@ (test_solve [3; 4; 2; 1; 3; 3] [4; 3; 5; 3; 9; 3]) 11
+ ; test_case "same number is 0 distance apart" `Quick
+ @@ (test_distance_calculation 0 0) 0
+ ; test_case "same number is 0 distance apart" `Quick
+ @@ (test_distance_calculation 1 1) 0
+ ; test_case "distance is equal to abs(x - y)" `Quick
+ @@ (test_distance_calculation 0 1) 1
+ ; test_case "order of inputs doesn't matter" `Quick
+ @@ (test_distance_calculation 1 0) 1 ] ) ]
diff --git a/ocaml/test/year2024/day01/test_part02.ml b/ocaml/test/year2024/day01/test_part02.ml
new file mode 100644
index 0000000..142add6
--- /dev/null
+++ b/ocaml/test/year2024/day01/test_part02.ml
@@ -0,0 +1,16 @@
+open Year2024.Day01.Part02
+
+let test_solve haveLeft haveRight want () =
+ Alcotest.(check int) "incorrect solution" want @@ solve haveLeft haveRight
+
+let () =
+ let open Alcotest in
+ run "test suite for day 01 part 02"
+ [ ( "test solutions"
+ , [ test_case "empty list is 0" `Quick @@ (test_solve [] []) 0
+ ; test_case "ten x zero occurrances of ten" `Quick
+ @@ (test_solve [10] [1; 2; 3]) 0
+ ; test_case "two x three occurrances of two" `Quick
+ @@ (test_solve [2] [2; 2; 2]) 6
+ ; test_case "solves example correctly" `Quick
+ @@ (test_solve [3; 4; 2; 1; 3; 3] [4; 3; 5; 3; 9; 3]) 31 ] ) ]
diff --git a/ocaml/test/year2024/day02/dune b/ocaml/test/year2024/day02/dune
new file mode 100644
index 0000000..c4a75c4
--- /dev/null
+++ b/ocaml/test/year2024/day02/dune
@@ -0,0 +1,10 @@
+(tests
+ (names
+ test_part01)
+ (libraries
+ year2024
+ alcotest
+ qcheck-core
+ qcheck-alcotest)
+ (action (run %{test} -e))
+ (modes byte exe))
diff --git a/ocaml/test/year2024/day02/test_part01.ml b/ocaml/test/year2024/day02/test_part01.ml
new file mode 100644
index 0000000..4feea41
--- /dev/null
+++ b/ocaml/test/year2024/day02/test_part01.ml
@@ -0,0 +1,41 @@
+open Year2024.Day02.Part01
+
+let test_solve have want () =
+ Alcotest.(check int) "incorrect solution" want @@ solve have
+
+let test_report_safety_check have want () =
+ Alcotest.(check bool) "incorrect report safety" want
+ @@ check_report_safety have
+
+let () =
+ let open Alcotest in
+ run "test suite for day 2 part 1"
+ [ ( "test solutions"
+ , [ test_case "empty list should have no valid reports" `Quick
+ @@ test_solve [] 0
+ ; test_case "valid report should count as one" `Quick
+ @@ test_solve [[7; 6; 4; 2; 1]] 1
+ ; test_case "provided example" `Quick
+ @@ test_solve
+ [ [7; 6; 4; 2; 1]
+ ; [1; 2; 7; 8; 9]
+ ; [9; 7; 6; 2; 1]
+ ; [1; 3; 2; 4; 5]
+ ; [8; 6; 4; 4; 1]
+ ; [1; 3; 6; 7; 9] ]
+ 2 ] )
+ ; ( "test report safety check"
+ , [ test_case "all increasing" `Quick
+ @@ test_report_safety_check [1; 2; 3] true
+ ; test_case "all decreasing" `Quick
+ @@ test_report_safety_check [3; 2; 1] true
+ ; test_case "increasing and decreasing" `Quick
+ @@ test_report_safety_check [1; 3; 2] false
+ ; test_case "change of at least one" `Quick
+ @@ test_report_safety_check [1; 2; 3] true
+ ; test_case "no change" `Quick
+ @@ test_report_safety_check [1; 1; 2] false
+ ; test_case "change of at most three" `Quick
+ @@ test_report_safety_check [1; 2; 4; 7] true
+ ; test_case "change of at more than three" `Quick
+ @@ test_report_safety_check [1; 5] false ] ) ]
diff --git a/ocaml/test/year2024/day03/dune b/ocaml/test/year2024/day03/dune
new file mode 100644
index 0000000..c4a75c4
--- /dev/null
+++ b/ocaml/test/year2024/day03/dune
@@ -0,0 +1,10 @@
+(tests
+ (names
+ test_part01)
+ (libraries
+ year2024
+ alcotest
+ qcheck-core
+ qcheck-alcotest)
+ (action (run %{test} -e))
+ (modes byte exe))
diff --git a/ocaml/test/year2024/day03/test_part01.ml b/ocaml/test/year2024/day03/test_part01.ml
new file mode 100644
index 0000000..78d04e2
--- /dev/null
+++ b/ocaml/test/year2024/day03/test_part01.ml
@@ -0,0 +1,29 @@
+open Year2024.Day03.Part01
+
+let example_input =
+ "xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"
+
+let test_find_mul () =
+ Alcotest.(check (list string))
+ "incorrect mul list found"
+ ["mul(2,4)"; "mul(5,5)"; "mul(11,8)"; "mul(8,5)"]
+ (let result = find_mul example_input in
+ List.map (fun x -> Re2.Match.get_exn x ~sub:(`Index 0)) result )
+
+let test_extract_pairs () =
+ Alcotest.(check (list (pair int int)))
+ "incorrect pairs"
+ [(2, 4); (5, 5); (11, 8); (8, 5)]
+ (let matches = find_mul example_input in
+ extract_pairs matches )
+
+let test_solve () = Alcotest.(check int) "" 161 @@ solve example_input
+
+let () =
+ let open Alcotest in
+ run "test suite for day 3 part 1"
+ [ ( "test implementation"
+ , [ test_case "muls extracted from example correctly" `Quick
+ @@ test_find_mul
+ ; test_case "pairs extracted from matches" `Quick @@ test_extract_pairs
+ ; test_case "example correctly solved" `Quick @@ test_solve ] ) ]
diff --git a/ocaml/test/year2024/day04/dune b/ocaml/test/year2024/day04/dune
new file mode 100644
index 0000000..c4a75c4
--- /dev/null
+++ b/ocaml/test/year2024/day04/dune
@@ -0,0 +1,10 @@
+(tests
+ (names
+ test_part01)
+ (libraries
+ year2024
+ alcotest
+ qcheck-core
+ qcheck-alcotest)
+ (action (run %{test} -e))
+ (modes byte exe))
diff --git a/ocaml/test/year2024/day04/test_part01.ml b/ocaml/test/year2024/day04/test_part01.ml
new file mode 100644
index 0000000..5776e49
--- /dev/null
+++ b/ocaml/test/year2024/day04/test_part01.ml
@@ -0,0 +1,141 @@
+open Year2024.Day04.Part01
+
+let example_input =
+ [ "MMMSXXMASM"
+ ; "MSAMXMSMSA"
+ ; "AMXSXMAAMM"
+ ; "MSAMASMSMX"
+ ; "XMASAMXAMM"
+ ; "XXAMMXXAMA"
+ ; "SMSMSASXSS"
+ ; "SAXAMASAAA"
+ ; "MAMMMXMMMM"
+ ; "MXMXAXMASX" ]
+
+let is_in_bounds_tests =
+ let test_is_in_bounds expected length index _ =
+ let actual = is_in_bounds length index in
+ assert_equal ~printer:string_of_bool expected actual
+ in
+ [ "not in bounds if below 0" >:: test_is_in_bounds false 1 (-1)
+ ; "in bounds if gte 0 and lt length" >:: test_is_in_bounds true 1 0
+ ; "not in bounds if eq length" >:: test_is_in_bounds false 1 1
+ ; "not in bounds if gt length" >:: test_is_in_bounds false 1 2 ]
+
+let calculate_num_of_matches_tests =
+ let test_calculate_num_of_matches expected line row_offset character _ =
+ let actual = calculate_num_of_matches line row_offset character in
+ assert_equal ~printer:string_of_int expected actual
+ in
+ [ "is 0 when no matches present"
+ >:: test_calculate_num_of_matches 0 "SMMX" 1 {index= 3; character= 'X'}
+ ; "is 1 when only one match present - top to bottom"
+ >::
+ (*
+ |.|X|.|
+ |.|M|.|
+ |.|A|.|
+ |.|S|.|
+ *)
+ test_calculate_num_of_matches 1 ".X..M..A..S." 3 {index= 1; character= 'X'}
+ ; "is 1 when only one match present - top to bottom edge"
+ >::
+ (*
+ |X|.|.|
+ |M|.|.|
+ |A|.|.|
+ |S|.|.|
+ *)
+ test_calculate_num_of_matches 1 "X..M..A..S.." 3 {index= 0; character= 'X'}
+ ; "is 1 when only one match present - bottom to top"
+ >::
+ (*
+ |.|S|.|
+ |.|A|.|
+ |.|M|.|
+ |.|X|.|
+ *)
+ test_calculate_num_of_matches 1 ".S..A..M..X." 3 {index= 10; character= 'X'}
+ ; "is 1 when only one match present - left to right"
+ >::
+ (*
+ |X|M|A|S|
+ *)
+ test_calculate_num_of_matches 1 "XMAS" 4 {index= 0; character= 'X'}
+ ; "is 1 when only one match present - right to left"
+ >::
+ (*
+ |S|A|M|X|
+ *)
+ test_calculate_num_of_matches 1 "SAMX" 4 {index= 3; character= 'X'}
+ ; "is 1 when only one match present - bottom right to top left"
+ >::
+ (*
+ |S|.|.|.|
+ |.|A|.|.|
+ |.|.|M|.|
+ |.|.|.|X|
+ *)
+ test_calculate_num_of_matches 1 "S....A....M....X" 4
+ {index= 15; character= 'X'}
+ ; "is 1 when only one match present - top left to bottom right"
+ >::
+ (*
+ |X|.|.|.|
+ |.|M|.|.|
+ |.|.|A|.|
+ |.|.|.|S|
+ *)
+ test_calculate_num_of_matches 1 "X....M....A....S" 4
+ {index= 0; character= 'X'}
+ ; "is 1 when only one match present - bottom left to top right"
+ >::
+ (*
+ |.|.|.|S|
+ |.|.|A|.|
+ |.|M|.|.|
+ |X|.|.|.|
+ *)
+ test_calculate_num_of_matches 1 "...S..A..M..X..." 4
+ {index= 12; character= 'X'}
+ ; "is 1 when only one match present - top right to bottom left"
+ >::
+ (*
+ |.|.|.|X|
+ |.|.|M|.|
+ |.|A|.|.|
+ |S|.|.|.|
+ *)
+ test_calculate_num_of_matches 1 "...X..M..A..S..." 4
+ {index= 3; character= 'X'}
+ ; "is 8 when matches present in all possible directions"
+ >::
+ (*
+ |S|.|.|S|.|.|S|
+ |.|A|.|A|.|A|.|
+ |.|.|M|M|M|.|.|
+ |S|A|M|X|M|A|S|
+ |.|.|M|M|M|.|.|
+ |.|A|.|A|.|A|.|
+ |S|.|.|S|.|.|S|
+ *)
+ test_calculate_num_of_matches 8
+ "S..S..S.A.A.A...MMM..SAMXMAS..MMM...A.A.A.S..S..S" 7
+ {index= 24; character= 'X'} ]
+
+let solve_tests =
+ let test_solve have want () =
+ let actual = solve have in
+ Alcotest.(check int) "incorrect solution" want actual
+ in
+ [ ( "test solution"
+ , [ Alcotest.test_case "example is solved correctly" `Quick
+ @@ test_solve example_input 18
+ ; Alcotest.test_case "minimal example is solved correctly" `Quick
+ @@ test_solve ["..X..."; ".SAMX."; ".A..A."; "XMAS.S"; ".X...."] 4 ] )
+ ]
+
+let () =
+ let open Alcotest in
+ run "test suite for day 4 part 1" is_in_bounds_tests
+ @ calculate_num_of_matches_tests @ solve_tests