Difference between revisions of "Binary Search Tree Assignment"

From CSE425S Wiki
Jump to navigation Jump to search
Line 80: Line 80:
  
 
===required (at minimum) tree===
 
===required (at minimum) tree===
<nowiki>(* TODO: replace unit with the datatype(s) and/or type synonym(s) you decide upon *)
+
<syntaxhighlight lang="sml">
type ('a,'k) tree = unit</nowiki>
+
 
 +
(* TODO: replace unit with the datatype(s) and/or type synonym(s) you decide upon *)
 +
type ('a,'k) tree = unit
 +
</syntaxhighlight>
 +
 
 
==create_empty==
 
==create_empty==
<nowiki>fun create_empty(cmp : 'k compare_function, to_key : ('e,'k) to_key_function) : ('e,'k) tree =
+
<syntaxhighlight lang="sml">
raise Fail("NotYetImplemented")</nowiki>
+
fun create_empty(cmp : 'k compare_function, to_key : ('e,'k) to_key_function) : ('e,'k) tree =
 +
raise Fail("NotYetImplemented")
 +
</syntaxhighlight>
  
 
==find==
 
==find==
 
reference: [https://smlfamily.github.io/Basis/list.html#SIG:LIST.find:VAL List.find]
 
reference: [https://smlfamily.github.io/Basis/list.html#SIG:LIST.find:VAL List.find]
  
<nowiki>fun find(t : ('e,'k) tree, key : 'k) : 'e option =  
+
<syntaxhighlight lang="sml">
raise Fail("NotYetImplemented")</nowiki>
+
fun find(t : ('e,'k) tree, key : 'k) : 'e option =  
 +
raise Fail("NotYetImplemented")
 +
</syntaxhighlight>
  
 
==insert==
 
==insert==
<nowiki>fun insert(t : ('e,'k) tree, element : 'e) : (('e,'k) tree * 'e option) =
+
<syntaxhighlight lang="sml">
raise Fail("NotYetImplemented")</nowiki>
+
fun insert(t : ('e,'k) tree, element : 'e) : (('e,'k) tree * 'e option) =
 +
raise Fail("NotYetImplemented")
 +
</syntaxhighlight>
  
 
NOTE: if the key for the specified item matches a key already in the tree, the previous item is replaced.
 
NOTE: if the key for the specified item matches a key already in the tree, the previous item is replaced.
Line 103: Line 113:
  
 
==remove==
 
==remove==
<nowiki>fun remove(t : ('e,'k) tree, key : 'k) : (('e,'k) tree * 'e option) =
+
<syntaxhighlight lang="sml">
raise Fail("NotYetImplemented")</nowiki>
+
fun remove(t : ('e,'k) tree, key : 'k) : (('e,'k) tree * 'e option) =
 +
raise Fail("NotYetImplemented")</syntaxhighlight>
  
 
<code>remove</code> the item whose key matches <code>item_key</code>, if it is found.
 
<code>remove</code> the item whose key matches <code>item_key</code>, if it is found.
Line 143: Line 154:
  
 
===fold_lnr===
 
===fold_lnr===
<nowiki>(*
+
<syntaxhighlight lang="sml">
 +
(*
 
  * depth-first, in-order traversal
 
  * depth-first, in-order traversal
 
  * https://en.wikipedia.org/wiki/Tree_traversal#In-order_(LNR)
 
  * https://en.wikipedia.org/wiki/Tree_traversal#In-order_(LNR)
 
  *)
 
  *)
 
fun fold_lnr(f, init, t) =  
 
fun fold_lnr(f, init, t) =  
raise Fail("NotYetImplemented")</nowiki>
+
raise Fail("NotYetImplemented")
 
+
</syntaxhighlight>
 
===fold_rnl===
 
===fold_rnl===
<nowiki>(*
+
<syntaxhighlight lang="sml">
 +
(*
 
  * depth-first, reverse in-order traversal
 
  * depth-first, reverse in-order traversal
 
  * https://en.wikipedia.org/wiki/Tree_traversal#Reverse_in-order_(RNL)
 
  * https://en.wikipedia.org/wiki/Tree_traversal#Reverse_in-order_(RNL)
 
  *)
 
  *)
 
fun fold_rnl(f, init, t) =  
 
fun fold_rnl(f, init, t) =  
raise Fail("NotYetImplemented")</nowiki>
+
raise Fail("NotYetImplemented")
 +
</syntaxhighlight>
  
 
=Debugging=
 
=Debugging=

Revision as of 18:34, 14 July 2023

We will implement a binary search tree data structure as well as Higher Order Function Hall of Fame inductees: find and fold.

Background

student record example

type course = (string*int)
type student = {first_name: string, last_name: string, wustl_key: string, bear_bucks: real, courses: course list}

val bst = BinarySearchTree.create_empty(String.compare, (fn(s:student) => (#wustl_key s)))
val (bst, _) = BinarySearchTree.insert(bst, {first_name="Bruce", last_name="Wayne", wustl_key="wayne.b", bear_bucks=999999.99, courses=[("Business", 101)]})
val (bst, _) = BinarySearchTree.insert(bst, {first_name="Peter", last_name="Parker", wustl_key="webslinger", bear_bucks=12.34, courses=[("Biology", 101)]})
val (bst, _) = BinarySearchTree.insert(bst, {first_name="Diana", last_name="Prince", wustl_key="amazon_diana", bear_bucks=234.56, courses=[("Anthropology", 101)]})
val (bst, _) = BinarySearchTree.insert(bst, {first_name="Clark", last_name="Kent", wustl_key="i.m.superman", bear_bucks=34.56, courses=[("Journalism", 101)]})
val (bst, _) = BinarySearchTree.insert(bst, {first_name="Bruce", last_name="Banner", wustl_key="gamma.ray", bear_bucks=456.78, courses=[("Physics", 101)]})

Bst example supers.svg

student record example with int as key

datatype department = COMPUTER_SCIENCE | ENGLISH | ARCHITECTURE | BIOCHEMISTRY
type student = {id: int, name: string, major: department}

val bst = BinarySearchTree.create_empty(Int.compare, fn(s:student) => #id s)
val (bst,_) = BinarySearchTree.insert(bst, {id=401936, name="Tennessee Williams", major=ENGLISH})
val (bst,_) = BinarySearchTree.insert(bst, {id=401927, name="Charles Eams", major=ARCHITECTURE})
val (bst,_) = BinarySearchTree.insert(bst, {id=401991, name="Rochelle Walensky", major=BIOCHEMISTRY})

Bst example students.svg

order

SML's General structure's order

datatype order = LESS | EQUAL | GREATER

Values of type order are used when comparing elements of a type that has a linear ordering.

Functions which take (('k * 'k) -> order) functions behave as Int.compare does:

compare (i, j)
returns LESS, EQUAL, or GREATER when i is less than, equal to, or greater than j, respectively.

list

We will implement some of the higher ordered functions list provides on our binary tree.

traversal order

Code to Implement

file: src/main/sml/binary_tree/binary_tree.sml Smlnj-logo.png
functions: create_empty
find
insert
remove
fold_lnr
fold_rnl
debug_message
to_graphviz_dot
signature BINARY_SEARCH_TREE = sig
    type 'k compare_function = (('k * 'k) -> order)
    type ('e,'k) to_key_function = 'e -> 'k

    type ('e,'k) tree;

    val create_empty : ('k compare_function * ('e,'k) to_key_function) -> ('e,'k) tree

    val find : (('e,'k) tree * 'k) -> 'e option
    val insert : (('e,'k) tree * 'e) -> (('e,'k) tree * 'e option)
    val remove : (('e,'k) tree * 'k) -> (('e,'k) tree * 'e option)

    val fold_lnr : ((('e * 'b) -> 'b) * ('b) * (('e,'k) tree)) -> 'b 
    val fold_rnl : ((('e * 'b) -> 'b) * ('b) * (('e,'k) tree)) -> 'b 
    
    val debug_message : (('e -> string) * (('e,'k) tree)) -> string
    val to_graphviz_dot : (('e -> string) * ('k -> string) * (('e,'k) tree)) -> string
end

type

provided compare_function and to_key_function type synonyms

type 'k compare_function = (('k * 'k) -> order)
type ('e,'k) to_key_function = 'e -> 'k

required (at minimum) tree

(* TODO: replace unit with the datatype(s) and/or type synonym(s) you decide upon *)
type ('a,'k) tree = unit

create_empty

fun create_empty(cmp : 'k compare_function, to_key : ('e,'k) to_key_function) : ('e,'k) tree =
	raise Fail("NotYetImplemented")

find

reference: List.find

fun find(t : ('e,'k) tree, key : 'k) : 'e option = 
	raise Fail("NotYetImplemented")

insert

fun insert(t : ('e,'k) tree, element : 'e) : (('e,'k) tree * 'e option) =
	raise Fail("NotYetImplemented")

NOTE: if the key for the specified item matches a key already in the tree, the previous item is replaced.

NOTE: it is critical to build the tree "on the way out" of the recursion. This is much like one must build the list "on the way out" in the Remove First and Eliminate Unsorted assignments.

insert returns a pair containing the new tree and the (optional) replaced value.

remove

fun remove(t : ('e,'k) tree, key : 'k) : (('e,'k) tree * 'e option) =
	raise Fail("NotYetImplemented")

remove the item whose key matches item_key, if it is found.

returns a pair of the modified tree and the (optional) removed item.

Remove contains the most challenging aspect of this studio. When instructed to remove a node from a tree, there are several cases:

not found

What will indicate that you reached the point where you know the node is not found in the tree?
note: this has a trivial solution.

no child in the left tree

How will you detect this pattern?
note: this has a trivial solution.

no child in the right tree

how will you detect this pattern?
note: this has a trivial solution.

both children are present

If you need to remove a node and it has both children, now you have a legit problem. You must maintain a correct binary search tree.

standard approach

A common approach is to choose one of the following:

  • remove the right most descendant in the left child, and promote it to be the node at the current level, or
  • remove the left most descendant in the right child, and promote it to be the node at the current level

The image below shows finding the left most child in the right subtree for promotion:

AVL-tree-delete.svg

Building a helper function will likely be helpful.

Wikipedia BST Deletion

alternate approach?

Can you come up with a different approach that produces a clean solution while still providing O(lg(N)) expected performance?

fold

reference: List.foldl foldr

LNR would produce: ABCDEFGHI
RNL would produce: IHGFEDCBA

fold_lnr

(*
 * depth-first, in-order traversal
 * https://en.wikipedia.org/wiki/Tree_traversal#In-order_(LNR)
 *)
fun fold_lnr(f, init, t) = 
	raise Fail("NotYetImplemented")

fold_rnl

(*
 * depth-first, reverse in-order traversal
 * https://en.wikipedia.org/wiki/Tree_traversal#Reverse_in-order_(RNL)
 *)
fun fold_rnl(f, init, t) = 
	raise Fail("NotYetImplemented")

Debugging

debug_message

fun debug_message(element_to_string, t) =
	"(Optional) TODO: BinarySearchTree.debug_message"

debug_binary_search_tree

file: debug_binary_search_tree.sml

in folder: src/test/sml/run_binary_search_tree_testing

BinarySearchTree to_graphviz_dot

	fun to_graphviz_dot(element_to_string, key_to_string, t) =
		let
			
			(* TODO: bind root *)
			val root = raise Fail("NotYetImplemented")
			(* TODO: bind to_key *)
			val to_key = raise Fail("NotYetImplemented")
			

			fun node_to_dot(element) =
				"\t" ^ key_to_string(to_key(element)) ^ " [label= \"{ " ^ element_to_string(element) ^ " | { <child_left> | <child_right> } }\"]"

			fun edge_to_dot(parent_element_opt, tag, element) = 
				case parent_element_opt of
				  NONE => ""
				| SOME(parent_element) => "\t" ^ key_to_string(to_key(parent_element)) ^ tag ^ " -> " ^ key_to_string(to_key(element))

			fun nodes_to_dot(bst) =
					raise Fail("NotYetImplemented")

			fun edges_to_dot(bst, parent_element_opt, tag) =
					raise Fail("NotYetImplemented")

		in
			"digraph g {\n\n\tnode [\n\t\tshape = record\n\t]\n\n\tedge [\n\t\ttailclip=false,\n\t\tarrowhead=vee,\n\t\tarrowtail=dot,\n\t\tdir=both\n\t]\n\n" ^ nodes_to_dot(root) ^ edges_to_dot(root, NONE, "") ^ "\n}\n"
		end

graphviz dot vs code

search for "graphviz dot" in vs code extensions marketplace

Testing

Fall 22 Note:

the command line option:

 --ignore_potential_remove_problems_for_full_credit=true 

may be used to ignore some of the problems in remove and still receive credit.

A student reported experiencing a bug which slipped by the BinarySearchTesting but showed up in the SortedDictionary testing.  To help with debugging this type of problem, I added BinarySearchTree tests.  I did not want to penalize people with late arriving tests so the  --ignore_potential_remove_problems_for_full_credit=true switch exists for dishing out credit.

Complete

source folder: src/test/sml/binary_search_tree
how to run with CM.make verbosity off: sml -Ccm.verbose=false run_binary_search_tree_testing.sml
how to run with CM.make verbosity on: sml run_binary_search_tree_testing.sml

note: ensure that you have removed all printing to receive credit for any assignment.

SML Error Messages

Without Remove

sml -Ccm.verbose=false run_binary_search_tree_testing.sml --remove=false
sml run_binary_search_tree_testing.sml --remove=false

Pledge, Acknowledgments, Citations

file: studio-binary-search-tree-pledge-acknowledgments-citations.txt

More info about the Honor Pledge