open List
open Printf
open String
(* ----- TUPLES ----- *)
(** Returns the first component of a pair. *)
let fst (a, _) = a
(** Returns the second component of pair. *)
let snd (_, b) = b
(* ----- FOLDS AND TRAVERSALS ----- *)
(**
* Left-associative fold of a list.
* This function is not tail recursive.
*
* @param fn function to apply to each element of the list;
* fn is called with the accumulator and an element,
* and must return a new accumulator
* @param acc initial accumulator value
* @param lst list of elements to be fed to f
* @result final accumulator value created by applying f
* to each element of xs, from the left
* @see fold_left
*)
let foldl fn acc lst = fold_left fn acc lst
(**
* Right-associative fold of a list.
* This function is tail recursive.
*
* @param fn function to apply to each element of the list;
* fn is called with the accumulator and an element,
* and must return a new accumulator
* @param acc initial accumulator value
* @param lst list of elements to be fed to f
* @result final accumulator value created by applying f
* to each element of xs, from the right
* @see fold_right
*)
let foldr fn lst acc = fold_right fn lst acc
(**
* Maps each element of a list to a function, and combines the results.
* This function is not tail recursive.
*
* @param fn function to apply to each element of the list
* @param lst list of elements to be fed to f
* @result new list of the form [f l1; f l2; f l3; ...]
* @see map
*)
let map fn lst = List.map fn lst
(**
* Maps each element of a list to a function, and throws away the results.
* This function is not tail recursive.
*
* @param fn function to apply to each element of the list
* @param lst list of elements to be fed to f
* @see List.iter
*)
let map_ fn lst = List.iter fn lst
(**
* Returns the last element of a list, which must be non-empty.
* This function is tail-recursive.
*
* @param lst list to get the last element of
* @see first
*)
let rec last lst = match lst with
| x::[] -> x
| x::xs -> last xs
| _ -> failwith "last []"
(**
* Returns all elements excepting the last of a list,
* which must be non-empty.
* This function is not tail-recursive.
*
* @param lst list from which to extract the initial elements
* @see tail
*)
let rec init lst = match lst with
| x::[] -> []
| x::xs -> x::init xs
| _ -> failwith "init []"
(**
* Left-associative fold of a list, which must be non-empty.
* The first element of the list is used as the initial accumulator value.
* This function is not tail recursive.
*
* @param fn function to apply to each element of the list;
* fn is called with the accumulator and an element,
* and must return a new accumulator
* @param lst list of elements to be fed to f
* @see foldl
* @see fold_left
* @see foldr'
*)
let foldl' fn lst = match lst with
| x::xs -> fold_left fn x xs
| _ -> failwith "foldl' []"
(**
* Right-associative fold of a list, which must be non-empty.
* The last element of the list is used as the initial accumulator value.
* This function is tail recursive.
*
* @param fn function to apply to each element of the list, from the right;
* fn is called with the accumulator and an element,
* and must return a new accumulator
* @param lst list of elements to be fed to f
* @see foldr
* @see fold_right
* @see foldl'
*)
let foldr' fn lst = match lst with
| _::_ -> let acc = init lst
and lst' = last lst in
fold_right fn acc lst'
| _ -> failwith "foldr' []"
(* ----- LIST OPERATIONS ----- *)
(** Returns whether or not a list is empty. *)
let null lst = match lst with
| [] -> true
| _ -> false
(** Returns the length of a list. *)
let length = List.length
(** Returns the first element of a list, which must be non-empty. *)
let head lst = match lst with
| _::_ -> List.hd lst
| [] -> failwith "head []"
(** Returns the remaining elements of a list, which must be non-empty. *)
let tail lst = match lst with
| _::_ -> List.tl lst
| [] -> failwith "tail []"
(** Returns a copy of a list, with the elements in reverse order. *)
let reverse = List.rev
(** Appends two lists .*)
let (++) lst1 lst2 = append lst1 lst2
(**
* Equivalent to `(reverse lst1) ++ lst2`,
* but tail recursive and more efficient.
*
* @see reverse
* @see (++)
* @see append
*)
let reverse_append lst1 lst2 = List.rev_append lst1 lst2
(**
* Concatenates a list of lists.
*
* @param xss a list of lists of the form [[]; []; ...]
*)
let concat xss = List.concat xss
(**
* Returns a number of elements from the beginning of a list.
* This function is not tail-recursive.
*
* @param num number of items to take from the list
* @param lst list from which to take elements from the left of
* @see drop
*)
let rec take num lst =
if num <= 0 || (null lst)
then []
else head lst::take (num-1) (tail lst)
(**
* Drops a number of elements from the beginning of a list.
* This function is tail-recursive.
*
* @param num the number of elements to drop from the list
* @param lst list from which to drop elements
* @result a list that is the list lst with the first num elements removed
* @see @take
*)
let rec drop num lst =
if num <= 0
then lst
else drop (num-1) (tail lst)
(**
* Returns the biggest element of a list, which must be non-empty.
*
* @see minimum
*)
let maximum lst = match lst with
| _::_ -> foldl' (fun a b -> max a b) lst
| _ -> failwith "maximum []"
(**
* Returns the smallest element of a list, which must be non-empty.
*
* @see maximum
*)
let minimum lst = match lst with
| _::_ -> foldl' (fun a b -> min a b) lst
| _ -> failwith "minimum []"
(**
* Returns the sum of a list of integers, which must be non-empty.
*
* @see product
*)
let sum lst = match lst with
| _::_ -> foldl' (fun a b -> a+b) lst
| _ -> failwith "sum []"
(**
* Returns the product of a list of integers, which must be non-empty.
*
* @see sum
*)
let product lst = match lst with
| _::_ -> foldl' (fun a b -> a*b) lst
| _ -> failwith "product []"
(**
* Returns whether or not an element is in a list.
* This function is tail-recursive.
*
* @param x the element to search for
* @param lst the list in which to search for the element x
* @result true if x is found in the list lst, otherwise false
*)
let rec elem x lst = match lst with
| y::ys -> if x = y
then true
else elem x ys;
| [] -> false
(* ----- NUMERIC FUNCTIONS ----- *)
(** Returns the negative of an integer. *)
let negate n = (-n)
(** Returns whether or not an integer is even. *)
let even n = n mod 2 = 0
(** Returns whether or not an integer is odd. *)
let odd n = n mod 2 != 0
(** Returns the sign of an integer, either 1 or -1. *)
let signum n = if n >= 0 then 1 else (-1)
(**
* Returns the greatest common divisor of two integers.
* This function is tail-recursive.
*)
let gcd a b =
let rec gcd' a b = if b = 0
then a
else gcd' b (a mod b) in
gcd' (abs a) (abs b)
(** Returns the smallest positive integer that two integers divide by. *)
let lcm a b = match (a, b) with
| (_, 0) -> 0
| (0, _) -> 0
| (a, b) -> (abs a*b) / (gcd a b)
(* ----- STRING OPERATIONS ----- *)
(** Returns whether or not the string is empty. *)
let is_empty str = str = ""
(** Compares two strings, ignoring case. *)
let compare_ignore_case str1 str2 =
let lower1 = lowercase str1
and lower2 = lowercase str2 in
compare lower1 lower2
(**
* Returns whether or not two strings are equal, ignoring case.
*
* @see compare_ignore_case
*)
let equals_ignore_case str1 str2 = compare_ignore_case str1 str2 = 0
(**
* Returns whether or not one string begins with another.
*
* @param prefix prefix to be searched for in str
* @param str string to be tested if it begins with prefix
* @result true if str begins with prefix, otherwise false
* @see ends_with
*)
let begins_with prefix str =
let str_len = String.length str
and pref_len = String.length prefix in
if str_len < pref_len
then false
else String.sub str 0 pref_len = prefix
(**
* Returns whether or not one string ends with another.
*
* @param suffix suffix to be searched for in str
* @param str string to be tested if it ends with suffix
* @result true if str ends with suffix, otherwise false
* @see begins_with
*)
let ends_with suffix str =
let str_len = String.length str
and suff_len = String.length suffix in
let pos = str_len-suff_len in
if str_len < suff_len
then false
else String.sub str pos suff_len = suffix
(**
* Returns whether or not the string contains the substring.
* This function is tail-recursive.
*
* @param substr substring to search for in the string
* @param str string to be searched for the substring
* @result true if the string contains the substring, otherwise false
*)
let rec contains_substring substr str = match str with
| "" -> false
| _ -> if begins_with substr str
then true
else
let str_len = (String.length str)-1 in
let new_str = String.sub str 1 str_len in
contains_substring substr new_str
(**
* Returns the index within the string of the character ch.
* Throws Not_found if the character does not occur in the string.
*
* @param ch character to search for in the string
* @param str string to search for the character ch
* @result index of the first position where ch occurs in str
* @see index_of
* @see last_index_of_char
*)
let index_of_char ch str = index str ch
(**
* Returns the index within the string str of the string substr.
* Throws Not_found if the string does not occur in the string str.
* This function is tail-recursive.
*
* @param substr string to search for in the string str
* @param str string to search for the string substr
* @result index of the first position where substr occurs in str
* @see index_of_char
* @see last_index_of
*)
let index_of substr str =
if substr = ""
then 0
else
let ch = substr.[0] in
let rec fn from =
let i = index_from str from ch in
let sub_len = String.length substr in
let found = String.sub str i sub_len in
if found = substr
then i
else fn (i+1) in
fn 0
(**
* Returns the last index within the string of the character ch.
* Throws Not_found if the character does not occur in the string.
*
* @param ch character to search for in the string
* @param str string to search for the character ch
* @result index of the last position where ch occurs in the string
* @see index_of_char
* @see last_index_of
*)
let last_index_of_char ch str = rindex str ch
(**
* Returns the last index within the string str of the string substr.
* Throws Not_found if the string does not occur in the string str.
* This function is tail-recursive.
*
* @param substr string to search for in the string str
* @param str string to search for the string substr
* @result index of the last position where substr occurs in str
* @see index_of
* @see last_index_of_char
*)
let last_index_of substr str =
let last_index = String.length str - 1 in
if substr = ""
then last_index
else let sub_len = String.length substr
and ch = substr.[0] in
let rec fn from =
let i = rindex_from str from ch in
let found = String.sub str i sub_len in
if found = substr
then i
else fn (i-1) in
fn last_index
(**
* Replaces all occurances of a character within
* the string with a different character.
*
* @param old character to replace in the string
* @param nu character to replace the old character with
* @param str string in which to replace occurances of a character
* @return new string with all occurances of old replaced by nu
*)
let replace_char old nu str =
let fn ch = if ch = old
then nu
else ch in
String.map fn str
(**
* Splits the string str on sep and returns a list of the resulting strings.
* The separator string is not included in the returned list of strings.
* This function is tail-recursive.
*
* @param sep separator used to split the string
* @param str string that (possibly) contains one or more
* occurances of the separator
* @result list of strings that were separated by sep
*)
let split_on sep str =
let sep_len = String.length sep in
let rec fn acc str =
try let i = last_index_of sep str in
let pos = sep_len+i
and len = (String.length str)-sep_len-i in
let new_str = String.sub str 0 i
and substr = String.sub str pos len in
fn (substr::acc) new_str
with Not_found -> str::acc in
fn [] str
(**
* Joins the list of strings, interspersing the separator sep between them.
*
* @param sep separator to put between each pair of strings
* @param strs list of strings to be joined together, with the separator sep
* in between each pair
* @result string concatenation of the strings in strs, separated by sep
*)
let join_with sep strs = String.concat sep strs
(** Returns the string representation of a boolean value. *)
let string_of_boolean flag = match flag with
| true -> "true"
| _ -> "false"
(** Folds a list into a string. *)
let string_of_list fn lst =
fold_left fn "" lst |> sprintf "[%s]"
(** Returns the string representation of a list of integers. *)
let string_of_int_list lst =
let fn a n = if a = ""
then sprintf "%d" n
else sprintf "%s; %d" a n in
string_of_list fn lst
(** Returns the string representation of a list of strings. *)
let string_of_string_list lst =
let fn a x = if a = ""
then sprintf "\"%s\"" x
else sprintf "%s; \"%s\"" a x in
string_of_list fn lst
(** ----- MISCELLANEOUS FUNCTIONS ----- *)
(** Returns the identity of a value. *)
let id x = x
(**
* Extracts the second element of a list,
* which must have at least two elements.
*
* @param lst the list from which to extract the second element
* @return second element of list lst
*)
let second lst = match lst with
| _::x::xs -> x
| _ -> failwith "second"
(* ----- FUNCTION TESTS ----- *)
type boolean_test = { test : string; expect : bool; result : bool }
type int_test = { test : string; expect : int; result : int }
type string_test = { test : string; expect : string; result : string }
type int_list_test = { test : string; expect : int list; result : int list }
type string_list_test = { test : string; expect : string list; result : string list }
type test =
| BooleanTest of boolean_test
| IntTest of int_test
| StringTest of string_test
| IntListTest of int_list_test
| StringListTest of string_list_test
(** Prints the result of performing the test. *)
let print_test_result x =
let print_test test expect result =
let msg = test ^ " => " ^ result in
print_endline @@ if expect = result
then msg
else msg ^ " *** BUT EXPECTED "^ expect in
match x with
| BooleanTest x -> print_test x.test
(string_of_boolean x.expect)
(string_of_boolean x.result)
| IntTest x -> print_test x.test
(string_of_int x.expect)
(string_of_int x.result)
| StringTest x -> print_test x.test x.expect x.result
| IntListTest x -> print_test x.test
(string_of_int_list x.expect)
(string_of_int_list x.result)
| StringListTest x -> print_test x.test
(string_of_string_list x.expect)
(string_of_string_list x.result)
let () =
let p = ["alpha"; "bravo"; "charlie"; "delta"; "echo"]
and q = ["foxtrot"; "golf"; "hotel"; "igloo"; "juliet"]
and r = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
and n = 42 in
let t = (p, q) in
let tests = [
StringListTest { test = "fst t";
expect = ["alpha"; "bravo"; "charlie"; "delta"; "echo"];
result = fst t };
StringListTest { test = "snd t";
expect = ["foxtrot"; "golf"; "hotel"; "igloo"; "juliet"];
result = snd t };
BooleanTest { test = "even n"; expect = true; result = even n };
BooleanTest { test = "odd n"; expect = false; result = odd n };
IntTest { test = "signum n"; expect = 1; result = signum n };
IntTest { test = "negate n"; expect = (-42); result = negate n };
IntTest { test = "gcd 42 12"; expect = 6; result = gcd 42 12 };
IntTest { test = "lcm 42 12"; expect = 84; result = lcm 42 12 };
IntTest { test = "minimum r"; expect = 1; result = minimum r };
IntTest { test = "maximum r"; expect = 10; result = maximum r };
IntTest { test = "sum r"; expect = 55; result = sum r };
IntTest { test = "product r"; expect = 3628800; result = product r };
BooleanTest { test = "is_empty \"\"";
expect = true;
result = is_empty "" };
BooleanTest { test = "is_empty \"NOT!\"";
expect =false;
result = is_empty "NOT!" };
IntTest { test = "compare_ignore_case \"JESUS\" \"Jesus\"";
expect = 0;
result = compare_ignore_case "JESUS" "Jesus" };
(*
BooleanTest { test = "begins_with \"H\" \"Hello\"";
expect = true;
test = begins_with "H" "Hello" };
*)
BooleanTest { test = "ends_with '!' \"Hello!\"";
expect = true;
result = ends_with "!" "Hello, World!" };
BooleanTest { test = "contains_substring \"World\" \"Hello, World!\"";
expect = true;
result = contains_substring "World" "Hello, World!" };
BooleanTest { test = "contains_substring \"world\" \"Hello, World!\"";
expect = false;
result = contains_substring "world" "Hello, World!" };
IntTest { test = "index_of_char '!' \"Hello!, World!\"";
expect = 5;
result = index_of_char '!' "Hello!, World!" };
IntTest { test = "index_of \"World\" \"HelloW, World!\"";
expect = 8;
result = index_of "World" "HelloW, World!" };
IntTest { test = "last_index_of_char '!' \"Hello!, World!\"";
expect = 13;
result = last_index_of_char '!' "Hello!, World!" };
IntTest { test = "last_index_of \"o!\" \"Hello!, World!\"";
expect = 4;
result = last_index_of "o!" "Hello!, World!" };
StringTest { test = "replace_char 'l' 'x' \"Hello, World!\"";
expect = "Hexxo, Worxd!";
result = replace_char 'l' 'x' "Hello, World!" };
BooleanTest { test = "null p"; expect = false; result = null p };
IntTest { test = "length p"; expect = 5; result = length p };
BooleanTest { test = "elem 3 r"; expect = true; result = elem 3 r };
StringTest { test = "head q"; expect = "foxtrot"; result = head q };
StringTest { test = "second q"; expect = "golf"; result = second q };
StringListTest { test = "tail q";
expect = ["golf"; "hotel"; "igloo"; "juliet"];
result = tail q };
StringListTest { test = "init p";
expect = ["alpha"; "bravo"; "charlie"; "delta"];
result = init p };
StringTest { test = "last p"; expect = "echo"; result = last p };
IntListTest { test = "reverse r";
expect = [10; 9; 8; 7; 6; 5; 4; 3; 2; 1];
result = reverse r };
StringListTest { test = "append p q";
expect = ["alpha"; "bravo"; "charlie"; "delta"; "echo"; "foxtrot"; "golf"; "hotel"; "igloo"; "juliet"];
result = append p q };
StringListTest { test = "reverse_append p q";
expect = ["echo"; "delta"; "charlie"; "bravo"; "alpha"; "foxtrot"; "golf"; "hotel"; "igloo"; "juliet"];
result = reverse_append p q };
StringListTest { test = "concat [p; q]";
expect = ["alpha"; "bravo"; "charlie"; "delta"; "echo"; "foxtrot"; "golf"; "hotel"; "igloo"; "juliet"];
result = concat [p; q] };
StringListTest { test = "take 2 p";
expect = ["alpha"; "bravo"];
result = take 2 p };
StringListTest { test = "drop 2 p";
expect = ["charlie"; "delta"; "echo"];
result = drop 2 p };
IntTest { test = "foldl (fun a n -> a-n) 0 r";
expect = -55;
result = foldl (fun a n -> a-n) 0 r };
IntTest { test = "foldr (fun n a -> n+a) r 0";
expect = 55;
result = foldr (fun n a -> n+a) r 0 };
IntTest { test = "foldl' (fun a n -> a-n) r";
expect = -53;
result = foldl' (fun a n -> a-n) r };
IntTest { test = "foldr' (fun a n -> a+n) r";
expect = 55;
result = foldr' (fun a n -> a+n) r };
IntListTest { test = "map String.length p";
expect = [5; 5; 7; 5; 4];
result = map String.length p };
StringListTest { test = "split_on \";\" \"aaa;bbb;;ccc\"";
expect = ["aaa"; "bbb"; ""; "ccc"];
result = split_on ";" "aaa;bbb;;ccc" };
StringTest { test = "join_with \", \" [\"aaa\"; \"bbb\"; \"ccc\"]";
expect = "aaa, bbb, ccc";
result = join_with ", " ["aaa"; "bbb"; "ccc"] } ] in
printf "p => %s\n" (string_of_string_list p);
printf "q => %s\n" (string_of_string_list q);
printf "r => %s\n" (string_of_int_list r);
printf "t => (%s, %s)\n" (string_of_string_list p) (string_of_string_list q);
print_endline "";
List.iter print_test_result tests;
print_endline ""