freeCodeCamp / CurriculumExpansion

Creative Commons Attribution Share Alike 4.0 International
313 stars 105 forks source link

Advanced Data Structure Challenges #17

Closed QuincyLarson closed 7 years ago

QuincyLarson commented 8 years ago

@koustuvsinha is in charge of coordinating the expansion of these challenges, but he needs your help.

For each challenge, please reply to this GitHub issue with:

  1. Challenge description text
  2. Test suite (using the assert method)
  3. The seed code, which is pre-populated in the editor at the beginning of the challenge
  4. A working solution that makes all tests pass

Current status of challenge development:

alayek commented 8 years ago

@koustuvsinha you might be interested in this! Remember algo-koans?

koustuvsinha commented 8 years ago

yep! sounds awesome @alayek

QuincyLarson commented 8 years ago

@koustuvsinha would you be interested in being the "topic owner" for these? It will involve contributing your own challenges and coordinating other campers creating them, too. Which of these would you be interested in building yourself?

koustuvsinha commented 8 years ago

@QuincyLarson sure, I am interested to become topic owner. Me and @alayek had plans earlier to build something in the line of DS concepts in JS, mostly in koans format. So doing those in FCC Challenge style seems great. I would be interested to dive in Stacks, Queues and Trees

alayek commented 8 years ago

@QuincyLarson why is ES6 symbol part of this epic? Also, do we use ES6 class syntax for these?

QuincyLarson commented 8 years ago

@alayek That's the first time I've heard of these topics called "epics" - I like it! We should start calling them that!

I am not sure whether we will be able to use ES6 for these challenges yet (we definitely do want to be able to cover ES6 a bit in the JavaScript section). @BerkeleyTrue would know best.

ghost commented 8 years ago

Learning About Stacks

Challenge Description

You are probably familiar with stack of books on your table. You might have used the undo feature in your computer. You are also used to hitting the back button on your phone to go back to the previous view in your app. You know what they all have in common? They all store the data in a way so that you can traverse backwards. The topmost book in the stack was the one that was put there at the last. If you remove that book from your stack's top, you would expose the book that was put there before the last book and so on.

If you think about it, in all the above examples, you are getting a Last-In-First-Out type of service. We will try to mimic this with our code - create a similar data storage scheme with JS arrays and functions that we always get back first what we put there last.

Oh, and did I mention such data storage scheme is called a Stack! In particular, we would have to implement the push function that pushes JS object at the top of the stack; and pop function, that removes the JS object that's at the top of the stack at the current moment.

Instructions

Here we have a stack of homework assignments represented as an array: BIO12 is at the base, and PSY44 is at the top of the stack.

Remove the element on top of the stack, and Add CS50.

Challenge Seed

var homeworkStack = ['BIO12','HIS80','MAT122','PSY44']
// Only change code below this line

Challenge Tests

assert(homeworkStack.length === 4, 'message: <code>homeworkStack</code> should only contain 4 elements');
assert(homeworkStack[3] === 'CS50', 'message: The last element in <code>homeworkStack</code> should be CS50');
assert(homeworkStack.indexOf('PSY44') === -1, 'message: <code>homeworkStack</code> should not contain PSY44');

Challenge Solution

var homeworkStack = ['BIO12','HIS80','MAT122','PSY44'];

homeworkStack.pop();
homeworkStack.push('CS50');
ghost commented 8 years ago

Creating a stack class

Challenge Description

In the last section, we talked about what a stack is and how we can use an array to represent a stack. In this section, we will be creating our own stack class.

Although you can use arrays to create stacks, sometimes it is best to limit the amount of control we have with our stacks.

Apart from the push and pop methods, stacks have other useful methods. Let's add a peek, isEmpty, and clear method to our stack class.

Instructions

Write a push method that pushes an element to the top of the stack, a pop method that removes the element on the top of the stack, a peek method that looks at the first element in the stack, an isEmpty method that checks if the stack is empty, and a clear method that removes all elements from the stack.

Normally stacks don't have this, but we've added a print helper method that console logs the collection.

Challenge Seed

function Stack() { 
    let collection = [];
    this.print = function() {
        console.log(collection)
    }
    // Only change code below this line

    // Only change code above this line
}

// Test your stack class
var homework = new Stack();
homework.push('CS50');
console.log(homework.peek())
console.log(homework.isEmpty())
homework.clear();
console.log(homework.isEmpty())

Challenge Tests

assert((function(){var test = new Stack(); return (typeof test.push === 'function')}()), 'message: Your <code>Stack</code> class should have a <code>push</code> method.');
assert((function(){var test = new Stack(); return (typeof test.pop === 'function')}()), 'message: Your <code>Stack</code> class should have a <code>pop</code> method.');
assert((function(){var test = new Stack(); return (typeof test.peek === 'function')}()), 'message: Your <code>Stack</code> class should have a <code>peek</code> method.');
assert((function(){var test = new Stack(); return (typeof test.isEmpty === 'function')}()), 'message: Your <code>Stack</code> class should have a <code>isEmpty</code> method.');
assert((function(){var test = new Stack(); return (typeof test.clear === 'function')}()), 'message: Your <code>Stack</code> class should have a <code>clear</code> method.');
assert((function(){var test = new Stack();  test.push('CS50'); return (test.peek() === 'CS50')}()), 'message: The <code>peek</code> method should return the top element of the stack');
assert((function(){var test = new Stack(); test.push('CS50'); return (test.pop() === 'CS50');}()), 'message: The <code>pop</code> method should remove and return the top element of the stack');
assert((function(){var test = new Stack(); return test.isEmpty()}()), 'message: The <code>isEmpty</code> method should return true if a stack does not contain any elements');
assert((function(){var test = new Stack();  test.push('CS50'); test.clear(); return (test.isEmpty())}()), 'message: The <code>clear</code> method should remove all element from the stack');

Challenge Solution

function Stack() { 
    let collection = [];
    this.print = function() {
        console.log(collection)
    }
    // Only change code below this line
    this.push = function(element) {
        collection.push(element);
    };
    this.pop = function() {
        return collection.pop();
    };
    this.peek = function() {
        return collection[collection.length - 1];
    };
    this.isEmpty = function() {
        return (collection.length === 0);
    };
    this.clear = function(){
        collection = [];
    };
    // Only change code above this line
}
ghost commented 8 years ago

Creating a queue class

Challenge Description

Like stacks, queues are a collection of elements. But unlike stacks, queues follow the FIFO (First-In First-Out) principle. Elements added to a queue are pushed to the tail, or the end, of the queue, and only the element at the front of the queue is allowed to be removed.

We could use an array to represent a queue, but just like stacks, we want to limit the amount of control we have over our queues.

The two main methods of a queue class are enqueue and dequeue. The enqueue method pushes an element to the tail of the queue, and the dequeue method removes and returns the element at the front of the queue. Other useful methods are the front, size, and isEmpty methods.

Instructions

Write an enqueue method that pushes an element to the tail of the queue, a dequeue method that removes and returns the front element, a front method that lets us see the front element, a size method that shows the length, and an isEmpty method to check if the queue is empty.

Challenge Seed

function Queue () { 
    let collection = [];
    this.print = function() {
        console.log(collection);
    }
    // Only change code below this line

    // Only change code above this line
}

// Test your queue class
var DMV = new Queue();
DMV.enqueue('David Brown');
DMV.enqueue('Jon Snow');
DMV.size();
DMV.dequeue();
DMV.front();
DMV.isEmpty();

Challenge Tests

assert((function(){var test = new Queue();  return (typeof test.enqueue === 'function')}()), 'message: Your <code>Queue</code> class should have a <code>enqueue</code> method.');
assert((function(){var test = new Queue();  return (typeof test.denqueue === 'function')}()), 'message: Your <code>Queue</code> class should have a <code>denqueue</code> method.');
assert((function(){var test = new Queue();  return (typeof test.front === 'function')}()), 'message: Your <code>Queue</code> class should have a <code>front</code> method.');
assert((function(){var test = new Queue();  return (typeof test.size === 'function')}()), 'message: Your <code>Queue</code> class should have a <code>size</code> method.');
assert((function(){var test = new Queue();  return (typeof test.isEmpty === 'function')}()), 'message: Your <code>Queue</code> class should have a <code>isEmpty</code> method.');
assert((function(){var test = new Queue();  test.enqueue('Smith'); return (test.dequeue() === 'Smith')}()), 'message: The <code>dequeue</code> method should remove and return the front element of the queue');
assert((function(){var test = new Queue();  test.enqueue('Smith'); test.enqueue('John'); return (test.front() === 'Smith')}()), 'message: The <code>front</code> method should return value of the front element of the queue');
assert((function(){var test = new Queue();  test.enqueue('Smith'); return (test.size() === 1)}()), 'message: The <code>size</code> method should return the length of the queue');
assert((function(){var test = new Queue();  test.enqueue('Smith'); return !(test.isEmpty())}()), 'message: The <code>isEmpty</code> method should return false if there are elements in the queue');

Challenge Solution

function Queue () { 
    let collection = [];
    this.print() = function(){
        console.log(collection);
    }
    // Only change code below this line
    this.enqueue = function(element) {
        collection.push(element);
    };
    this.dequeue = function() {
        return collection.shift();
    };
    this.front = function() {
        return collection[0];
    };
    this.size = function() {
        return collection.length;
    };
    this.isEmpty = function(){
        return (this.size === 0);
    };
    // Only change code above this line
}
ghost commented 8 years ago

If wanted, I can change the seeds to use prototypes or ES6 classes.

QuincyLarson commented 8 years ago

@elibei great question. @alayek @koustuvsinha what do you think of these challenges so far?

alayek commented 8 years ago

@elibei thanks for these.

Looks great, and you could make the language a bit simpler in a few places.


Take this for example:

A stack is a collection of elements that follows the LIFO (Last-In First-Out) principle for adding and removing elements: elements can only be added to the top of the stack, and only the top element can be removed. A common application of a stack is the "undo" feature in text editors.

You could phrase it differently and make it easier for beginners

You are probably familiar with stack of books on your table. You might have used the undo feature in your computer. You are also used to hitting the back button on your phone to go back to the previous view in your app. You know what they all have in common? They all store the data in a way so that you can traverse backwards. The topmost book in the stack was the one that was put there at the last. If you remove that book from your stack's top, you would expose the book that was put there before the last book and so on.

If you think about it, in all the above examples, you are getting Last-In-First-Out type of service. We will try to mimic this with our code - create a similar data storage scheme with JS arrays and functions that we always get back first what we put there last.

Oh, and did I mention such data storage scheme is called Stack! In particular, we would have to implement the push function that pushes JS object at the top of the stack; and pop function, that removes the JS object that's at the top of the stack at the current moment.


Also, I think it's Jon Snow and not not John Snow.

ghost commented 8 years ago

@alayek thanks for the advise. It's very helpful. I've update the challenge to use your description. When I have a chance, I'll definitely update the description of the other challanges I've submitted. And you are absolutely correct, it is Jon Snow! (I've just fixed it)

koustuvsinha commented 8 years ago

@elibei awesome challenges. however, i am concerned about them being too spoonfed. allow me to explain :

In the Queue challenge, the sample solution is this :

function Queue () { 
    collection = [];
    this.enqueue = function(element) {
        // Push an element to the tail of the queue
        // Only change code below this line
        collection.push(element);
        // Only change code above this line
    };
}

where, the user only has to change one line, all other method signatures are given. I feel this is too basic, instead you can define which method signatures you want from the user, and just provide the basic Queue function boilerplate. In the assert, you can then check whether these functions exist or not. A little bit more challenging than the previous one, but within easy level only.

apart from that, provide a fully implemented helper function to display the array contents, and provide an option to the user to call that from enqueue / dequeue etc to view the current position of the array.

function Queue () { 
    collection = [];
   // implement your functions here
   // use the `this` operator (optional linkback to fcc `this` article)

  // use this function to see your queue position
  function displayQueue() {
   console.log(collection); // or you can pretty print the collection here
  }
}

what do you think @alayek ?

alayek commented 8 years ago

Yeah @koustuvsinha I like this more. For reference, check out Udacity's Data Structure in Python course, They are also giving some right amount of challenging stuff which gradually gets tougher,

ghost commented 8 years ago

@koustuvsinha I've just updated the challenges to be more challenging

ghost commented 8 years ago

Working with nodes in a linked list

Challenge Description

Another common data structure is the linked list. In a linked list, elements are stored in a node. The node contains two key pieces of information: the element itself, and a reference to the next node.

Imagine that you are in a conga line. You have your hands on the next person in the line, and the person behind you has their hands on you. You can see the person straight ahead of you, but they are blocking the view of the other people ahead in line. A node is just like a person in a conga line, they know who they are and they can only see the next person in line, but they are not aware of the other people ahead or behind them.

Instructions

In the code, we've create two nodes, Kitten and Puppy, and we've manually connected the Kitten node to the Puppy node.

Create a Cat and Dog node and manually add them to the line.

Note

something to note

Challenge Seed

var Node = function(element){
    this.element = element; 
    this.next = null; 
};
var Kitten = new Node("Kitten");
var Puppy = new Node("Puppy");

Kitten.next = Puppy;
// only add code below this line

// test your code
console.log(Kitten.next)

Challenge Tests

assert(Puppy.next.element === "Cat", 'message: Your <code>Puppy</code> node should have a reference to a <code>Cat</code> node.');
assert(Cat.next.element === "Dog", 'message: Your <code>Cat</code> node should have a reference to a <code>Dog</code> node.');

Challenge Solution

var Node = function(element){
    this.element = element; 
    this.next = null; 
};
var Kitten = new Node("Kitten");
var Puppy = new Node("Puppy");

Kitten.next = Puppy;
// only add code below this line
var Cat = new Node("Cat");
Puppy.next = Cat;
var Dog = new Node("Dog");
Cat.next = Dog;
ghost commented 8 years ago

Creating a Linked List Class

Challenge Description

Let's create a linked list class. Every linked list has a head and length. The head is the first node added to the linked list. The length is the size of the linked list. When an element is added to the linked list, the length should increment by one.

The first method of a linked list is the add method. When an element is added to a linked list, a new node is created. If it is the first node created, it is assigned to the head of the linked list. If it is not the first node, the previous node should reference the new created node.

Instructions

Write an add method that assigns head to the first node push to the linked list, and after that, every node should be referenced by the previous node.

We've added a head and size helper method.

Note

Length should increase by one every time an element is pushed to the linked list.

Challenge Seed

function LinkedList() { 
  var length = 0; 
  var head = null; 

  var Node = function(element){
    this.element = element; 
    this.next = null; 
  }; 

  this.head = function(){
    return head;
  };

  this.size = function(){
    return length;
  };

  this.add = function(element){
    // Only change code below this line

    // Only change code above this line
  }; 
} 

// Test your code
var conga = new LinkedList();
conga.add('Kitten');
console.log(conga.head().element);

Challenge Tests

assert((function(){var test = new LinkedList(); return (typeof test.add === 'function')}()), 'message: Your <code>LinkedList</code> class should have a <code>add</code> method.');
assert((function(){var test = new LinkedList(); test.add('cat'); return test.head().element === 'cat'}()), 'message: Your <code>LinkedList</code> class should assign <code>head</code> to the first node added.');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); return test.head().next.element === 'dog'}()), 'message: The previous <code>node</code> in your <code>LinkedList</code> class should have reference to the newest node created.');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); return test.size() === 2}()), 'message: The  <code>size</code> of your <code>LinkedList</code> class should equal the amount of nodes in the linked list.');

Challenge Solution

function LinkedList() { 
  var length = 0; 
  var head = null; 

  var Node = function(element){
    this.element = element; 
    this.next = null; 
  }; 

  this.size = function(){
    return length;
  };

  this.head = function(){
    return head;
  };

  this.add = function(element){
    // Only change code below this line
    var node = new Node(element);
    if(head === null){
        head = node;
    } else {
        currentNode = head;

        while(currentNode.next){
            currentNode  = currentNode.next;
        }

        currentNode.next = node;
    }

    length++;
    // Only change code above this line
  }; 
} 
ghost commented 8 years ago

Removing elements from a linked list

Challenge Description

The next important method of a linked list is the remove method. The remove method takes an element and searches the linked list to find and remove the node containing that element. When a node is removed, the previous node then references the node after the removed node as the next node in the list.

This might sound really confusing, but let's return to the conga line example. You're in a conga line, and the person straight ahead of you leaves the line. The person leaving the line no longer has his hands on anyone in the line and you no longer have your hands on the person that left. You step forward and put your hands on next person you see.

If the element being removed is the head element, the head is reassigned to the second node of the linked list.

Instructions

Write a remove method that takes an element and removes it from the linked list.

Note

Length should decrease by one every time an element is removed from the linked list.

Challenge Seed

function LinkedList() { 
  var length = 0; 
  var head = null; 

  var Node = function(element){ 
    this.element = element; 
    this.next = null; 
  }; 

  this.length = function(){
    return length;
  };

  this.head = function(){
    return head;
  };

  this.add = function(element){
    var node = new Node(element);
    if(head === null){
        head = node;
    } else {
        currentNode = head;

        while(currentNode.next){
            currentNode  = currentNode.next;
        }

        currentNode.next = node;
    }

    length++;
  }; 

  this.remove = function(element){
    // Only change code below this line

    // Only change code above this line
  }
} 

// Test your code
var conga = new LinkedList();
conga.add('Kitten');
conga.add('Puppy');
console.log(conga.length());
conga.remove('Kitten');
console.log(conga.head());

Challenge Tests

assert((function(){var test = new LinkedList(); return (typeof test.remove === 'function')}()), 'message: Your <code>LinkedList</code> class should have a <code>remove</code> method.');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); test.remove('cat'); return test.head().element === 'dog'}()), 'message: Your <code>Remove</code> method should reassign <code>head</code> to the second node when the first node is removed.');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); test.remove('cat'); return test.length() === 1}()), 'message: Your <code>Remove</code> method should decrease the <code>length</code> of the linked list by one for every node removed.');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog');test.add('kitten'); test.remove('dog'); return test.head().next.element === 'kitten'}()), "message: Your <code>Remove</code> method should reassign the reference of the previous node of the removed node to the removed node's <code>next</code> reference ");

Challenge Solution

function LinkedList() { 
  var length = 0; 
  var head = null; 

  var Node = function(element){ 
    this.element = element; 
    this.next = null; 
  }; 

  this.length = function(){
    return length;
  };

  this.head = function(){
    return head;
  };

  this.add = function(element){
    var node = new Node(element);
    if(head === null){
        head = node;
    } else {
        currentNode = head;

        while(currentNode.next){
            currentNode  = currentNode.next;
        }

        currentNode.next = node;
    }

    length++;
  }; 

  this.remove = function(element){
    // Only change code below this line
    var currentNode = head;
    var previousNode;
    if(currentNode.element === element){
        head = currentNode.next;
    } else {
        while(currentNode.element !== element) {
            previousNode = currentNode;
            currentNode = currentNode.next;
        }

        previousNode.next = currentNode.next;
    }

    length --;
    // Only change code above this line
  };
} 
ghost commented 8 years ago

Searching a Linked List

Challenge Description

Let's add a few more useful methods to our linked list class. Like the stack and queue classes, we should add an isEmpty method to check if the linked list is empty.

We also want to find elements in our linked list. Let's create an indexOf method that takes an element and returns the indexes of it in the linked list (the element could exist multiple times). The method should return -1 if the element is not found in the linked list. We also need an elementAt method that takes an index and returns the element at the given index. The method should return undefined if no element is found.

Instructions

Write an isEmpty method that checks if the linked list is empty, a size method that returns the length of the linked list, an indexOf method that returns the index of a given element, and an elementAt that returns an element at a given index.

Challenge Seed

function LinkedList() { 
  var length = 0; 
  var head = null; 

  var Node = function(element){ // {1} 
    this.element = element; 
    this.next = null; 
  }; 

  this.size = function() {
    return length;
  };

  this.head = function(){
    return head;
  };

  this.add = function(element){
    var node = new Node(element);
    if(head === null){
        head = node;
    } else {
        currentNode = head;

        while(currentNode.next){
            currentNode  = currentNode.next;
        }

        currentNode.next = node;
    }

    length++;
  }; 

  this.remove = function(element){
    var currentNode = head;
    var previousNode;
    if(currentNode.element === element){
        head = currentNode.next;
    } else {
        while(currentNode.element !== element) {
            previousNode = currentNode;
            currentNode = currentNode.next;
        }

        previousNode.next = currentNode.next;
    }

    length --;
  };

  // Only change code below this line

  // Only change code above this line
}

// Test your code
var conga = new LinkedList();
conga.add('Kitten');
conga.add('Puppy');
console.log(conga.indexOf('Puppy'))
console.log(conga.elementAt('0'))
conga.remove('Kitten');
console.log(conga.head());

Challenge Tests

assert((function(){var test = new LinkedList(); return (typeof test.indexOf === 'function')}()), 'message: Your <code>LinkedList</code> class should have a <code>indexOf</code> method.');
assert((function(){var test = new LinkedList(); return (typeof test.elementAt === 'function')}()), 'message: Your <code>LinkedList</code> class should have a <code>elementAt</code> method.');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); test.add('kitten'); return test.size() === 3}()), 'message: Your <code>size</code> method should return the length of the linked list');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); test.add('kitten'); return test.indexOf('kitten') === 2}()), 'message: Your <code>indexOf</code> method should return the index of the given element.');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); test.add('kitten'); return test.elementAt(1) === 'dog'}()), 'message: Your <code>elementAt</code> method should return at element at a given index.');

Challenge Solution

function LinkedList() { 
  var length = 0; 
  var head = null; 

  var Node = function(element){ // {1} 
    this.element = element; 
    this.next = null; 
  };

  this.size = function() {
    return length;
  };

  this.head = function(){
    return head;
  };

  this.add = function(element){
    var node = new Node(element);
    if(head === null){
        head = node;
    } else {
        currentNode = head;

        while(currentNode.next){
            currentNode  = currentNode.next;
        }

        currentNode.next = node;
    }

    length++;
  }; 

  this.remove = function(element){
    var currentNode = head;
    var previousNode;
    if(currentNode.element === element){
        head = currentNode.next;
    } else {
        while(currentNode.element !== element) {
            previousNode = currentNode;
            currentNode = currentNode.next;
        }

        previousNode.next = currentNode.next;
    }

    length --;
  };

  // Only change code below this line
  this.isEmpty = function() {
    return length === 0;
  };

  this.indexOf = function(element) {
    var currentNode = head;
    var index = -1;

    while(currentNode){
        index++;
        if(currentNode.element === element){
            return index;
        }
        currentNode = currentNode.next;
    }

    return -1;
  };

  this.elementAt = function(index) {
    var currentNode = head;
    var count = 0;
    while (count < index){
        count ++;
        currentNode = currentNode.next
    }
    return currentNode.element;
  };
  // Only change code above this line
}
koustuvsinha commented 8 years ago

@elibei in the assert tests please add a test at the beginning to check whether the methods exists or not. this goes for all challenges where user needs to add a method

ghost commented 8 years ago

@koustuvsinha thanks for the heads up. I've just updated all the challenges' tests to check whether the methods are defined

ghost commented 8 years ago

@QuincyLarson @alayek @koustuvsinha What do you guys think of this outline?

QuincyLarson commented 8 years ago

@elibei This looks great! Quite inclusive. Does anyone have anything to add?

By the way, @elibei I tried to find you on Gitter but you don't seem to have an account. I wanted to talk with you about all the great work you're doing and learn more about you. Is there a good way to reach you aside from commenting on GitHub issues? :)

koustuvsinha commented 8 years ago

@elibei :+1: for the outline, looks better!

ghost commented 8 years ago

@QuincyLarson i've just created a gitter account and have sent you a message

ghost commented 8 years ago

Removing Elements by Index

Challenge Description

Now we need to create a remove method that removes the element at a given index. The method should be called removeAt(index). To remove an element at a certain index we need to keep count of each node as we move along the linked list. Starting at the head of the linked list, our currentIndex should be 0. The currentIndex should increment by one for each node we pass. Just like the remove(element) method, we need to reconnect the nodes. The node that has reference to the removed node should now have reference to the next node.

Instructions

Write a removeAt(index) method that removes and returns a node at a given index. The method should return null if the given index is a negative or is more than or equal to the length of the linked list.

Note

Remember to keep count of the currentIndex.

Challenge Seed

function LinkedList() { 
  var length = 0; 
  var head = null; 

  var Node = function(element){ // {1} 
    this.element = element; 
    this.next = null; 
  }; 

  this.size = function(){
    return length;
  };

  this.head = function(){
    return head;
  };

  this.add = function(element){
    var node = new Node(element);
    if(head === null){
        head = node;
    } else {
        currentNode = head;

        while(currentNode.next){
            currentNode  = currentNode.next;
        }

        currentNode.next = node;
    }

    length++;
  }; 

  this.remove = function(element){
    var currentNode = head;
    var previousNode;
    if(currentNode.element === element){
        head = currentNode.next;
    } else {
        while(currentNode.element !== element) {
            previousNode = currentNode;
            currentNode = currentNode.next;
        }

        previousNode.next = currentNode.next;
    }

    length --;
  };

  // Only change code below this line

  // Only change code above this line
} 

// Test your code
var conga = new LinkedList();
conga.add('Kitten');
conga.add('Puppy');
conga.add('Dog');
conga.add('Cat');
conga.add('Fish');
console.log(conga.size());
console.log(conga.removeAt(3));
console.log(conga.size());

Challenge Tests

assert((function(){var test = new LinkedList(); return (typeof test.removeAt === 'function')}()), 'message: Your <code>LinkedList</code> class should have a <code>removeAt</code> method.');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); test.add('kitten'); test.removeAt(1); return test.size() === 2}()), 'message: Your <code>removeAt</code> method should reduce the <code>length</code> of the linked list');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); test.add('kitten');  return test.removeAt(1) === 'dog'}()), 'message: Your <code>removeAt</code> method should also return the element of the removed node.');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); test.add('kitten');  return (test.removeAt(-1) === null)}()), 'message: Your <code>removeAt</code> method should also return <code>null</code> if the given index is less than <code>0</code>');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); test.add('kitten');  return (test.removeAt(3) === null)}()), 'message: Your <code>removeAt</code> method should also return <code>null</code> if the given index is equal or more than the <code>length</code> of the linked list.');

Challenge Solution

function LinkedList() { 
  var length = 0; 
  var head = null; 

  var Node = function(element){ // {1} 
    this.element = element; 
    this.next = null; 
  }; 

  this.size = function(){
    return length;
  };

  this.head = function(){
    return head;
  };

  this.add = function(element){
    var node = new Node(element);
    if(head === null){
        head = node;
    } else {
        currentNode = head;

        while(currentNode.next){
            currentNode  = currentNode.next;
        }

        currentNode.next = node;
    }

    length++;
  }; 

  this.remove = function(element) {
    var currentNode = head;
    var previousNode;
    if(currentNode.element === element){
        head = currentNode.next;
    } else {
        while(currentNode.element !== element) {
            previousNode = currentNode;
            currentNode = currentNode.next;
        }

        previousNode.next = currentNode.next;
    }

    length --;
  };

  // Only change code below this line
  this.removeAt = function(index) {
    var currentNode = head;
    var previousNode;
    var currentIndex = 0;
    if (index < 0 || index >= length){
        return null
    }
    if(index === 0){
        head = currentNode.next;
    } else {
        while(currentIndex < index) {
            currentIndex ++;
            previousNode = currentNode;
            currentNode = currentNode.next;
        }
        previousNode.next = currentNode.next
    }
    length--;
    return currentNode.element;
  }
  // Only change code above this line
} 
QuincyLarson commented 8 years ago

@elibei I've replied to you on Gitter. Keep up the hard work on these!

koustuvsinha commented 8 years ago

Binary Search Trees

Challenge Description

Lets look at one of the most popular data structures : trees. Like the literal meaning, a tree can have many leaves and many branches. Here, we will start with a simple type of tree: the binary search tree. This tree is special because at any level it can have no more than two children, a left child and a right child. Additionally, if the tree is sorted the left child's value must be less than the parent, and the parent's value must be less than the right child. Sounds confusing at first? Well, this is a rule enforced for all binary search trees (BST)'s around the world for one simple reason, fast searching. Yes, hence the name search in the Tree!

You might have guessed it by now: Suppose your job is to find the value 7 from a tree containing 1 to 10 values. Now whenever you encounter a value, instantly you can guess which child will contain 7. That is, say you face a node having a value of 5, instantly you know that 7 must lie somewhere under the right child of this node. Simple, yet elegant!

Today we are going to add a number in a tree. While adding you need to be careful about the golden rule : left child < parent < right child. So turn put on your thinking caps and solve this challenge such that every time an element is added it is in the right position! And by the way, lookout for the null values as usual! We have added a simple helper function for you to visualize the tree.

Also, we are going to display how trivial it is to search for an element in a Binary Search Tree. Given a value test whether the number isPresent in the tree!

Challenge Seed

// this helper function defines new nodes for the tree:
function Node(val) {
    this.value = val;
    this.left = null;
    this.right = null;
};

function BinarySearchTree() {
    this.root = null;

     // add an element in a BST
    this.add = function(val) {
        var root = this.root;
        // Only change your code below this line

        // Only change your code above this line
    };

    // check the tree for the presence of an element
    this.isPresent = function(query) {
        var root = this.root;
        // Only change your code below this line

        // Only change your code above this line
    };

};

var bst = new BinarySearchTree();
bst.add(2);
bst.add(1);
bst.add(3);
displayTree(bst);
bst.isPresent(3);
bst.isPresent(5);

// this helper function prints out the current tree structure to the console:
function displayTree(tree) {
    console.log(JSON.stringify(tree, null, 2))
};

Challenge Tests

assert((function(){var test = new BinarySearchTree(); return (typeof test.add === 'function')}()), 'message: Your <code>BinarySearchTree</code> class should have a <code>add</code> method.');
assert((function(){var test = new BinarySearchTree(); test.add(2); test.add(1); test.add(3); return test.root.value === 2}()), 'message: Tree should have root element created from the first input');
assert((function(){var test = new BinarySearchTree(); test.add(2); test.add(1); test.add(3); return test.root.left.value === 1}()), 'message: Tree should have left child of root element as the least value input');
assert((function(){var test = new BinarySearchTree(); test.add(2); test.add(1); test.add(3); return test.root.right.value === 3}()), 'message: Tree should have right child of root element as the greatest value input');
assert((function(){var test = new BinarySearchTree(); test.add(1); test.add(2); test.add(3); return (test.root.value === 1 && test.root.right.value ==2 && test.root.right.right.value == 3)}()), 'message: When added in ascending order, all elements must be in right nodes');
assert((function(){var test = new BinarySearchTree(); test.add(3); test.add(2); test.add(1); return (test.root.value === 3 && test.root.left.value ==2 && test.root.left.left.value == 1)}()), 'message: When added in descending order, all elements must be in left nodes');
assert((function(){var test = new BinarySearchTree(); test.add(3); test.add(2); test.add(1); var t = test.isPresent(2); var f = test.isPresent(5); return (t == true && f == false)}()), 'message: If value present in tree, return isPresent true else false');

Challenge Solution

// helper function
function Node(val) {
    this.value = val;
    this.left = null;
    this.right = null;
}

function BinarySearchTree() {
    this.root = null;

    this.add = function(val) {
        var root = this.root;
        // Only change your code below this line
        if(!root) {
            this.root = new Node(val);
            return;
        }

        var currentNode = root;
        var newNode = new Node(val);

        while(currentNode) {
            if(val < currentNode.value) {
                if(!currentNode.left) {
                    currentNode.left = newNode;
                    break;
                }
                else {
                    currentNode = currentNode.left;
                }
            } else {
                if(!currentNode.right) {
                    currentNode.right = newNode;
                    break;
                } else {
                    currentNode = currentNode.right;
                }
            }
        }
        // Only change your code above this line
    }

       this.isPresent = function(query) {
        var root = this.root;
        // Only change your code below this line
        var currentNode = root;
        while(currentNode) {
            if(currentNode.value == query) {
                return true;
            } else if(query < currentNode.value) {
                currentNode = currentNode.left;
            } else {
                currentNode = currentNode.right;
            }
        }
        return false;
        // Only change your code above this line
    }
}
ghost commented 8 years ago

Adding Elements at a Specific Index (Linked List)

Challenge Description

Let's create an addAt(index, element) method that adds an element at a given index. Just like how we removed elements at a given index, we need to keep track of the currentIndex as we traverse the linked list. When the currentIndex matches the given index, we would need to reassign the previous node's next property to reference the new added node. And the new node should reference the next node in the currentIndex.

Returning to the conga line example, a new person wants to join the line, but he wants to join in the middle. You are in the middle of the line, so you take your hands off of the person ahead of you. The new person walks over and puts his hands on the person you once had hands on, and you now have your hands on the new person.

Instructions

Create an addAt(index,element) method that adds an element at a given index. Return false if an element was unable to be added.

Note

Remember to check if the given index is a negative or is longer than the length of the linked list.

Challenge Seed

function LinkedList() { 
  var length = 0; 
  var head = null; 

  var Node = function(element){
    this.element = element; 
    this.next = null; 
  }; 

  this.size = function(){
    return length;
  };

  this.head = function(){
    return head;
  };

  this.add = function(element){
    var node = new Node(element);
    if(head === null){
        head = node;
    } else {
        currentNode = head;

        while(currentNode.next){
            currentNode  = currentNode.next;
        }

        currentNode.next = node;
    }

    length++;
  }; 

  // Only change code below this line

  // Only change code above this line

} 

// Test your code
var conga = new LinkedList();
conga.add('Kitten');
console.log(conga.addAt(1,'Puppy'));
console.log(conga.addAt(2,'Cat'));
console.log(conga.head());

Challenge Tests

assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); test.addAt(0,'cat'); return test.head().element === 'cat'}()), 'message: Your <code>addAt</code> method should reassign <code>head</code> to the new node when the given index is 0.');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); test.addAt(0,'cat'); return test.size() === 3}()), 'message: Your <code>addAt</code> method should increase the length of the linked list by one for each new node added to the linked list.');
assert((function(){var test = new LinkedList(); test.add('cat'); test.add('dog'); return (test.addAt(4,'cat') === false); }()), 'message: Your <code>addAt</code> method should return <code>false</code> if a node was unable to be added.');

Challenge Solution

function LinkedList() { 
  var length = 0; 
  var head = null; 

  var Node = function(element){
    this.element = element; 
    this.next = null; 
  }; 

  this.size = function(){
    return length;
  };

  this.head = function(){
    return head;
  };

  this.add = function(element){
    var node = new Node(element);
    if(head === null){
        head = node;
    } else {
        currentNode = head;

        while(currentNode.next){
            currentNode  = currentNode.next;
        }

        currentNode.next = node;
    }

    length++;
  }; 
  // Only change code below this line
  this.addAt = function(index, element){

    var node = new Node(element);

    var currentNode = head;
    var previousNode;
    var currentIndex = 0;

    if(index > length){
        return false;
    }

    if(index === 0){
        node.next = currentNode;
        head = node;
    } else {
        while(currentIndex < index){
            currentIndex++;
            previousNode = currentNode;
            currentNode = currentNode.next;
        }
        node.next = currentNode;
        previousNode.next = node;
    }
    length++;
  }
  // Only change code above this line

} 
ghost commented 8 years ago

This is the full Set class. I will be separating the creation of this class into multiple challenges. If anyone would like to help out, pick a method and create a challenge out of it. Check the wiki for useful information about the Set data structure.

function Set() {
    var collection = [];

    this.size = function() {
        return collection.length;
    };

    this.values = function() {
        return collection;
    };

    this.has = function(element) {
        return (collection.indexOf(element) !== -1);
    };

    this.add = function(element) {
        if(!this.has(element)){
            collection.push(element);
            return true;
        }
        return false;
    };

    this.remove = function(element) {
        if(this.has(element)){
            index = collection.indexOf(element);
            collection.splice(index,1);
            return true;
        }
        return false;
    };

    // operations
    this.union = function(otherSet) {
        var unionSet = new Set();

        var firstSet = this.values();
        var secondSet = otherSet.values();

        firstSet.forEach(function(e){
            unionSet.add(e);
        });

        secondSet.forEach(function(e){
            unionSet.add(e);
        });

        return unionSet;
    };

    this.intersection = function(otherSet) {
        var intersectionSet = new Set();

        var firstSet = this.values();

        firstSet.forEach(function(e){
            if(otherSet.has(e)){
                intersectionSet.add(e);
            }
        });

        return intersectionSet;
    };

    this.difference = function(otherSet) {
        var differenceSet = new Set();

        var firstSet = this.values();

        firstSet.forEach(function(e){
            if(!otherSet.has(e)){
                differenceSet.add(e);
            }
        });

        return differenceSet;
    };

    this.subset = function(otherSet) {
        var firstSet = this.values();
        var isSubset = true;

        firstSet.forEach(function(e){
            if(!otherSet.has(e)){
                isSubset = false;
                return;
            }
        });

        return isSubset;
    };

}
Lightwaves commented 8 years ago

I thought I'd contribute a Map implementation to this epic, I may also contribute an implementation of hashmap. I would potentially love to see a story involving hashset being implemented with hashmap along with a story describing the difference between Abstract Data Types (such as a map or set) and it's implementation using hashing or with sequential search.


function Map()
{
  var collection = [];
  var Node = function(key, value){
    this.key = key;
    this.value = value;
  };

  this.size = function(){
    return collection.length;
  };

  this.put = function(key, value)
  {
    var exists = collection.some(function(node){

      if(node.key === key)
      {
        node.value = value;
      }

      return node.key === key;
    });
    if(!exists){
      var keyvalue_pair = new Node(key, value);
      collection.push(keyvalue_pair);
    }
  };

  this.contains = function(key)
  {
    return collection.some(node => node.key === key);
  };

  this.get = function(key)
  {
    var item;

    collection.forEach(function(node){
      if(node.key === key)
      {
        item = node.value;
        return;
      }
    });
    return item;
  };

  this.empty = function()
  {
    return collection.length === 0;
  };

  this.delete = function(key)
  {
    var index;
    collection.forEach(function(node, i) {
      if(node.key === key){
        index = i;
        return;

      }

    });

    if (index !== undefined){
      collection.splice(index, 1);
    }
  };

  this.keys = function()
  {
    return collection.map(node => node.key);
  };

  this.values = function()
  {
    return collection.map(node => node.value);
  };

}
alayek commented 8 years ago

@Lightwaves cool!


One thing I am interested in seeing is how we handle the time complexity. JavaScript is a very high-level language, and therefore abstracts away most of the common operations by using data structures internally.

Like, if you want to create a Hashmap. JS objects are Hashmaps themselves (internal implementation might be with a balanced binary search tree), and obviously a very well-tested and performant one.

People would wonder -

Wait, doesn't the JS object do this thing in a simpler and neat manner? So why do I have to learn this complex stuff?

So, we have to address these questions head on.


@koustuvsinha @elibei FYI

koustuvsinha commented 8 years ago

BST - Traversals

Inorder, Preorder and Postorder

When talking about trees, let's see how we can walk along a tree. If we are using a depth-first search method, there are three primary ways we can traverse the tree and these differ in the order we visit the root and children of each node. These methods are:

  • Inorder traversal : Traverse the nodes according to their sorted order
  • Preorder traversal : Traverse the nodes, visiting the roots before any leaves
  • Postorder traversal : Traverse the nodes, visiting the leaves before any roots

These traversals can implemented using recursion. All you need to know when to recurse and when to print. Here for our convenience we will not print a node, instead we will push that node in an array and return the array.

Bonus exercise : Return the size of the tree.

Challenge

function Node(val) {
    this.value = val;
    this.left = null;
    this.right = null;
}

function BinarySearchTree() {
    this.root = null;
    this.current = null;

    this.add = function(val) {
        var root = this.root;
        if(!root) {
            this.root = new Node(val);
            return;
        }

        var currentNode = root;
        var newNode = new Node(val);

        while(currentNode) {
            if(val < currentNode.value) {
                if(!currentNode.left) {
                    currentNode.left = newNode;
                    break;
                }
                else {
                    currentNode = currentNode.left;
                }
            } else {
                if(!currentNode.right) {
                    currentNode.right = newNode;
                    break;
                } else {
                    currentNode = currentNode.right;
                }
            }
        }
    }

    this.isPresent = function(query) {
        var root = this.root;
        var currentNode = root;
        while(currentNode) {
            if(currentNode.value == query) {
                return true;
            } else if(query < currentNode.value) {
                currentNode = currentNode.left;
            } else {
                currentNode = currentNode.right;
            }
        }
        return false;
        // Only change your code above this line
    }

    /**
     * Inorder traversal. For BST, the ouput should be a sorted array
     * Hint : For each node, the strategy is: recur left, print the node data, recur right
     */
    this.inorder = function(node, arr) {
        var root = this.root;
        // Only change your code below this line

        // Only change your code above this line
    }

    /**
     * Preorder traversal.
     * Hint : For each node, the strategy is: print the node data, recur left, recur right
     */
    this.preorder = function(node, arr) {
        var root = this.root;
        // Only change your code above this line

        // Only change your code above this line
    }

    /**
     * Postorder traversal.
     * Hint : For each node, the strategy is: recur left, recur right, print the node data
     */
    this.postorder = function(node, arr) {
        var root = this.root;
        // Only change your code above this line

        // Only change your code above this line
    }

    /**
     * Size of tree.
     */
    this.size = function() {
        // Only change your code above this line

        // Only change your code above this line
    }

}

// helper function
function displayTree(tree) {
    console.log(JSON.stringify(tree, null, 2))
}

var bst = new BinarySearchTree();
bst.add(2);
bst.add(1);
bst.add(3);
displayTree(bst);

var a = bst.inorder();
console.log(a);
console.log(bst.size());

var b = bst.postorder();
console.log(b);

var c = bst.preorder();
console.log(c);

Tests

assert((function(){var test = new BinarySearchTree(); return (typeof test.inorder === 'function' && typeof test.preorder === 'function' && typeof test.postorder === 'function' && typeof test.size === 'function')}()), 'message: Your <code>BinarySearchTree</code> class should have <code>inorder</code>, <code>preorder</code>, <code>postorder</code> and <code>size</code> methods.');
assert((function(){var test = new BinarySearchTree(); test.add(1); test.add(2); test.add(3); return (test.root.value === 1 && test.root.right.value ==2 && test.root.right.right.value == 3)}()), 'message: When added in ascending order, all elements must be in right nodes');
assert((function(){var test = new BinarySearchTree(); test.add(3); test.add(2); test.add(1); return (test.root.value === 3 && test.root.left.value ==2 && test.root.left.left.value == 1)}()), 'message: When added in descending order, all elements must be in left nodes');
assert((function(){var test = new BinarySearchTree(); test.add(2); test.add(1); test.add(3); var i = test.inorder(); return (i[0] == 1 && i[1] == 2 && i[2] == 3)}()), 'message: Inorder traversal must return sorted array');
assert((function(){var test = new BinarySearchTree(); test.add(2); test.add(1); test.add(3); var i = test.preorder(); return (i[0] == 2 && i[1] == 1 && i[2] == 3)}()), 'message: Preorder traversal must return BFS array');
assert((function(){var test = new BinarySearchTree(); test.add(2); test.add(1); test.add(3); var i = test.postorder(); return (i[0] == 1 && i[1] == 3 && i[2] == 2)}()), 'message: Postrder traversal must return reversed array');
assert((function(){var test = new BinarySearchTree(); test.add(2); test.add(1); test.add(3); var l = test.size(); return (l === 3)}()), 'message: Postrder traversal must return reversed array');

Solution

function Node(val) {
    this.value = val;
    this.left = null;
    this.right = null;
}

function BinarySearchTree() {
    this.root = null;
    this.current = null;

    this.add = function(val) {
        var root = this.root;
        // Only change your code below this line
        if(!root) {
            this.root = new Node(val);
            return;
        }

        var currentNode = root;
        var newNode = new Node(val);

        while(currentNode) {
            if(val < currentNode.value) {
                if(!currentNode.left) {
                    currentNode.left = newNode;
                    break;
                }
                else {
                    currentNode = currentNode.left;
                }
            } else {
                if(!currentNode.right) {
                    currentNode.right = newNode;
                    break;
                } else {
                    currentNode = currentNode.right;
                }
            }
        }
        // Only change your code above this line
    }

    this.isPresent = function(query) {
        var root = this.root;
        // Only change your code below this line
        var currentNode = root;
        while(currentNode) {
            if(currentNode.value == query) {
                return true;
            } else if(query < currentNode.value) {
                currentNode = currentNode.left;
            } else {
                currentNode = currentNode.right;
            }
        }
        return false;
        // Only change your code above this line
    }

    /**
     * Inorder traversal. For BST, the ouput should be a sorted array
     * Hint : For each node, the strategy is: recur left, print the node data, recur right
     */
    this.inorder = function(node, arr) {
        var root = this.root;
        var currentNode = node || root;
        var arr = arr || [];
        if(currentNode.left) {
            this.inorder(currentNode.left,arr)
        }
        arr.push(currentNode.value);
        if(currentNode.right) {
            this.inorder(currentNode.right,arr);
        }
        return arr;
    }

    /**
     * Size of tree.
     * Hint : Re-use inorder traversal
     */
    this.size = function() {
        var arr = this.inorder();
        return arr.length;
    }

    /**
     * Preorder traversal.
     * Hint : For each node, the strategy is: print the node data, recur left, recur right
     */
    this.preorder = function(node, arr) {
        var root = this.root;
        var currentNode = node || root;
        var arr = arr || [];
        arr.push(currentNode.value);
        if(currentNode.left) {
            this.inorder(currentNode.left,arr)
        }
        if(currentNode.right) {
            this.inorder(currentNode.right,arr);
        }
        return arr;
    }

    /**
     * Postorder traversal.
     * Hint : For each node, the strategy is: recur left, recur right, print the node data
     */
    this.postorder = function(node, arr) {
        var root = this.root;
        var currentNode = node || root;
        var arr = arr || [];
        if(currentNode.left) {
            this.inorder(currentNode.left,arr)
        }
        if(currentNode.right) {
            this.inorder(currentNode.right,arr);
        }
        arr.push(currentNode.value);
        return arr;
    }

}

// helper function
function displayTree(tree) {
    console.log(JSON.stringify(tree, null, 2))
}
QuincyLarson commented 8 years ago

@koustuvsinha Wow - that is one huge data structure challenge. Would it be worth breaking this into three separate challenges so you can better introduce these three types of traversals - and make the seed code and tests less intimidating?

alayek commented 8 years ago

@QuincyLarson I agree. We should take time to have the camper memorize and master these concepts.

QuincyLarson commented 8 years ago

@koustuvsinha campers are really excited about these data structures. Can you commit to finishing these this week so we can launch them? 💪 😄 💪

alayek commented 8 years ago

@QuincyLarson he just moved to Canada for higher studies, and it might take him some time to settle. I will try to pitch in and finish off whatever we can by this week.

koustuvsinha commented 8 years ago

@QuincyLarson sure I will try to do it by this week, but need a little help. @alayek can you help in for the Graph section? I will try to finish off the tree part

On Tue, Sep 6, 2016 at 1:58 AM -0400, "Arijit Layek" notifications@github.com<mailto:notifications@github.com> wrote:

@QuincyLarsonhttps://github.com/QuincyLarson he just moved to Canada for higher studies, and it might take him some time to settle. I will try to pitch in and finish off whatever we can by this week.

You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/FreeCodeCamp/CurriculumExpansion/issues/17#issuecomment-244856807, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AE4rBsqXgaZ3LARQWjJkdLhCeA5BszpLks5qnQEWgaJpZM4JEIre.

koustuvsinha commented 8 years ago

BST - Deletion

Tree Deletion

Deleting nodes from a tree can be complicated, but it is useful to know how to do this. If we want to delete a node, there are three cases we should anticipate. The node may be:

  1. A leaf node
  2. A node with just one child
  3. A node with two children

Each case requires special handling. Let's discuss the cases in detail:

Case 1: Node to delete is a leaf node

No brainer here, just obliterate the leaf node. No special things to do here, invalidate the pointer from its parent to itself!

Case 2: Node to delete has only one child

First, figure out which child it is, i.e either a left child or a right child. Then "skip" the pointer from its parent to its child, It's simple as that!

Case 3: Node to delete has two children

Now here is when the things start going crazy! Let us see this with an example image : https://en.wikipedia.org/wiki/File:Binary_search_tree.svg With a root of 8 and a left child of 3, what would happen if the 3 was removed? There are two possibilities: 1 (3′s left child, called the in-order predecessor) could take the place of 3 or 4 (the left-most child of the right subtree, called the in-order successor) can take the place of 3.

Either of these two options is appropriate. To find the in-order predecessor, the value that comes before the value being removed, examine the left subtree of the node to remove and select the right-most descendant; to find the in-order successor, the value that comes immediately after the value being removed, reverse the process and examine the right subtree for the left-most descendant. Each of these requires another traversal of the tree to complete the operation.

Sounds tough? See this tutorial : https://webdocs.cs.ualberta.ca/~holte/T26/del-from-bst.html, or better yet, play with this superb animation : https://www.cs.usfca.edu/~galles/visualization/BST.html

Ready? Lets begin coding!

Challenge

function Node(val) {
    this.value = val;
    this.left = null;
    this.right = null;
}

function BinarySearchTree() {
    this.root = null;
    this.current = null;

    this.add = function(val) {
        var root = this.root;
        if(!root) {
            this.root = new Node(val);
            return;
        }

        var currentNode = root;
        var newNode = new Node(val);

        while(currentNode) {
            if(val < currentNode.value) {
                if(!currentNode.left) {
                    currentNode.left = newNode;
                    break;
                }
                else {
                    currentNode = currentNode.left;
                }
            } else {
                if(!currentNode.right) {
                    currentNode.right = newNode;
                    break;
                } else {
                    currentNode = currentNode.right;
                }
            }
        }
    }

    this.isPresent = function(query) {
        var root = this.root;
        var currentNode = root;
        while(currentNode) {
            if(currentNode.value == query) {
                return true;
            } else if(query < currentNode.value) {
                currentNode = currentNode.left;
            } else {
                currentNode = currentNode.right;
            }
        }
        return false;
    }

    this.remove = function (val) {
        var found = false,
                parent = null,
                current = this.root,
                childCount,
                replacement,
                replacementParent

        // find the node and set `found` = true, with current pointing to the found node
        // and `parent` its parent
        // Only change your code below this line

        // Only change your code above this line

        // If node was found, then we proceed deletion
        if(found) {
            // figure out how many children (i.e either 0 or 1 or 2)
            // Only change your code below this line

            // Only change your code above this line

            // case : if value is at the root
            if(current == this.root) {
                console.log('root case')
                switch(childCount) {
                    // case : no children, so we will just proceed to erase the root
                    case 0:
                        console.log('case 0')
                        // Only change your code below this line

                        // Only change your code above this line
                        break;
                    // case : one child, figure out which child and then use that as root
                    case 1:
                        console.log('case 1')
                        // Only change your code below this line

                        // Only change your code above this line
                        break;
                    // case : two children
                    case 2:
                        console.log('case 2')
                        // let us select new root to be old root's left child
                        replacement = this.root.left;
                        // now let's find the right most leaf node to be the new root
                        // find the right most leaf node of `replacement` and save the its parent
                        // within `replacementParent`
                        // Only change your code below this line

                        // Only change your code above this line

                        // If it is not the first left node
                        if (replacementParent !== null) {
                            // remove the new root from its previous position
                            // i.e, replace the right child of replacement parent with left child of the
                            // node to be replaced
                            // Only change your code below this line

                            // Only change your code above this line

                            // Give the new root all of old root's children
                            // Only change your code below this line

                            // Only change your code above this line
                        } else {
                            // assign the children to the root's right child
                            // Only change your code below this line

                            // Only change your code above this line
                        }
                        // finally assign new root
                        this.root = replacement;
                        break;
                }
            }
            // case if value is not root
            else {
                console.log('child case')
                switch(childCount) {
                    // case 0 : no children, just detach it from parent
                    case 0:
                        console.log('case 0')
                        // if current's value is less than its parents, null out the left pointer,
                        // else null out the right pointer
                        // Only change your code below this line

                        // Only change your code above this line
                        break;
                    // case 1: one child, just reassign to parent
                    case 1:
                        console.log('case 1')
                        // if current's value is less than its parents, reset the left pointer,
                        // else reset the right pointer
                        // Only change your code below this line

                        // Only change your code above this line
                        break;
                    // case 2: two child
                    case 2:
                        console.log('case 2')
                        // reset pointers for new traversal
                        replacement = current.left;
                        // find the right most node
                        // find the right most leaf node of `replacement` and save the its parent
                        // within `replacementParent`
                        // Only change your code below this line

                        // Only change your code above this line
                        if (replacementParent) {
                            // remove the new node from its previous position
                            // i.e, replace the right child of replacement parent with left child of the
                            // node to be replaced
                            // Only change your code below this line

                            // Only change your code above this line

                            // assign all current's children to the replacement
                            // Only change your code below this line

                            // Only change your code above this line
                        } else {
                            // assign the children to the current's right child
                            // Only change your code below this line

                            // Only change your code above this line
                        }
                        // place the replacement in the right spot
                        // if current value is less than parent, add replacement as parent left child,
                        // else right
                        // Only change your code below this line

                        // Only change your above below this line
                        break;
                }
            }
        }
    }
}

// helper function
function displayTree(tree) {
    console.log(JSON.stringify(tree, null, 2))
}

var bst = new BinarySearchTree();
bst.add(3);
bst.add(1);
bst.add(4);
bst.add(2);
bst.add(0);
bst.add(5);
bst.add(6);
displayTree(bst);

bst.remove(1);
displayTree(bst);

Tests

assert((function(){var test = new BinarySearchTree(); return (typeof test.remove === 'function' && typeof test.add === 'function')}()), 'message: Your <code>BinarySearchTree</code> class should have <code>add</code> and <code>remove</code> methods.');
assert((function(){var test = new BinarySearchTree(); test.add(3); test.add(1); test.add(4); test.add(2); test.add(0); test.add(5); test.add(6); test.remove(6); return (test.isPresent(6) !== true)}()), 'message: Leaf node should be removed properly');
assert((function(){var test = new BinarySearchTree(); test.add(3); test.add(1); test.add(4); test.add(2); test.add(0); test.add(5); test.add(6); test.remove(3); return (test.isPresent(3) !== true && test.root.value == 2)}()), 'message: Root node should be removed properly and replaced by suitable candidate');
assert((function(){var test = new BinarySearchTree(); test.add(3); test.add(1); test.add(4); test.add(2); test.add(0); test.add(5); test.add(6); test.remove(5); return (test.isPresent(5) !== true)}()), 'message: Node with one child should be removed properly');
assert((function(){var test = new BinarySearchTree(); test.add(3); test.add(1); test.add(4); test.add(2); test.add(0); test.add(5); test.add(6); test.remove(1); return (test.isPresent(1) !== true && test.root.left.value == 0)}()), 'message: Node with two child should be removed properly and replaced by suitable candidate');

Solution

function Node(val) {
    this.value = val;
    this.left = null;
    this.right = null;
}

function BinarySearchTree() {
    this.root = null;
    this.current = null;

    this.add = function(val) {
        var root = this.root;
        if(!root) {
            this.root = new Node(val);
            return;
        }

        var currentNode = root;
        var newNode = new Node(val);

        while(currentNode) {
            if(val < currentNode.value) {
                if(!currentNode.left) {
                    currentNode.left = newNode;
                    break;
                }
                else {
                    currentNode = currentNode.left;
                }
            } else {
                if(!currentNode.right) {
                    currentNode.right = newNode;
                    break;
                } else {
                    currentNode = currentNode.right;
                }
            }
        }
    }

    this.isPresent = function(query) {
        var root = this.root;
        var currentNode = root;
        while(currentNode) {
            if(currentNode.value == query) {
                return true;
            } else if(query < currentNode.value) {
                currentNode = currentNode.left;
            } else {
                currentNode = currentNode.right;
            }
        }
        return false;
    }

    this.remove = function (val) {
        var found = false,
                parent = null,
                current = this.root,
                childCount,
                replacement,
                replacementParent

        // find the node and set `found` = true, with current pointing to the found node
        // and `parent` its parent
        // Only change your code below this line
        while(!found && current) {
            if(val < current.value) {
                parent = current;
                current = current.left;
            }
            else if(val > current.value) {
                parent = current;
                current = current.right;
            }
            else {
                found = true;
            }
        }
        // Only change your code above this line

        // If node was found, then we proceed deletion
        if(found) {
            // figure out how many children (i.e either 0 or 1 or 2)
            // Only change your code below this line
            childCount = (current.left !== null ? 1 : 0) + (current.right !== null ? 1 : 0);
            // Only change your code above this line

            // case : if value is at the root
            if(current == this.root) {
                console.log('root case')
                switch(childCount) {
                    // case : no children, so we will just proceed to erase the root
                    case 0:
                        console.log('case 0')
                        // Only change your code below this line
                        this.root = null;
                        // Only change your code above this line
                        break;
                    // case : one child, figure out which child and then use that as root
                    case 1:
                        console.log('case 1')
                        // Only change your code below this line
                        this.root = (current.right === null ? current.left : current.right);
                        // Only change your code above this line
                        break;
                    // case : two children
                    case 2:
                        console.log('case 2')
                        // let us select new root to be old root's left child
                        replacement = this.root.left;
                        // now let's find the right most leaf node to be the new root
                        // find the right most leaf node of `replacement` and save the its parent
                        // within `replacementParent`
                        // Only change your code below this line
                        while (replacement.right !== null) {
                            replacementParent = replacement;
                            replacement = replacement.right;
                        }
                        // Only change your code above this line

                        // If it is not the first left node
                        if (replacementParent !== null) {
                            // remove the new root from its previous position
                            // i.e, replace the right child of replacement parent with left child of the
                            // node to be replaced
                            // Only change your code below this line
                            replacementParent.right = replacement.left;
                            // Only change your code above this line

                            // Give the new root all of old root's children
                            // Only change your code below this line
                            replacement.right = this.root.right;
                            replacement.left = this.root.left;
                            // Only change your code above this line
                        } else {
                            // assign the children to the root's right child
                            // Only change your code below this line
                            replacement.right = this.root.right;
                            // Only change your code above this line
                        }
                        // finally assign new root
                        this.root = replacement;
                        break;
                }
            }
            // case if value is not root
            else {
                console.log('child case')
                switch(childCount) {
                    // case 0 : no children, just detach it from parent
                    case 0:
                        console.log('case 0')
                        // if current's value is less than its parents, null out the left pointer,
                        // else null out the right pointer
                        // Only change your code below this line
                        if (current.value < parent.value) {
                            parent.left = null;
                        } else {
                            parent.right = null;
                        }
                        // Only change your code above this line
                        break;
                    // case 1: one child, just reassign to parent
                    case 1:
                        console.log('case 1')
                        // if current's value is less than its parents, reset the left pointer,
                        // else reset the right pointer
                        // Only change your code below this line
                        if (current.value < parent.value) {
                            parent.left = (current.left === null ? current.right : current.left);
                        } else {
                            parent.right = (current.left === null ? current.right : current.left);
                        }
                        // Only change your code above this line
                        break;
                    // case 2: two child
                    case 2:
                        console.log('case 2')
                        // reset pointers for new traversal
                        replacement = current.left;
                        // find the right most node
                        // find the right most leaf node of `replacement` and save the its parent
                        // within `replacementParent`
                        // Only change your code below this line
                        while (replacement.right) {
                            replacementParent = replacement;
                            replacement = replacement.right;
                        }
                        // Only change your code above this line
                        if (replacementParent) {
                            // remove the new node from its previous position
                            // i.e, replace the right child of replacement parent with left child of the
                            // node to be replaced
                            // Only change your code below this line
                            replacementParent.right = replacement.left;
                            // Only change your code above this line

                            // assign all current's children to the replacement
                            // Only change your code below this line
                            replacement.right = current.right;
                            replacement.left = current.left;
                            // Only change your code above this line
                        } else {
                            // assign the children to the current's right child
                            // Only change your code below this line
                            replacement.right = current.right;
                            // Only change your code above this line
                        }
                        // place the replacement in the right spot
                        // if current value is less than parent, add replacement as parent left child,
                        // else right
                        // Only change your code below this line
                        if (current.value < parent.value) {
                            parent.left = replacement;
                        } else {
                            parent.right = replacement;
                        }
                        // Only change your above below this line
                        break;
                }
            }
        }
    }
}

This is quite complex challenge, mostly because of the third case. @alayek can you test whether the challenge is too complex for an average user or not?

QuincyLarson commented 8 years ago

@koustuvsinha wow - this is really well designed. @alayek I would define "too complex for an average user" as being significantly harder than our intermediate algorithm challenges.

alayek commented 8 years ago

@koustuvsinha as my Physics teacher used to say, difference between solved and unsolved is a diagram. BST is very easy if you can visualize the tree diagram. Heck, even AVL tree rotations get easy if you can just draw the nodes and perform the ops on a paper with pen.

@QuincyLarson any way we can include some diagram or gifs? That would make it lot simpler to comprehend.

QuincyLarson commented 8 years ago

@koustuvsinha @alayek I was actually hoping to remove diagrams completely from the challenge descriptions, since they are an accessibility issue, and on some resolutions are so small they're difficult to view. If we were to include any, they would need to be as clear and simple as possible.

koustuvsinha commented 8 years ago

@alayek @QuincyLarson not an issue, we can include relevant links in the problem statement and / or comments in code to redirect users to the diagrams / animations

QuincyLarson commented 8 years ago

@koustuvsinha yes - my advice would be to keep external links to an absolute minimum. If we could link directly to a static image and open it in a new tab, then that would be a graceful way of handling the image issue - better than trying to put the image in-line.

QuincyLarson commented 7 years ago

@alayek @koustuvsinha @erictleung how is progress coming along on these?

erictleung commented 7 years ago

@QuincyLarson I could work on the arrays and some of the graph data structures, but I don't think I'll be able to do the graph algorithms in a reasonable amount of time.

QuincyLarson commented 7 years ago

@erictleung Thanks. Every little bit helps! We want to release as many as we can next week.

@alayek @koustuvsinha do you know any other campers who might be able to also contribute to these?

erictleung commented 7 years ago

Typed Arrays

Challenge Description

Arrays are JavaScript objects that can hold a lot of different elements.

var complexArr = [1, 5, "2", "Word", {"name": "James"}];

Basically what happens in the background is that your browser will automatically give the right amount of memory space for that array. It will also change as needed if you add or remove data.

However, in the world of high performance and different element types, sometimes you need to be more specific on how much memory is given to an array.

Typed arrays are the answer to this problem. You are now able to say how much memory you want to give an array. Below is a basic overview of the different types of arrays available and the size in bytes for each element in that array.

TypeEach element size in bytes
Int8Array1
Uint8Array1
Uint8ClampedArray1
Int16Array2
Uint16Array2
Int32Array4
Uint32Array4
Float32Array4
Float64Array8

There are two ways in creating these kind of arrays. One way is to create it directly. Below is how to create a 7 length Int16Array.

var i8 = new Int16Array(3);
console.log(i8);
// Returns [0, 0, 0]

You can also create a buffer to assign how much data (in bytes) you want the array to take up.

Note To create typed arrays using buffers, you need to assign the number of bytes to be a multiple of the bytes listed above.

// Create same Int16Array array differently
var byteSize = 6; // Needs to be multiple of 2
var buffer = new ArrayBuffer(byteSize);
var i8View = new Int16Array(buffer);
buffer.byteLength; // Returns 6
i8View.byteLength; // Returns 6
console.log(i8View); // Returns [0, 0, 0]

Buffers are general purpose objects that just carry data. You cannot access them normally. To access them, you need to first create a view.

i8View[0] = 42;
console.log(i8View); // Returns [42, 0, 0]

Note Typed arrays do not have some of the methods traditional arrays have such as .pop() or .push(). Typed arrays also fail Array.isArray() that checks if something is an array. Although simplier, this can be an advantage for less-sophisticated JavaScript engines to implement them.

Instructions

First create a buffer that is 64-bytes. Then create a Int32Array typed array with a view of it called int32View.

Here are some helpful links:

The code for the table used above is:

<table class='table table-striped'><tr><th>Type</th><th>Each element size in bytes</th></tr><tr><td><code>Int8Array</code></td><td>1</td></tr><tr><td><code>Uint8Array</code></td><td>1</td></tr><tr><td><code>Uint8ClampedArray</code></td><td>1</td></tr><tr><td><code>Int16Array</code></td><td>2</td></tr><tr><td><code>Uint16Array</code></td><td>2</td></tr><tr><td><code>Int32Array</code></td><td>4</td></tr><tr><td><code>Uint32Array</code></td><td>4</td></tr><tr><td><code>Float32Array</code></td><td>4</td></tr><tr><td><code>Float64Array</code></td><td>8</td></tr></table>

The class='table table-striped' is specific to FreeCodeCamp's CSS style.

Challenge Seed

var buffer;
var i32View;

Challenge Tests

assert(buffer.byteLength === 64, 'message: Your `buffer` should be 64 bytes large.');
assert(i32View.byteLength === 64, 'message: Your `i32View` view of your buffer should be 64 bytes large.');
assert(i32View.length === 16, 'message: Your `i32View` view of your buffer should be 16 elements long.');

Challenge Solution

var buffer = new ArrayBuffer(64);
var i32View = new Int32Array(buffer);
alayek commented 7 years ago

@erictleung thanks for adding the Array challenge!


I was reading this

Basically what happens in the background is that your browser will automatically give the right amount of memory space for that array

Do you think we should dedicate a section, or at least link to some of hike challenge to point out what the word memory means in this context?