How can we insert an element in a BST in Haskell
Overview
Haskell is a functional programming language. It is a type of programming paradigm where the focus is on changing data through expressions that don’t have side effects. These expressions are often called pure functions. Let’s see how we can implement a pure function, which inserts an element in a binary search tree (BST).
Recursive solution
The most intuitive way to insert an element in a BST is by using recursion. We use the following algorithm for recursion:
- If the tree is empty, then we insert the
rootnode. - If the inserted key is less than the
rootkey, then we recurse at theleftsubtree. Otherwise, we recurse at therightsubtree. When the tree ends, the function inserts at the left (if the inserted key is less than the current key), and vice versa.
data Tree a = Nil | Node (Tree a) (Tree a) aindentation:: Int-> Stringindentation = \indents ->case indents of0 -> ""1 -> "+---"_ -> "| " ++ indentation (indents-1)printTree:: Show a => Tree a -> Int -> StringprintTree = \tree -> \level ->case tree ofNil -> ""Node Nil Nil b -> (show b)Node left Nil b -> (show b) ++ "\n" ++ indentation (level) ++ (printTree left (level+1))Node Nil right b -> (show b) ++ "\n" ++ indentation (level) ++ (printTree right (level+1))Node left right b -> (show b) ++ "\n" ++ indentation (level) ++ (printTree left (level+1)) ++ "\n" ++ indentation (level) ++ (printTree right (level+1))instance Show a => Show (Tree a) whereshow = \tree ->printTree tree 1insert :: Ord a => Tree (a,b) -> (a,b) -> Tree (a,b)insert = \tree -> \(x,y) ->case tree ofNil -> Node Nil Nil (x,y) -- if empty, then insert rootNode left right (a,b) | x < a -> Node (insert left (x,y)) right (a,b)| otherwise -> Node left (insert right (x,y)) (a,b)main = print (insert (insert (insert (insert Nil (7, "Seven")) (8, "Eight")) (6, "Six")) (5, "Five"))
Code explanation
In the code given above:
-
In line 1, we define the datatype
Tree, which can either be an empty tree or a node that contains two subtrees and a value. As we can infer,Nilrepresents an empty tree, and aNodecontains two subtrees of typeTreeand a value whose type will be determined on function invocation. -
For the sake of simplicity, we do not need to concern ourselves with the internal workings of
indentationandprintTree. Their purpose is to neatly print the tree when we use theinsertfunction. -
In lines 18–21, we make
Treean instance of theShowtype class, by defining theshowfunction that takes aTreeand returns astring. -
We consider a tree that contains a tuple of key-value pairs such that the tree is organized as a binary search tree based on the key value. A binary search tree stores all the keys that are less than the one in the
rootin theleftsubtree and stores the rest in therightsubtree. We insert new values in the tree, according to these above constraints. -
In line 23,
Ord a => Tree (a,b) -> (a,b) -> Tree (a,b)is the type annotation for ourinsertfunction. Haskell is statically typed, and every function or variable has a type. In this case,insertis a function that takes aTreeand a tuple, and returns aTree(the tree with a newly inserted value). TheOrd aensures that the first element of the tuple should be of a totally ordered datatype. -
Next, in line 24, we have the function definition, which shows that our function takes a
Tree, a tuple(x,y), and acaseexpression onTreethat implements our recursive formulation of the problem. -
In line 26, The first equation tells that if the
TreeisNil, then this means that it is an empty tree and we need to insert therootof the tree. Hence, we returnNode Nil Nil (x,y). -
In line 27, The second equation solves the general case. We have a
Treewhich is aNodewithleftandrightsubtrees and a key-value pair(x,y). If the key to be inserted,a, is less thanx, then we return the tree where the recursive function is called at theleftsubtree. Otherwise, we recurse at therightsubtree. -
Finally, in line 29, we generate the whole tree by invoking our
insertfunction and calling theprintTreefunction to print the final tree.