In this assignment, we'll implement and test two basic data structures:
1. Queue
A queue works like a line for an amusement park ride -- people enter at the end of the queue and leave from the front (FIFO: first in, first out).
Issuing instructions to your sandwich-making robot.
2. Stack
A stack works like a stack of plates -- plates are added and removed from the the top of the stack (LIFO: last in, first out).
Implement both stack and queue data structures in each of four instantiation styles. The functional style is stubbed out in src/functional/queue.js
and src/functional/stack.js
to get you started.
- No arrays! Instead, use an object with numeric keys.
- Pass all the tests (open
SpecRunner.html
in a browser).
-
Implement a stack with the following methods:
push(string)
- Add a string to the top of the stackpop()
- Remove and return the string on the top of the stacksize()
- Return the number of items on the stack
-
Implement a queue with the following methods:
enqueue(string)
- Add a string to the back of the queuedequeue()
- Remove and return the string at the front of the queuesize()
- Return the number of items in the queue
- Functional instantiation: a simple "maker" pattern
- Do:
- Work within the
src/functional/
folder. - Define all functions and properties within the maker function.
- Work within the
- Don't:
- Use the keyword
new
, the keywordthis
, or anyprototype
chains. - Capitalize the maker function name.
- Use the keyword
- Example: The provided classes
Stack
andQueue
already follow this pattern
- Do:
- Functional instantiation with shared methods: same as step 1, but with shared methods
- Do:
- Work within the
src/functional-shared/
folder. - Create an object that holds the methods that will be shared by all instances of the class.
- You'll need to use the keyword
this
in your methods. - Use
_.extend
to copy the methods onto the instance.
- Work within the
- Don't:
- Use the keyword
new
orprototype
chains
- Use the keyword
- Example: functional instantiation example
- Do:
- Prototypal instantiation: using
Object.create
- Do:
- Work within the
src/protoypal/
folder. - Use
Object.create
with the object from step 2 to create instances of your class
- Work within the
- Don't:
- Use the keyword
new
- Use the keyword
- Example: prototypal instantiation example
- Do:
- Pseudoclassical instantiation: create instances with the keyword
new
- Do:
- Work within the
src/pseudoclassical/
folder. - Capitalize your function name to indicate that it is intended to be run with the keyword
new
- Use the keyword
new
when instantiating your class - Use the keyword
this
in your constructor
- Work within the
- Don't:
- Declare the instance explicitly
- Return the instance explicitly
- Example: pseudoclassical instantiation example
- Do:
- Use the Chrome profiling tools to compare the performance of each instantiation pattern.
- Create a new HTML page with your data structures and an additional profiling script. It should instantiate and use a large number of stacks and queues.
- Reload the page with the CPU profiler running to investigate the runtime of your functions.
- Take a heap snapshot to investigate object allocations and memory use.
- Optionally, reload the page with the heap profiler running to investigate garbage collection behavior.
- Do this for each of the instantiation styles, record, and compare the results. Write a brief analysis.
This repo holds a mostly-empty Mocha test suite. To run it, open up SpecRunner.html
.
Some failing specs are included. You're welcome! You should make them pass, then write more specs and more code.
1. Linked List
- A
linkedList
class, in functional style, with the following properties:-
.head
property, alinkedListNode
instance -
.tail
property, alinkedListNode
instance -
.addToTail()
method, takes a value and adds it to the end of the list -
.removeHead()
method, removes the first node from the list and returns its value -
.contains()
method, returns boolean reflecting whether or not the passed-in value is in the linked list - What is the time complexity of the above functions?
-
2. Tree
- A
tree
class, in functional with shared methods style, with the following properties:-
.children
property, an array containing a number of subtrees -
.addChild()
method, takes any value, sets that as the target of a node, and adds that node as a child of the tree - A
.contains()
method, takes any input and returns a boolean reflecting whether it can be found as the value of the target node or any descendant node - What is the time complexity of the above functions?
-
3. Graph
- Implement a
graph
class, in pseudoclassical style, with the following properties:- It is an undirected graph. It does not have to be 'connected'.
- An
.addNode()
method that takes a new node and adds it to the graph - A
.contains()
method that takes any node and returns a boolean reflecting whether it can be found in the graph - A
.removeNode()
method that takes any node and removes it from the graph, if present. All edges connected to that Node are removed as well. - An
.addEdge()
method that creates a edge (connection) between two nodes if they both are present within the graph - A
.hasEdge()
method that returns a boolean reflecting whether or not two nodes are connected - A
.removeEdge()
method that removes the connection between two nodes - A
.forEachNode()
method that traverses through the graph, calling a passed in function once on each node - What is the time complexity of the above functions?
4. Set
- A
set
class, in prototypal style, with the following properties:- An
.add()
method, takes any string and adds it to the set - A
.contains()
method, takes any string and returns a boolean reflecting whether it can be found in the set - A
.remove()
method, takes any string and removes it from the set, if present - What is the time complexity of the above functions?
- Note: Sets should not use up any more space than necessary. Once a value is added to a set, adding it a second time should not increase the size of the set.
- Note: Until the extra credit section, your sets should handle only string values.
- Note: This is a rather simple data structure. Take a look at the Wikipedia entry. Which native Javascript type fits the requirements best?
- An
5. Hash Table
- A
hashTable
class, in pseudoclassical style:- First: Play with each of the helper functions provided to get a sense of what they do.
- You will use the provided hash function to convert any key into an array index. Try interacting with it from the console first.
- A
limitedArray
helper has been provided for you. Use it to store all inserted values. Try interacting with it from the console first.
- Make the following properties appear on all instances:
- An
.insert()
method - A
.retrieve()
method - A
.remove()
method
- An
- What is the time complexity of the above functions?
- First: Play with each of the helper functions provided to get a sense of what they do.
- Using your understanding of hash tables, refactor your set implementation from above to run in constant time
On Objects and Hash Tables: An astute hacker might find themself wondering "Is it not so that a JavaScript object is a hash table?" or even further, "Why would I ever need to create a hash table in JavaScript?" While it is true that objects and hash tables are functionally similar, knowing how a hash table works is hugely important as they are an incredibly useful and fundamental data structure. To have detailed knowledge of how a hash table is constructed will give you valuable insight on your path to code mastery. Additionally, other languages might not provide the convenience of JavaScript's object class, meaning you may someday have to put your hash table construction abilities to good use.
**Interesting Aside: JavaScript objects aren't necessarily backed by hash tables. Despite the similarities, the ECMA-262 standard makes no restrictions on how JavaScript objects are implmented. The V8 JavaScript engine, which is used in Chrome, implements objects in a way that is significantly faster than using a hash table.
- Implement a
binarySearchTree
class with the following properties:- A
.left
property, a binary search tree (BST) where all values are lower than than it the current value. - A
.right
property, a BST where all values are higher than than it the current value. - A
.insert()
method, which accepts a value and places in the tree in the correct position. - A
.contains()
method, which accepts a value and returns a boolean reflecting whether or not the value is contained in the tree. - A
.depthFirstLog()
method, which accepts a callback and executes it on every value contained in the tree. - What is the time complexity of the above functions?
- Use case: Given a list of a million numbers, write a function that takes a new number and returns the closest number in the list using your BST. Profile this against the same operation using an array.
- A
tl;dr: prefix private properties and methods with an underscore
APIs are more than just URLs that return data; API is a general term that refers to the visible surface of any system, object, or library with which your code interacts. E.g., an airplane has an API that consists of knobs, dials, pedals, and a yoke which allow the pilot to make use of the airplane's underlying implementation--an engine, wings, and rudders. Importantly, the next model of airplane will probably have improvements to the engine, but the pilot need not know about this, as the controls will remain basically unchanged.
This relationship between APIs and implementations--that they remain independent--is what keeps the towering stacks of software we use everyday from falling over.
In JavaScript, because there is no concept of private variables (except through closure), sometimes API and implementation are both visible. If it's impossible to distinguish between API and implementation, you might depend upon a piece of implementation that later changes and breaks your code.
To prevent this from happening to your collaborators and consumers, indicate private properties and methods by prefixing them with an underscore. This is how we do.
Note: Prompts have varying levels of difficulty. In order to decide how to allocate your time, please review all of them before starting.
Write all of the following data structures and improvements in the order shown. Use any style you like.
- Create a doubly-linked-list, with all the methods of
your linked list, and add the following properties:
- An
.addToHead()
method which takes a value and adds it to the front of the list. - A
.removeTail()
method which removes the last node from the list and returns its value.
- Note: each
node
object will need to have a new.previous
property pointing to the node behind it (or tonull
); this is what makes it a doubly-linked list.
- An
- Add parent links to your tree
- A
.parent
property, which refers to the parent node or null when there is no node - A
.removeFromParent()
method, which disassociates the tree with its parent (in both directions)
- A
- To prevent excessive collisions, make your
hashTable
double in size as soon as 75 percent of the spaces have been filled. - To save space, make sure the
hashTable
shrinks when space usage falls below 25 percent. - Implement a
.traverse()
method on yourtree
. Your.traverse()
should accept a callback and execute it on every value contained in the tree. -
.breadthFirstLog()
method forbinarySearchTee
, logs the nodes contained in the tree using a breadth-first approach. - Make your
set
capable of handling numbers as well as strings. - Make your
set
capable of handling input objects of any type. - Make your
binarySearchTree
rebalance as soon as the max depth is more than twice the minimum depth.
- Implement a
bloomFilter
:- Read about it here: Bloom Filters. tl;dr: It's a probabalistic hash table that can gain enormous space and speed advantages over traditional hash tables. Downside is it has to accept a false positive rate when looking up whether an item is in the table. Use cases are often for checking against a giant list locally and only doing a full lookup when the local one comes back positive. You may find this tutorial useful.
- Create an "m=18, k=3" bloom filter. This means 18 slots, with 3 hash functions.
- Run a small loop that runs 10,000 trials of trying to retreive a mix of items that are in the filter and not in the filter.
- Record the empirical rate of false-positives by comparing your result with what you know to be true from the inputs you selected.
- Compare that rate with the approximation given for Bloom filters, which is approximated as
(1- e^(-kn/m))^k
- Write a
prefixTree
that can handle autocomplete for T9-style texting. - Write a b-tree.
- Write a red-black tree.
- Design a data structure that finds every English word that can be made from a given bag of Scrabble letters.
- Optimize the algorithm and the data structure to return the set of words as quickly as possible.
- Your priority for this task is to optimize for time complexity, but do try to avoid wasted space in your solution.
- You can assume you have all the time required to do preprocessing on a dictionary of English words.