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 ""