diff options
Diffstat (limited to 'ocaml/lib/shared')
| -rw-r--r-- | ocaml/lib/shared/dune | 10 | ||||
| -rw-r--r-- | ocaml/lib/shared/file.ml | 6 | ||||
| -rw-r--r-- | ocaml/lib/shared/matrix.ml | 196 | ||||
| -rw-r--r-- | ocaml/lib/shared/parser.ml | 36 | ||||
| -rw-r--r-- | ocaml/lib/shared/range.ml | 67 |
5 files changed, 315 insertions, 0 deletions
diff --git a/ocaml/lib/shared/dune b/ocaml/lib/shared/dune new file mode 100644 index 0000000..f71a5fe --- /dev/null +++ b/ocaml/lib/shared/dune @@ -0,0 +1,10 @@ +(library + (name shared) + (inline_tests) + (preprocess + (pps + ppx_deriving.show + ppx_deriving.eq + ppx_deriving.ord + ppx_expect + ppx_inline_test))) diff --git a/ocaml/lib/shared/file.ml b/ocaml/lib/shared/file.ml new file mode 100644 index 0000000..74b5abc --- /dev/null +++ b/ocaml/lib/shared/file.ml @@ -0,0 +1,6 @@ +(** [read filename] is the contents of [filename] as a string. + The empty string is returned if [filename] does not exist. *) +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/shared/matrix.ml b/ocaml/lib/shared/matrix.ml new file mode 100644 index 0000000..fdf476f --- /dev/null +++ b/ocaml/lib/shared/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/shared/parser.ml b/ocaml/lib/shared/parser.ml new file mode 100644 index 0000000..099900b --- /dev/null +++ b/ocaml/lib/shared/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/shared/range.ml b/ocaml/lib/shared/range.ml new file mode 100644 index 0000000..a1bdc00 --- /dev/null +++ b/ocaml/lib/shared/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"] |
