From bc8b9edc0860f4804dfd0ea868ee7beaa564e62a Mon Sep 17 00:00:00 2001 From: DJ O'Leary Date: Sat, 27 Jun 2026 04:28:48 +0200 Subject: test: fix ocaml tests 2024 {WIP} --- ocaml/README.md | 20 +++++ ocaml/flake.nix | 3 +- ocaml/lib/year2024/day01/part01.ml | 40 +++++++++ ocaml/lib/year2024/day01/part02.ml | 44 ++++++++++ ocaml/lib/year2024/day02/part01.ml | 42 +++++++++ ocaml/lib/year2024/day03/part01.ml | 33 ++++++++ ocaml/lib/year2024/day04/part01.ml | 85 +++++++++++++++++++ ocaml/lib/year2024/day05/part01.ml | 64 ++++++++++++++ ocaml/lib/year2024/dune | 5 ++ ocaml/test/year2024/day01/dune | 11 +++ ocaml/test/year2024/day01/test_part01.ml | 30 +++++++ ocaml/test/year2024/day01/test_part02.ml | 16 ++++ ocaml/test/year2024/day02/dune | 10 +++ ocaml/test/year2024/day02/test_part01.ml | 41 +++++++++ ocaml/test/year2024/day03/dune | 10 +++ ocaml/test/year2024/day03/test_part01.ml | 29 +++++++ ocaml/test/year2024/day04/dune | 10 +++ ocaml/test/year2024/day04/test_part01.ml | 141 +++++++++++++++++++++++++++++++ 18 files changed, 633 insertions(+), 1 deletion(-) create mode 100644 ocaml/lib/year2024/day01/part01.ml create mode 100644 ocaml/lib/year2024/day01/part02.ml create mode 100644 ocaml/lib/year2024/day02/part01.ml create mode 100644 ocaml/lib/year2024/day03/part01.ml create mode 100644 ocaml/lib/year2024/day04/part01.ml create mode 100644 ocaml/lib/year2024/day05/part01.ml create mode 100644 ocaml/lib/year2024/dune create mode 100644 ocaml/test/year2024/day01/dune create mode 100644 ocaml/test/year2024/day01/test_part01.ml create mode 100644 ocaml/test/year2024/day01/test_part02.ml create mode 100644 ocaml/test/year2024/day02/dune create mode 100644 ocaml/test/year2024/day02/test_part01.ml create mode 100644 ocaml/test/year2024/day03/dune create mode 100644 ocaml/test/year2024/day03/test_part01.ml create mode 100644 ocaml/test/year2024/day04/dune create mode 100644 ocaml/test/year2024/day04/test_part01.ml (limited to 'ocaml') 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 -- cgit v1.2.3