Splay Tree Search Implementation - java

I have been trying to learn the ins and outs of some data structures and I am trying to get a binary splay tree to work properly. Every time I run the following code and the node I am looking for is more than one past the root it tells me it is there and then just deletes that whole side from the root down. It works fine if the node is only one level down from the top.
I am not sure what is going wrong but I suppose it has something to do with my rotate functions. I got it to work properly for the insert function which is what I modeled this after.
public class SplayBST {
Node root;
int count;
int level = 0;
boolean found = false;
public SplayBST() {
root = null;
count = 0;
}
public String searchIt(String x) {
//after finishing search method I need to check if splaySearch exists then don't insert just splay it
splaySearch(root, x);
if (this.found == true) {
this.found = false;
return x;
}
else {
return null;
}
}
Node splaySearch(Node h, String x) {
if (h == null) {
return null;
}
if (x.compareTo(h.value) < 0) {
try {
if (x.compareTo(h.left.value) < 0) {
h.left.left = splaySearch(h.left.left, x);
h = rotateRight(h);
} else if (x.compareTo(h.left.value) > 0) {
h.left.right = splaySearch(h.left.right, x);
h.left = rotateLeft(h.left);
}
else {
this.found = true;
return h.left;
}
return rotateRight(h);
}
catch (NullPointerException ex) {
return null;
}
}
else { //basically x.compareTo(h.value)>0
try {
if (x.compareTo(h.right.value) > 0) {
h.right.right = splaySearch(h.right.right, x);
h = rotateLeft(h);
} else if (x.compareTo(h.right.value) < 0) {
h.right.left = splaySearch(h.right.left, x);
h.right = rotateRight(h.right);
}
else {
this.found = true;
return h.right;
}
return rotateLeft(h);
}
catch (NullPointerException ex) {
return null;
}
}
}
Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
return x;
}
Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
return x;
}
class Node {
Node left;
Node right;
String value;
int pos;
public Node(String x) {
left = null;
right = null;
value = x;
}
}
}

I second Tristan Hull's approach to creating a regular BST with a "working" search method. Once you get that working, adding a splay method is rather trivial. I've actually done the same thing when I implemented a Splay Tree in Java. It's a better software design and a simpler implementation.

Your problem is that when you rotate, you update the reference to node H in the function "SplaySearch", but you do not update the parent node in the original "searchIt" function. Thus, the program "thinks" that the original parent node remains the parent, even though the rotated node should be the parent. Thus, when you run whatever method you use to print your tree, you print from a node that is not actually the parent node of the tree, but a child (the level it is on depends on how many times your program called rotateLeft and rotateRight).
To fix this, I suggest implementing search as in a normal binary tree, and then making a "splay" function completely separate from the search function that splays a node to the top of the tree. You would call this splay function at the end of every search, making sure to properly update your reference. This is the traditional way to implement splay trees, and I suggest you take a look at it (maybe look at the wikipedia article on splay trees or do a Google search). Also, you may want to know that your rotate functions are not complete. In terms of splay trees, you also have 2 separate types of double rotations which are very different from single rotations. Again, I suggest looking up splay trees to learn about them more in depth.

Related

How to manipulate a string (move substring to other part of string) in O(log n) using a rope or an order statistics splay tree

Two weeks ago I've finished an implementation of a splay tree that allows basic functions, like insert, delete, find key and and obtain the sum of a range of elements of the three. You can find this implementation here as reference for this question or out of curiosity.
As an extra task (its optional and its past due, I'm solving this not for a grade but because I believe its a useful data structure not easy to "Google about it"), I was asked to implement a Rope data structure to manipulate strings so if the string is "warlock" and the keys given are 0 2 2, then the string would be "lowarck" (0 2 is substring "war", "lock" is whats left after removing "war" and you insert it after 2nd char so turns into "lo"+"war"+"ck").
This is just one query but it can be up to 100k queries for a 300k character long string, so a naive solution wouldnt work.
My first doubt is about initializing the tree( For the ones who have read the gist,I'll use Node instead of Vertex in order to be easy to understand for most).
This is the Node class and the NodePair class:
class Node {
char key;
int size;
Node left;
Node right;
Node parent;
Node(char key, int size, Node left, Node right, Node parent) {
this.key = key;
this.size = size;
this.left = left;
this.right = right;
this.parent = parent;
}
}
class NodePair {
Node left;
Node right;
NodePair() {
}
NodePair(Node left, Node right) {
this.left = left;
this.right = right;
}
}
After that, I create the tree this way:
StringBuffer sb = new StringBuffer(br.readLine());
Node left=null;
for (int i=0;i<sb.length();i++){
root=new Vertex(sb.charAt(i), i+1, left, null, null);
if (i!=sb.length()-1){
left=root;
}
}
This creates a very unbalanced tree where the first char of the string (as node.key) has node.size 1 and is the leftmost child; and the last char of the string is the root with size=sb.length().
I am not completely sure if this is correctly initialized. I did an inorder traversal print with key and size and I got the whole string in size order, which is what I expected.
After that I have modified the Update method from this:
void update(Node v) {
if (v == null) return;
v.sum = v.key + (v.left != null ? v.left.sum : 0) + (v.right != null ? v.right.sum : 0);
if (v.left != null) {
v.left.parent = v;
}
if (v.right != null) {
v.right.parent = v;
}
}
To this: (based on CLRS chapter 14.1)
void update(Node v) {
if (v == null) return;
v.size = 1 + (v.left != null ? v.left.size : 0) + (v.right != null ? v.right.size : 0);
if (v.left != null) {
v.left.parent = v;
}
if (v.right != null) {
v.right.parent = v;
}
}
Then the find method, from the original:
NodePair find(Node root, int key) {
Node v = root;
Node last = root;
Node next = null;
while (v != null) {
if (v.key >= key && (next == null || v.key < next.key)) {
next = v;
}
last = v;
if (v.key == key) {
break;
}
if (v.key < key) {
v = v.right;
} else {
v = v.left;
}
}
root = splay(last);
return new NodePair(next, root);
}
to this:(Based in the Order Statistics-SELECT of CLRS Chapter 14.1)
Node find(Node r, int k){
int s = r.left.size + 1;
if (k==s) return r;
else if (k < s) {
return find(r.left,k);
}
return find(r.right,k-s);
}
However I already have a problem at this point since, as you can see, the original find returns a NodePair while this method returns a Node.
The explanation of the NodePair according to instructors is:
Returns pair of the result and the new root.If found, result is a
pointer to the node with the given key.Otherwise, result is a pointer
to the node with the smallest bigger key (next value in the order). If
the key is bigger than all keys in the tree, then result is null.
This complicates my split method since it uses Find method to look for the node to split.
Besides this, I'm obtaining NullPointerException at this find method and from other students I understand that to avoid other error we should use a non-recursive version, so basically I need to implement a non-recursive version of OS-Select that returns a NodePair as the previous find method or modify my split method which is:
NodePair split(Node root, int key) {
NodePair result = new NodePair();
NodePair findAndRoot = find(root, key);
root = findAndRoot.right;
result.right = findAndRoot.left;
if (result.right == null) {
result.left = root;
return result;
}
result.right = splay(result.right);
result.left = result.right.left;
result.right.left = null;
if (result.left != null) {
result.left.parent = null;
}
update(result.left);
update(result.right);
return result;
}
As you can see, the find method is assigned to the NodePair findAndRoot.
I believe that besides the OS-Select conversion to non-recursive my main problem is to understand the way NodePair is used by the previous find method and split method.
Finally, this is my implementation of the method to receive the tree and keys and manipulate them:
Node stringManip(Node v, int i, int j, int k){
Node left;
Node right;
NodePair middleRight =split(v,j+1);
left=middleRight.left;
right=middleRight.right;
NodePair leftMiddle = split(left,i);
Node start = leftMiddle.left;
Node substr = leftMiddle.right;
Node tmp = merge(start, right);
NodePair pairString = split(tmp,k+1);
Vertex fLeft =pairString.left;
Vertex fRight = pairString.right;
root = merge(merge(fLeft,substr),fRight);
root = splay(root);
update(root);
return root;
}
As you must realize from my code, I'm a beginner with only have 5 months that started learning to program and I picked Java, so from the info I've gathered I get that this type of data structure is more in the intermediate-expert level (I'm already surprise I was capable of implementing the previous splay tree.
So please, consider my beginner level in your answer.
PD: Here's a pseudocode version of the nonrecursive OS-Select, still having NullPointerException..
OS-SELECT(x, i)
while x != null
r <- size[left[x]]+1
if i = r
then return x
elseif i < r
x = left[x]
else
x = right[x]
i = i - r

Converting a recursive method (where the recursion is done inside a loop) into an iterative method

I have a recursive algorithm that I use to iterate over a hierarchical data structure, but unfortunately with some data, the hierarchical structure is so deep that I'm getting a StackOverflowError. I've seen this happen with a depth of about 150ish nodes, while the data could potentially grow to much further than that. For context, this code will run in limited environments and changing the JVM stack size is not an option, and the data structure is a given and represents different file systems with directories and files.
To work around the stack overflow, I've tried to convert the algorithm into an iterative one. It's not something I've had to do before, so I started from some examples showing how to do this with a simple recursion, but I'm not sure how to apply this to recursion inside a loop. I've found a way to do it that seems to work, but the code is rather insane.
Here is a simplified version of my original recursive method:
private CacheEntry sumUpAndCacheChildren(Node node) {
final CacheEntry entry = getCacheEntry(node);
if (entryIsValid(entry))
return entry;
Node[] children = node.listChildren();
long size = 0;
if (children != null) {
for (Node child : children) {
if (child.hasChildren()) {
size += sumUpAndCacheChildren(child).size;
} else {
size += child.size();
}
}
}
return putInCache(node, size);
}
Each leaf node has a size, while the size for any ancestor node is considered to be the size of all of its descendants. I want to know this size for each node, so the size is aggregated and cached for every node.
Here is the iterative version:
private CacheEntry sumUpAndCacheChildren(Node initialNode) {
class StackFrame {
final Node node;
Node[] children;
// Local vars
long size;
// Tracking stack frame state
int stage;
int loopIndex;
StackFrame(Node node) {
this.node = node;
this.children = null;
this.size = 0;
this.stage = 0;
this.loopIndex = 0;
}
}
final Stack<StackFrame> stack = new Stack<StackFrame>();
stack.push(new StackFrame(initialNode));
CacheEntry retValue = getCacheEntry(initialNode);
outer:
while (!stack.isEmpty()) {
final StackFrame frame = stack.peek();
final Node node = frame.node;
switch(frame.stage) {
case 0: {
final CacheEntry entry = getCacheEntry(node);
if (entryIsValid(entry)) {
retValue = entry;
stack.pop();
continue;
}
frame.children = node.asItem().listChildren();
frame.stage = frame.children != null ? 1 : 3;
} break;
case 1: {
for (int i = frame.loopIndex; i < frame.children.length; ++i) {
frame.loopIndex = i;
final Node child = frame.children[i];
if (child.hasChildren()) {
stack.push(new StackFrame(child));
frame.stage = 2; // Accumulate results once all the child stacks have been calculated.
frame.loopIndex++; // Make sure we restart the for loop at the next iteration the next time around.
continue outer;
} else {
frame.size += child.size();
}
}
frame.stage = 3;
} break;
case 2: {
// Accumulate results
frame.size += retValue.size;
frame.stage = 1; // Continue the for loop
} break;
case 3: {
retValue = putInCache(node, frame.type);
stack.pop();
continue;
}
}
}
return retValue;
}
This just feels more insane than it needs to be, and it would be painful to have to do this in all the places in the code where I recurse into the children and do different ops on them. What techniques could I use to make it easier to do recursion when I'm aggregating at each level and doing that in a for-loop over the children?
EDIT:
I was able to greatly simplify things with the help of the answers below. The code is now nearly as concise as the original recursive version. Now, I just need to apply the same principles everywhere else where I'm recursing over the same data structure.
Since you're dealing with a tree structure and wish to compute cumulative sizes, try a DFS while tracking the parent of each node. I assume here that you cannot change or subclass Node and I kept all the function signatures you used.
private class SizedNode {
public long cumulativeSize;
public Node node;
public SizedNode parent;
public SizedNode(SizedNode parent, Node node) {
this.node = node;
this.parent = parent;
}
public long getSize() {
if (node.hasChildren()) {
return cumulativeSize;
}
else {
return node.size();
}
}
}
private void sumUpAndCacheChildren(Node start)
{
Stack<SizedNode> nodeStack = new Stack<SizedNode>();
// Let's start with the beginning node.
nodeStack.push(new SizedNode(null, start));
// Loop as long as we've got nodes to process
while (!nodeStack.isEmpty()) {
// Take a look at the top node
SizedNode sizedNode = nodeStack.peek();
CacheEntry entry = getCacheEntry(sizedNode.node);
if (entryIsValid(entry)) {
// It's cached already, so we have computed its size
nodeStack.pop();
// Add the size to the parent, if applicable.
if (sizedNode.parent != null) {
sizedNode.parent.cumulativeSize += sizedNode.getSize();
// If the parent's now the top guy, we're done with it so let's cache it
if (sizedNode.parent == nodeStack.peek()) {
putInCache(sizedNode.parent.node, sizedNode.parent.getSize());
}
}
}
else {
// Not cached.
if (sizedNode.node.hasChildren()) {
// It's got a bunch of children.
// We can't compute the size yet, so just add the kids to the stack.
Node[] children = sizedNode.node.listChildren();
if (children != null) {
for (Node child : children) {
nodeStack.push(new SizedNode(sizedNode, child));
}
}
}
else {
// It's a leaf node. Let's cache it.
putInCache(sizedNode.node, sizedNode.node.size());
}
}
}
}
You're basically doing a post-order iterative traversal of an N-ary tree; you can try searching for that for more detailed examples.
In very rough pseudocode:
Node currentNode;
Stack<Node> pathToCurrent;
Stack<Integer> sizesInStack;
Stack<Integer> indexInNode;
pathToCurrent.push(rootNode);
sizesInStack.push(0);
indexInNode.push(0);
current = rootNode;
currentSize = 0;
currentIndex = 0;
while (current != null) {
if (current.children != null && currentIndex < current.children.size) {
//process the next node
nextChild = current.children[currentIndex];
pathToCurrent.push(current);
sizesInStack.push(currentSize);
indexInNode.push(currentIndex);
current = nextChild;
currentSize = 0;
currentIndex = 0;
} else {
//this node is a leaf, or we've handled all its children
//put our size into the cache, then pop off the stack and set up for the next child of our parent
currentSize += this.size();
putInCache(this, currentSize);
current = pathToCurrent.pop(); //If pop throws an exception on empty stack, handle it here and exit the loop
currentSize = currentSize + sizesInStack.pop();
currentIndex = 1 + indexInNode.pop();
}
}
OK, im gonna explain it in human words since i dont want to code right now :
Acquire topmost level of elements and write into a list
LOOP BEGIN
count elements on this level and add them to your counter
acquire list of children from your current list, store seperately
delete list of current elements
write list of children to where the list of the current elements was
LOOP END
you simply need to put a boolean into the loop-header and set it to false if the list of children has no elements anymore ... i hope i was able to express myself correctly, feel free to ask questions and/or inquire about clarification.
This algorithm will get exponentially slower ( --> O(n²) ) in each iteration if the data-structure keeps "folding out", its rather inefficient and im quite sure someone can come up with an optimization - but it will be faster than with recursion and it wont produce a stack overflow; yet it may produce an OutOfMemoryException for very large datasets - but since only one level is iterated at any time this is ... quite unrealistic i guess
After adapting #Marius's answer to my use case, I came up with this:
class SizedNode {
final Node node;
final SizedNode parent;
long size;
boolean needsCaching;
SizedNode(Node node, SizedNode parent) {
this.parent = parent;
this.node = node;
}
}
private CacheEntry sumUpAndCacheChildren(Node start) {
final Stack<SizedNode> stack = new Stack<SizedNode>();
stack.push(new SizedNode(start, null));
CacheEntry returnValue = getCacheEntry(start);
while (!stack.isEmpty()) {
final SizedNode sizedNode = stack.pop();
final CacheEntry entry = getCacheEntry(sizedNode.folder);
if (sizedNode.needsCaching) {
// We finished processing all children, and now we're done with this node.
if (sizedNode.parent != null) {
sizedNode.parent.size += sizedNode.size;
}
returnValue = putInCache(sizedNode.folder, sizedNode.size);
} else if (entryIsValid(entry)) {
if (sizedNode.parent != null) {
sizedNode.parent.size += entry.size;
}
returnValue = entry;
} else {
// The next time we see this node again, it will be after we process all of its children.
sizedNode.needsCaching = true;
stack.push(sizedNode);
for (Node child : sizedNode.node.listChildren()) {
if (child.hasChildren()) {
stack.push(new SizedNode(child, sizedNode));
} else {
sizedNode.size += child.size();
}
}
}
}
return returnValue;
}
This is much better than the crazy mess I came up with on my first pass. Just goes to show that you really have to think about transforming the algorithm so that it also makes sense as an iterative approach. Thanks all for the help!

Deleting elements from a binary search tree

I am currently writing a method that removes a given value from a binary search tree. However when I call it, it deletes the said value but then duplicates every other value. I have no idea why. Please tell me what is wrong.
There are two methods, one that find the elements, and the other that deletes it.
Here is the one that finds that element...
public static TreeNode delete(TreeNode t, Comparable x, TreeDisplay display)
{
if( x.compareTo(t.getValue()) > 0)
{
display.visit(t);
t.setRight(delete( t.getRight(), x, display));
}
else if ( x.compareTo(t.getValue()) < 0)
{
display.visit(t);
t.setLeft(delete(t.getLeft(), x, display));
}
else
{
t = deleteNode(t, display);
}
return t;
This is the method that deletes the value
private static TreeNode deleteNode(TreeNode t, TreeDisplay display)
{
if (t.getRight()!=null)
{
TreeNode right = t.getRight();
TreeNode max = (TreeNode)TreeUtil.leftmost(right);
TreeNode previous = null;
while ( right.getLeft()!=null&&right.getLeft().getLeft()!=null)
{
right = right.getLeft();
}
t.setValue(max.getValue());
if ( max.getRight()==null)
{
right.setLeft(null);
}
else
{
right.setLeft(max.getRight());
}
}
else if (t.getLeft() !=null)
{
TreeNode left = t.getLeft();
TreeNode max = (TreeNode)TreeUtil.rightmost(left);
while(left.getRight()!=null &&left.getRight().getRight()!=null)
{
left = left.getRight();
}
t.setValue(max.getValue());
if ( max.getLeft()==null)
{
left.setRight(null);
}
else
{
left.setRight(max.getLeft());
}
}
else
{
t = null;
}
return t;
}
Thanks in advance!
...it deletes the said value but then duplicates every other value. I have no idea why. Please tell me what is wrong.
It's because you're returning the deleted node from deleteNode(), and in the first method delete(), your calls to setRight() and setLeft() are setting every traversed element to the deleted element as the recursion unwinds back up the tree.

Is it possible to implement an algorithm to find the nth to last element of a singly linked list using recursion in java

I know that you can simply solve this question iteratively by using a counter to increment each time you pass a node in linkedlist; also creating an arraylist and setting the data found with each node inside it. Once you hit the tail of the linkedlist, just minus the Nth term from the total number of elements in the arraylist and you will be able to return the answer. However how would someone perform this using recursion? Is it possible and if so please show the code to show your genius :).
Note: I know you cannot return two values in Java (but in C/C++, you can play with pointers :])
Edit: This was a simple question I found online but I added the recursion piece to make it a challenge for myself which I've come to find out that it may be impossible with Java.
The trick is to do the work after the recursion. The array in the private method is basically used as a reference to a mutable integer.
class Node {
Node next;
int data;
public Node findNthFromLast(int n) {
return findNthFromLast(new int[] {n});
}
private Node findNthFromLast(int[] r) {
Node result = next == null ? null : next.findNthFromLast(r);
return r[0]-- == 0 ? this : result;
}
}
As a general rule, anything that can be done with loops can also be done with recursion in any reasonable language. The elegance of the solution may be wildly different. Here is a fairly java idiomatic version. I've omitted the usual accessor functions for brevity.
The idea here is to recur to the end of the list and increment a counter as the recursion unwinds. When the counter reaches the desire value, return that node. Otherwise return null. The non-null value is just returned all the way tot the top. Once down the list, once up. Minimal arguments. No disrespect to Adam intended, but I think this is rather simpler.
NB: OP's statement about Java being able to return only one value is true, but since that value can be any object, you can return an object with fields or array elements as you choose. That wasn't needed here, however.
public class Test {
public void run() {
Node node = null;
// Build a list of 10 nodes. The last is #1
for (int i = 1; i <= 10; i++) {
node = new Node(i, node);
}
// Print from 1st last to 10th last.
for (int i = 1; i <= 10; i++) {
System.out.println(i + "th last node=" + node.nThFromLast(i).data);
}
}
public static void main(String[] args) {
new Test().run();
}
}
class Node {
int data; // Node data
Node next; // Next node or null if this is last
Node(int data, Node next) {
this.data = data;
this.next = next;
}
// A context for finding nth last list element.
private static class NthLastFinder {
int n, fromLast = 1;
NthLastFinder(int n) {
this.n = n;
}
Node find(Node node) {
if (node.next != null) {
Node rtn = find(node.next);
if (rtn != null) {
return rtn;
}
fromLast++;
}
return fromLast == n ? node : null;
}
}
Node nThFromLast(int n) {
return new NthLastFinder(n).find(this);
}
}
Okay, I think think this should do the trick. This is in C++ but it should be easy to translate to Java. I also haven't tested.
Node *NToLastHelper(Node *behind, Node *current, int n) {
// If n is not yet 0, keep advancing the current node
// to get it n "ahead" of behind.
if (n != 0) {
return NToLastHelper(behind, current->next, n - 1);
}
// Since we now know current is n ahead of behind, if it is null
// the behind must be n from the end.
if (current->next == nullptr) {
return behind;
}
// Otherwise we need to keep going.
return NToLastHelper(behind->next, current->next, n);
}
Node *NToLast(Node *node, int n) {
// Call the helper function from the head node.
return NToLastHelper(node, node, n);
}
edit: If you want to return the value of the last node, you can just change it to:
int NToLast(Node *node, int n) {
// Call the helper function from the head node.
return NToLastHelper(node, node, n)->val;
}
This code will fail badly if node is null.
The recursion function:
int n_to_end(Node *no, int n, Node **res)
{
if(no->next == NULL)
{
if(n==0)
*res = no;
return 0;
}
else
{
int tmp = 1 + n_to_end(no->next, n, res);
if(tmp == n)
*res = no;
return tmp;
}
}
The wrapper function:
Node *n_end(Node *no, int n)
{
Node *res;
res = NULL;
int m = n_to_end(no, n, &res);
if(m < n)
{
printf("max possible n should be smaller than or equal to: %d\n", m);
}
return res;
}
The calling function:
int main()
{
List list;
list.append(3);
list.append(5);
list.append(2);
list.append(2);
list.append(1);
list.append(1);
list.append(2);
list.append(2);
Node * nth = n_end(list.head, 6);
if(nth!=NULL)
printf("value is: %d\n", nth->val);
}
This code has been tested with different inputs. Although it's a C++ version, you should be able to figure out the logic :)

red black tree balancing?

i am working to generate tango tree,
where i need to check whether every sub tree in tango is balanced or not.
if its not balanced i need to make it balance? I trying so hard to make entire RB-tree balance but i not getting any proper logic so can any one help me out??
here i am adding code to check how to find my tree is balanced are not but when its not
balanced how can i make it balance.
static boolean verifyProperty5(rbnode n) {
int left = 0, right = 0;
if (n != null) {
bh++;
left = blackHeight(n.left, 0);
right = blackHeight(n.right, 0);
}
if (left == right) {
System.out.println("black height is :: " + bh);
return true;
} else {
System.out.println("in balance");
return false;
}
}
public static int blackHeight(rbnode root, int len) {
bh = 0;
blackHeight(root, path1, len);
return bh;
}
private static void blackHeight(rbnode root, int path1[], int len) {
if (root == null)
return;
if (root.color == "black"){
root.black_count = root.parent.black_count+1;
} else{
root.black_count = root.parent.black_count;
}
if ((root.left == null) && (root.right == null)) {
bh = root.black_count;
}
blackHeight(root.left, path1, len);
blackHeight(root.right, path1, len);
}
Don't try to generate a tree and verify if it is balanced. Try to use a red-black structure from the start. It will guarantee that the resulting tree is balanced. Or did I misunderstood?
Edit:
It seems Tango tree is based on red black trees. Which means you need to have a red-black tree already implemented before implementing a Tango tree on top of that.
If you are looking for the red-black tree balancing, you can check out the code here.

Categories

Resources