From e6b401d986263d3102c45edd5f6e3f30549e8588 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Wed, 2 Dec 2020 15:10:29 +0100 Subject: [PATCH] add expect tests & refactor --- .gitignore | 1 + aoc2020.ml | 2 ++ day1.ml | 67 +++++++++++++++++++++++++++---------------- day2.ml | 81 +++++++++++++++++++++++++++++++++++----------------- dune | 10 ++++++- dune-project | 6 ++++ 6 files changed, 115 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index 655e32b..31b45b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ _build .merlin +aoc2020.opam diff --git a/aoc2020.ml b/aoc2020.ml index 98abc26..f0b2753 100644 --- a/aoc2020.ml +++ b/aoc2020.ml @@ -1,3 +1,5 @@ +open Aoc2020 + let days = [|Day1.main; Day2.main|] let _ = diff --git a/day1.ml b/day1.ml index 7a41bd2..0544c68 100644 --- a/day1.ml +++ b/day1.ml @@ -1,36 +1,53 @@ -module IntSet = Set.Make(Int) +module IntSet = struct + include Set.Make(Int) + + let exists_opt (type a) (f: int -> a option) (set: t): a option = + let exception Found of a in + let f' x = Option.iter (fun x -> raise (Found x)) (f x) in + match iter f' set with + | exception Found x -> Some x + | _ -> None +end let read_ints = Bracket.infile_lines ~line:int_of_string ~of_seq:IntSet.of_seq -let find_with target ints = - let open IntSet in - let p x = mem (target - x) ints in - let res = min_elt (filter p ints) in - res, target - res -let find = find_with 2020 +let rec find ?(target=2020) ~n set = + if n < 1 then + raise (Invalid_argument "n must be at least 1") + else if n = 1 then + if IntSet.mem target set then Some [target] else None + else + let joined x = + Option.map (List.cons x) (find ~target:(target - x) ~n:(n - 1) set) in + IntSet.exists_opt joined set -let main_part_1 input = - let ints = read_ints input in - let x, y = find ints in - Printf.printf "%d * %d = %d\n" x y (x * y) +let print_result xs = + let res = List.fold_left ( * ) 1 xs in + let rec print_prod fmt = function + | [] -> Format.pp_print_text fmt "{}" + | [x] -> Format.fprintf fmt "%d" x + | x::xs -> Format.fprintf fmt "%d * %a" x print_prod xs in + Format.printf "%a = %d\n" print_prod xs res -let main_part_2 input = - let ints = read_ints input in - let exception Found of int * int * int in - let go x = - match find_with (2020 - x) ints with - | exception Not_found -> () - | y, z -> raise (Found (x, y, z)) - in - match IntSet.iter go ints with - | exception Found (x, y, z) -> - Printf.printf "%d * %d * %d = %d\n" x y z (x * y * z) - | _ -> raise Not_found -let mains = [|main_part_1; main_part_2|] +let main' n input = + match find ~n (read_ints input) with + | Some xs -> print_result xs + | None -> raise Not_found + +let mains = [|main' 2; main' 3|] let main = function | [part; input] -> mains.(int_of_string part - 1) input - | _ -> Usage.exit "1 " + | _ -> Usage.exit "1 " + + +let%expect_test "part 1" = + mains.(0) "../../data/day1"; (* is this always the right path??? *) + [%expect{| 534 * 1486 = 793524 |}] + +let%expect_test "part 2" = + mains.(1) "../../data/day1"; + [%expect{| 71 * 686 * 1263 = 61515678 |}] diff --git a/day2.ml b/day2.ml index 58057f1..5964d0c 100644 --- a/day2.ml +++ b/day2.ml @@ -1,41 +1,70 @@ -let parse_line' f str = Scanf.sscanf str "%d - %d %c : %s%!" f +module type Part = sig + type t + val parse_line: string -> t + val ok: t -> bool +end + +let parse_line' f str = Scanf.sscanf str "%d - %d %c : %s" f -type part1 = {min: int; max: int; char: char; pass: string} +module Part1: Part = struct + type t = {min: int; max: int; char: char; pass: string} -let parse_line1 = parse_line' - (fun min max char pass -> {min; max; char; pass}) + let parse_line = parse_line' + (fun min max char pass -> {min; max; char; pass}) -let count c str = - let res = ref 0 in - String.iter (fun d -> if c = d then incr res) str; - !res + let count c str = + let res = ref 0 in + String.iter (fun d -> if c = d then incr res) str; + !res -let ok1 {min; max; char; pass} = - let res = count char pass in - res >= min && res <= max + let ok {min; max; char; pass} = + let res = count char pass in + res >= min && res <= max +end -type part2 = {pos1: int; pos2: int; char: char; pass: string} +module Part2: Part = struct + type t = {pos1: int; pos2: int; char: char; pass: string} -let parse_line2 = parse_line' - (fun pos1 pos2 char pass -> {pos1 = pos1 - 1; pos2 = pos2 - 1; char; pass}) + let parse_line = parse_line' + (fun pos1 pos2 char pass -> {pos1 = pos1 - 1; pos2 = pos2 - 1; char; pass}) -let ok2 {pos1; pos2; char; pass} = - (pass.[pos1] = char) <> (pass.[pos2] = char) + let ok {pos1; pos2; char; pass} = + (pass.[pos1] = char) <> (pass.[pos2] = char) +end -let main' ~line ~ok input = - let count_all = ref 0 in - let count_ok = ref 0 in - let check l = incr count_all; if ok l then incr count_ok in - Bracket.infile_iter_lines input ~line ~iter:check; - Printf.printf "%d out of %d ok\n" !count_ok !count_all +module type Main = sig val run: string -> unit end -let main1 = main' ~line:parse_line1 ~ok:ok1 -let main2 = main' ~line:parse_line2 ~ok:ok2 -let mains = [|main1; main2|] +module Main(P: Part)(): Main = struct + open P + + let count_all = ref 0 + let count_ok = ref 0 + let check l = + incr count_all; + if ok l then incr count_ok + + let run input = + Bracket.infile_iter_lines input ~line:parse_line ~iter:check; + Printf.printf "%d out of %d ok\n" !count_ok !count_all +end + +let main' (module P: Part) = + let module M = Main(P)() in M.run + +let mains = [|main' (module Part1); main' (module Part2)|] let main = function | [part; input] -> mains.(int_of_string part - 1) input - | _ -> Usage.exit "2 " + | _ -> Usage.exit "2 " + + +let%expect_test "part 1" = + mains.(0) "../../data/day2"; + [%expect{| 607 out of 1000 ok |}] + +let%expect_test "part 2" = + mains.(1) "../../data/day2"; + [%expect{| 321 out of 1000 ok |}] diff --git a/dune b/dune index 5817863..868d48e 100644 --- a/dune +++ b/dune @@ -1,2 +1,10 @@ (executable - (name aoc2020)) + (public_name aoc2020) + (libraries aoc2020) + (modules aoc2020)) + +(library + (public_name aoc2020) + (inline_tests) + (modules (:standard \ aoc2020)) + (preprocess (pps ppx_expect))) diff --git a/dune-project b/dune-project index 45acd3f..b8b9b31 100644 --- a/dune-project +++ b/dune-project @@ -1 +1,7 @@ (lang dune 2.7) +(generate_opam_files true) +(allow_approximate_merlin) + +(package + (name aoc2020) + (depends ppx_expect))