all week2's main funs are done
This commit is contained in:
parent
2159410dac
commit
304d944679
8 changed files with 530 additions and 0 deletions
BIN
sml/week2/assignment.pdf
Normal file
BIN
sml/week2/assignment.pdf
Normal file
Binary file not shown.
83
sml/week2/card.sml
Normal file
83
sml/week2/card.sml
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use "list.sml";
|
||||||
|
|
||||||
|
datatype color = Black | Red
|
||||||
|
datatype rank = Ace | Queen | King | Jack | Num of int
|
||||||
|
datatype suit = Club | Diamond | Heart | Spade
|
||||||
|
type card = suit * rank
|
||||||
|
datatype move = Draw | Discard of card
|
||||||
|
|
||||||
|
exception IllegalMove
|
||||||
|
|
||||||
|
type card_color = card -> color
|
||||||
|
val card_color: card_color = fn card =>
|
||||||
|
case card of
|
||||||
|
(Club, _) => Black
|
||||||
|
| (Spade, _) => Black
|
||||||
|
| (Diamond, _) => Red
|
||||||
|
| (Heart, _) => Red
|
||||||
|
|
||||||
|
type card_value = card -> int
|
||||||
|
val card_value: card_value = fn (_, rank) =>
|
||||||
|
case rank of
|
||||||
|
Num num => num
|
||||||
|
| Ace => 11
|
||||||
|
| _ => 10
|
||||||
|
|
||||||
|
type remove_card = card list * card * exn -> card list
|
||||||
|
val remove_card: remove_card = fn (cards, to_remove, exp) =>
|
||||||
|
let
|
||||||
|
fun filter card (found, acc) =
|
||||||
|
if card <> to_remove orelse found
|
||||||
|
then (found, card :: acc)
|
||||||
|
else (true orelse found, acc)
|
||||||
|
in
|
||||||
|
case fold filter (false, []) cards of
|
||||||
|
(true, filtered) => filtered
|
||||||
|
| (false, _) => raise exp
|
||||||
|
end
|
||||||
|
|
||||||
|
type all_same_color = card list -> bool
|
||||||
|
val rec all_same_color: all_same_color = fn cards =>
|
||||||
|
case cards of
|
||||||
|
[] => true
|
||||||
|
| one :: [] => true
|
||||||
|
| first :: second :: rest =>
|
||||||
|
card_color first = card_color second andalso all_same_color rest
|
||||||
|
|
||||||
|
type sum_cards = card list -> int
|
||||||
|
val sum_cards: sum_cards = fn cards =>
|
||||||
|
cards |> fold (fn card => fn sum => card_value card + sum) 0
|
||||||
|
|
||||||
|
type score = card list * int -> int
|
||||||
|
val score: score = fn (cards, goal) =>
|
||||||
|
let
|
||||||
|
val sum = sum_cards cards
|
||||||
|
val preliminary_score =
|
||||||
|
if sum < goal
|
||||||
|
then goal - sum
|
||||||
|
else 3 * (sum - goal)
|
||||||
|
val same_color = all_same_color cards
|
||||||
|
in
|
||||||
|
if same_color then preliminary_score div 2 else preliminary_score
|
||||||
|
end
|
||||||
|
|
||||||
|
type officiate = card list * move list * int -> int
|
||||||
|
val officiate: officiate = fn (deck, moves, goal) =>
|
||||||
|
let
|
||||||
|
fun draw (deck, hand) =
|
||||||
|
case deck of
|
||||||
|
[] => (deck, hand)
|
||||||
|
| head :: tail =>
|
||||||
|
if sum_cards hand > goal
|
||||||
|
then (deck, hand)
|
||||||
|
else (tail, head :: hand)
|
||||||
|
|
||||||
|
fun play move (deck, hand) =
|
||||||
|
case move of
|
||||||
|
Draw => draw (deck, hand)
|
||||||
|
| Discard card => (deck, remove_card (hand, card, IllegalMove))
|
||||||
|
|
||||||
|
fun round hand = score (hand, goal)
|
||||||
|
in
|
||||||
|
moves |> fold play (deck, []) |> #2 |> round
|
||||||
|
end
|
212
sml/week2/card_tests.sml
Normal file
212
sml/week2/card_tests.sml
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
use "test.sml";
|
||||||
|
use "card.sml";
|
||||||
|
|
||||||
|
val () =
|
||||||
|
assert (card_color (Spade, King) = Black)
|
||||||
|
"card_color: returns right color"
|
||||||
|
|
||||||
|
val () =
|
||||||
|
assert (card_color (Heart, King) = Red)
|
||||||
|
"card_color: returns right color x2"
|
||||||
|
|
||||||
|
val () =
|
||||||
|
assert (card_value (Heart, Num 5) = 5)
|
||||||
|
"card_value: number card"
|
||||||
|
|
||||||
|
val () =
|
||||||
|
assert (card_value (Heart, Ace) = 11)
|
||||||
|
"card_value: ace is 11"
|
||||||
|
|
||||||
|
val () =
|
||||||
|
assert (card_value (Heart, Jack) = 10)
|
||||||
|
"card_value: jack is 10"
|
||||||
|
|
||||||
|
val () =
|
||||||
|
assert (remove_card ([(Heart, Jack)], (Heart, Jack), IllegalMove) = [])
|
||||||
|
"remove_card: removes card"
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = [(Heart, Jack), (Heart, Jack)]
|
||||||
|
val card = (Heart, Jack)
|
||||||
|
val expected = [(Heart, Jack)]
|
||||||
|
val exp = IllegalMove
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
(remove_card (deck, card, exp) = expected)
|
||||||
|
"remove_card: leaves duplicates"
|
||||||
|
end
|
||||||
|
|
||||||
|
val _ =
|
||||||
|
let
|
||||||
|
val deck = []
|
||||||
|
val card = (Heart, Jack)
|
||||||
|
val expected = IllegalMove
|
||||||
|
val exp = IllegalMove
|
||||||
|
in
|
||||||
|
remove_card (deck, card, exp)
|
||||||
|
handle IllegalMove =>
|
||||||
|
let
|
||||||
|
val () = assert true "remove_card: raises exception"
|
||||||
|
in
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = []
|
||||||
|
val expected = true
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ all_same_color deck = expected
|
||||||
|
$ "all_same_color: true on empty list"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = [(Club, Jack)]
|
||||||
|
val expected = true
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ all_same_color deck = expected
|
||||||
|
$ "all_same_color: true on one value"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = [(Club, Jack), (Diamond, Jack)]
|
||||||
|
val expected = false
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ all_same_color deck = expected
|
||||||
|
$ "all_same_color: false on two different colors"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = []
|
||||||
|
val expected = 0
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ sum_cards deck = expected
|
||||||
|
$ "sum_cards: zero on empty list"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = [(Club, Jack), (Diamond, Ace)]
|
||||||
|
val expected = 21
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ sum_cards deck = expected
|
||||||
|
$ "sum_cards: correct sum"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = []
|
||||||
|
val goal = 20
|
||||||
|
val expected = 10
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ score (deck, goal) = expected
|
||||||
|
$ "score: empty list returns goal / 2 because empty deck = same color"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = [(Club, Jack), (Diamond, Jack)]
|
||||||
|
val goal = 21
|
||||||
|
val expected = 1
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ score (deck, goal) = expected
|
||||||
|
$ "score: when sum < goal then score = goal - sum"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = [(Club, Jack), (Diamond, Jack)]
|
||||||
|
val goal = 9
|
||||||
|
val expected = 33
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ score (deck, goal) = expected
|
||||||
|
$ "score: when sum > goal then score = 3 * (sum - goal)"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = [(Club, Jack), (Club, Ace)]
|
||||||
|
val goal = 18
|
||||||
|
val expected = 4
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ score (deck, goal) = expected
|
||||||
|
$ "score: when all cards are the same color score is divied by 2"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = [(Club, Jack), (Club, Ace)]
|
||||||
|
val moves = [Draw, Draw]
|
||||||
|
val goal = 18
|
||||||
|
val expected = 4
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ officiate (deck, moves, goal) = expected
|
||||||
|
$ "officiate: player draw all deck, get correct result"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = [(Club, Jack)]
|
||||||
|
val moves = [Draw, Draw]
|
||||||
|
val goal = 21
|
||||||
|
val expected = 5
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ officiate (deck, moves, goal) = expected
|
||||||
|
$ "officiate: game stops on empty deck even if there are moves"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = [(Club, Jack), (Club, Num 3), (Club, Num 10), (Club, Jack)]
|
||||||
|
val moves = [Draw, Draw, Draw, Draw]
|
||||||
|
val goal = 19
|
||||||
|
val expected = 6
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ officiate (deck, moves, goal) = expected
|
||||||
|
$ "officiate: game stops on sum > goal"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val deck = [(Club, Jack), (Club, Num 3), (Heart, Num 10), (Club, Jack)]
|
||||||
|
val moves = [Draw, Discard (Club, Jack), Draw, Draw]
|
||||||
|
val goal = 19
|
||||||
|
val expected = 6
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
$ officiate (deck, moves, goal) = expected
|
||||||
|
$ "officiate: player can discard cards"
|
||||||
|
end
|
||||||
|
|
||||||
|
val 0 =
|
||||||
|
let
|
||||||
|
val deck = [(Club, Jack), (Club, Num 3), (Heart, Num 10), (Club, Jack)]
|
||||||
|
val moves = [Discard (Club, Jack), Draw, Draw, Draw]
|
||||||
|
val goal = 19
|
||||||
|
val expected = 6
|
||||||
|
in
|
||||||
|
officiate (deck, moves, goal)
|
||||||
|
handle IllegalMove =>
|
||||||
|
assert true "officiate: raises exception on missing card"
|
||||||
|
|> (fn _ => 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
val () = complete ()
|
31
sml/week2/list.sml
Normal file
31
sml/week2/list.sml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use "operators.sml";
|
||||||
|
|
||||||
|
fun cons head tail = head :: tail
|
||||||
|
|
||||||
|
fun fold f acc lst =
|
||||||
|
case lst of
|
||||||
|
[] => acc
|
||||||
|
| head :: tail => fold f (f head acc) tail
|
||||||
|
|
||||||
|
fun reverse lst =
|
||||||
|
let
|
||||||
|
fun f elm acc = elm :: acc
|
||||||
|
in
|
||||||
|
fold f [] lst
|
||||||
|
end
|
||||||
|
|
||||||
|
fun filter predicate lst =
|
||||||
|
let
|
||||||
|
fun f elm acc = if predicate elm then elm :: acc else acc
|
||||||
|
in
|
||||||
|
lst |> fold f [] |> reverse
|
||||||
|
end
|
||||||
|
|
||||||
|
fun empty lst = lst = []
|
||||||
|
|
||||||
|
(* not efficient but works *)
|
||||||
|
fun exists elem lst =
|
||||||
|
lst
|
||||||
|
|> filter (fn needle => elem = needle)
|
||||||
|
|> empty
|
||||||
|
|> not
|
70
sml/week2/name.sml
Normal file
70
sml/week2/name.sml
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
use "operators.sml";
|
||||||
|
use "list.sml";
|
||||||
|
|
||||||
|
type FullName = {first: string, middle: string, last: string}
|
||||||
|
|
||||||
|
(* too complex *)
|
||||||
|
fun all_except_option (str, lst) =
|
||||||
|
if exists str lst
|
||||||
|
then SOME $ filter (fn elem => elem <> str) lst
|
||||||
|
else NONE
|
||||||
|
|
||||||
|
(* more straightforward *)
|
||||||
|
fun all_except_option (str, lst) =
|
||||||
|
let
|
||||||
|
val filtered = filter (fn elem => elem <> str) lst
|
||||||
|
in
|
||||||
|
if lst = filtered then NONE else SOME filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
(* maybe it's what assigment requires us to do but I don't like it *)
|
||||||
|
(* how to get it in 8 lines?! *)
|
||||||
|
fun all_except_option (str, lst) =
|
||||||
|
let
|
||||||
|
fun filter (same_lst, acc) lst =
|
||||||
|
case lst of
|
||||||
|
[] => (same_lst, acc)
|
||||||
|
| head :: tail =>
|
||||||
|
if head = str
|
||||||
|
then filter (false andalso same_lst, acc) tail
|
||||||
|
else filter (same_lst, head :: acc) tail
|
||||||
|
in
|
||||||
|
case filter (true, []) lst of
|
||||||
|
(false, lst) => SOME $ reverse lst
|
||||||
|
| (true, _) => NONE
|
||||||
|
end
|
||||||
|
|
||||||
|
fun get_substitutions1 (
|
||||||
|
substitutions: string list list,
|
||||||
|
name: string
|
||||||
|
): string list =
|
||||||
|
let
|
||||||
|
fun collect lst acc =
|
||||||
|
case all_except_option (name, lst) of
|
||||||
|
NONE => acc
|
||||||
|
| SOME names => acc @ names
|
||||||
|
in
|
||||||
|
fold collect [] substitutions
|
||||||
|
end
|
||||||
|
|
||||||
|
(* lol, previous function is already tail recursive 😎 *)
|
||||||
|
fun get_substitutions2 (
|
||||||
|
substitutions: string list list,
|
||||||
|
name: string
|
||||||
|
): string list = get_substitutions1(substitutions, name)
|
||||||
|
|
||||||
|
|
||||||
|
fun similar_names (
|
||||||
|
substitutions: string list list,
|
||||||
|
{first=first, middle=middle, last=last}: FullName
|
||||||
|
): FullName list =
|
||||||
|
let
|
||||||
|
fun full_substitution name acc =
|
||||||
|
{first=name, middle=middle, last=last} :: acc
|
||||||
|
in
|
||||||
|
get_substitutions1 (substitutions, first)
|
||||||
|
|> fold full_substitution []
|
||||||
|
|> reverse
|
||||||
|
|> cons {first=first, middle=middle, last=last}
|
||||||
|
end
|
||||||
|
|
96
sml/week2/name_tests.sml
Normal file
96
sml/week2/name_tests.sml
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
use "name.sml";
|
||||||
|
use "test.sml";
|
||||||
|
|
||||||
|
val () =
|
||||||
|
assert
|
||||||
|
(all_except_option ("str", []) = NONE)
|
||||||
|
"all_except_option: NONE on empty list"
|
||||||
|
|
||||||
|
val () =
|
||||||
|
assert
|
||||||
|
(all_except_option ("str", ["str"]) = SOME [])
|
||||||
|
"all_except_option: filters out matched string"
|
||||||
|
|
||||||
|
val () =
|
||||||
|
assert
|
||||||
|
(all_except_option ("str", ["str", "bar"]) = SOME ["bar"])
|
||||||
|
"all_except_option: filters only necessary elements"
|
||||||
|
|
||||||
|
val () =
|
||||||
|
assert
|
||||||
|
(all_except_option ("str", ["str", "bar", "str"]) = SOME ["bar"])
|
||||||
|
"all_except_option: filters all matches"
|
||||||
|
|
||||||
|
val () =
|
||||||
|
assert
|
||||||
|
(all_except_option ("str", ["str", "bar", "baz"]) = SOME ["bar", "baz"])
|
||||||
|
"all_except_option: preserve order"
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val substitutions = [
|
||||||
|
["Fred","Fredrick"],
|
||||||
|
["Elizabeth","Betty"],
|
||||||
|
["Freddie","Fred","F"]
|
||||||
|
]
|
||||||
|
val name = "Fred"
|
||||||
|
val expect = ["Fredrick","Freddie","F"]
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
(get_substitutions1 (substitutions, name) = expect)
|
||||||
|
"get_substitutions1: collects substitutions"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val substitutions = [
|
||||||
|
["Fred","Fredrick"],
|
||||||
|
["Jeff","Jeffrey"],
|
||||||
|
["Geoff","Jeff","Jeffrey"]
|
||||||
|
]
|
||||||
|
val name = "Jeff"
|
||||||
|
val expect = ["Jeffrey","Geoff","Jeffrey"]
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
(get_substitutions1 (substitutions, name) = expect)
|
||||||
|
"get_substitutions1: collect duplicates"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val substitutions = [
|
||||||
|
["Fred","Fredrick"],
|
||||||
|
["Jeff","Jeffrey"],
|
||||||
|
["Geoff","Jeff","Jeffrey"]
|
||||||
|
]
|
||||||
|
val name = "Jeff"
|
||||||
|
val expect = ["Jeffrey","Geoff","Jeffrey"]
|
||||||
|
val res1 = get_substitutions1 (substitutions, name)
|
||||||
|
val res2 = get_substitutions2 (substitutions, name)
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
(res1 = res2)
|
||||||
|
"get_substitutions2: works as previous one"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () =
|
||||||
|
let
|
||||||
|
val substitutions = [
|
||||||
|
["Fred","Fredrick"],
|
||||||
|
["Elizabeth","Betty"],
|
||||||
|
["Freddie","Fred","F"]
|
||||||
|
]
|
||||||
|
val full_name = {first="Fred", middle="W", last="Smith"}
|
||||||
|
val expect = [
|
||||||
|
{first="Fred", last="Smith", middle="W"},
|
||||||
|
{first="Fredrick", last="Smith", middle="W"},
|
||||||
|
{first="Freddie", last="Smith", middle="W"},
|
||||||
|
{first="F", last="Smith", middle="W"}
|
||||||
|
]
|
||||||
|
in
|
||||||
|
assert
|
||||||
|
(similar_names (substitutions, full_name) = expect)
|
||||||
|
"similar_names: creates correct list"
|
||||||
|
end
|
||||||
|
|
||||||
|
val () = complete ()
|
21
sml/week2/operators.sml
Normal file
21
sml/week2/operators.sml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
(*
|
||||||
|
Pipe operator.
|
||||||
|
Simply to write code like "data |> fun" instead "fun data".
|
||||||
|
Then you can "pipe" results to functions via partial application:
|
||||||
|
|
||||||
|
fun square a b = a * b
|
||||||
|
fun sum a b = a + b
|
||||||
|
|
||||||
|
10 |> sum 10 |> square 2 // 40
|
||||||
|
*)
|
||||||
|
fun |> (x, f) = f x
|
||||||
|
|
||||||
|
(* apply operator *)
|
||||||
|
fun $ (f, x) = f x
|
||||||
|
|
||||||
|
(* composition operator *)
|
||||||
|
fun >> (f, g) x = g(f(x))
|
||||||
|
|
||||||
|
infix |>
|
||||||
|
infix $
|
||||||
|
infix >>
|
17
sml/week2/test.sml
Normal file
17
sml/week2/test.sml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
fun assert condition message =
|
||||||
|
if condition
|
||||||
|
then
|
||||||
|
print (message ^ "\n")
|
||||||
|
else
|
||||||
|
let
|
||||||
|
val () = print ("Assert error: " ^ message ^ "\n")
|
||||||
|
in
|
||||||
|
OS.Process.exit OS.Process.failure
|
||||||
|
end
|
||||||
|
|
||||||
|
fun complete () =
|
||||||
|
let
|
||||||
|
val () = print "All tests passed!"
|
||||||
|
in
|
||||||
|
OS.Process.exit OS.Process.success
|
||||||
|
end
|
Loading…
Reference in a new issue