diff --git a/.gitignore b/.gitignore index 7bbc71c..280527d 100644 --- a/.gitignore +++ b/.gitignore @@ -99,3 +99,4 @@ ENV/ # mypy .mypy_cache/ +node_modules/ diff --git a/README.md b/README.md index 90a6e94..bb65712 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,20 @@ **Testing Tools**: pytest, pytest-cov +## Sorting Algorithms: + +* **Insertion Sort** - *The insertion sort algorithm sorts a list of numbers by looping through each individual number in the list one by one, and (if they are smaller than the last index) swapping the two numbers. Unlike bubble sort, after this swap the sort continues to swap the number down into lower indexed positions until it finds the right position. The runtime for this function is at worst-case O(n^2), because in that scenario the list would be exactly the reverse of what it should be.* + ## Data Structures: * **Binary Search Tree** — *a BST is a "tree shaped" data structure containing nodes. Each node can have a maximum of two children or "leaves," and all node values are properly located based on its parents and siblings values. Nodes to the left of the "root"/head node have values smaller than the root. Those to the right have values larger than the root. There are no duplicate values.* * **Trie Tree** - *a Trie Tree is a "tree shaped" data structure containing nodes with references to letters. These nodes string together (using each node's "children" and "parent" attriutes) to form words. This tree allows for quick lookup time of words, and is used for things such as word suggestion/auto-complete.* +* **Linked List (JavaScript)** - *A singly linked list is a data structure made of nodes, all of which have a single "next" pointer pointing from the head to the tail. The runtime of traversing the LL is O(n) because it grows proportionally with the length of the list.* + +* **Stack (JavaScript)** - *A stack is much like a singly linked list. It can be visualized as a stack of cards, and it is a "last in, first out" data structure that pops and pushes to the same end (or 'top') of the structure. It has O(n) runtime.* + ## Time Complexities: * balance() = *This BST function returns the balance (or size difference) between the left and right parts of the tree. Its runtime is O(1), because it always takes the same amount of time to run regardless of tree size, and only performs simple subtraction.* diff --git a/js-src/js-doubly-linked-list.js b/js-src/js-doubly-linked-list.js new file mode 100644 index 0000000..0ccc69e --- /dev/null +++ b/js-src/js-doubly-linked-list.js @@ -0,0 +1,138 @@ +"use strict"; + +class Node { + constructor(data, nextNode=null, prevNode=null) { + this.data = data + this.nextNode = nextNode + this.prevNode = prevNode + } +} + +class DLL { + constructor(iter=null) { + this.head = null + this.tail = null + this.counter = 0 + if (Array.isArray(iter)) { + iter.forEach(item => this.push(item)) + } + else if (iter !== null) { + throw 'LinkedList only takes arrays as inputs.' + } + } + + push(val) { + this.head = new Node(val, this.head) + this.counter ++ + if (this.size() === 1) { + this.tail = this.head + } else { + this.head.nextNode.prevNode = this.head + } + } + + pop() { + if (this.size() === 0) { + throw 'Cannot pop() from empty DoublyLinkedList.' + } + this.counter -- + var output = this.head.data + this.head = this.head.nextNode + if (this.head) { + this.head.prevNode = null + } + return output + } + + size() { + return this.counter + } + + search(val) { + var curr = this.head + while (curr.data !== val) { + if (curr.nextNode === null) { + throw 'This value is not in the LinkedList.' + } + curr = curr.nextNode + } + return curr + } + + remove(val) { + var curr = this.head + var prev = null + while (curr) { + if (curr.data == val) { + this.counter -- + if (curr == this.head) { + if (this.head == this.tail) { + this.head = null + this.tail = null + return curr.data + } + this.head = curr.nextNode + this.head.prevNode = null + return curr.data + } + prev.nextNode = curr.nextNode + curr.nextNode.prevNode = prev + return curr.data + } + if (curr.nextNode == null) { + throw 'Values not in the DoublyLinkedList cannot be removed.' + } + prev = curr + curr = curr.nextNode + } + return curr + } + + append(val){ + var firstTail = this.tail + this.tail = new Node(val, this.prevNode=this.tail) + this.counter ++ + if(firstTail){ + firstTail.nextNode = this.tail + } else { + this.head = this.tail + } + this.tail.nextNode = null + } + + shift(){ + if (!this.tail){ + throw "Cannot shift() from empty DLL." + } + var output = this.tail.data + this.tail = this.tail.prevNode + if(this.tail) { + this.tail.nextNode = null + } else { + this.head = null + } + this.counter -- + return output + } + + + display() { + var start_paren = "(" + if (this.head === null) { + return '()' + } + var curr = this.head + while (curr) { + if (curr.nextNode === null) { + start_paren += curr.data + ')' + return start_paren + } + else { + start_paren += curr.data + ', ' + curr = curr.nextNode + } + } + } +} + +module.exports = DLL diff --git a/js-src/js-linked-list.js b/js-src/js-linked-list.js new file mode 100644 index 0000000..9b458c7 --- /dev/null +++ b/js-src/js-linked-list.js @@ -0,0 +1,95 @@ +'use strict'; + +class Node { + constructor(data, nextNode=null) { + this.data = data; + this.nextNode = nextNode; + } +} + +class LL { + constructor(iter=null) { + this.head = null; + this.counter = 0; + if (Array.isArray(iter)) { + iter.forEach(item => this.push(item)); + } + else if (iter !== null) { + return 'LinkedList only takes arrays as inputs.'; + } + } + + push(val) { + this.head = new Node(val, this.head); + this.counter ++; + } + + pop() { + if (this.counter === 0) { + return 'Cannot pop() from empty LinkedList.'; + } + var output = this.head.data; + this.head = this.head.nextNode; + this.counter --; + return output; + } + + size() { + return this.counter; + } + + search(val) { + var curr = this.head; + while (curr.data !== val) { + if (curr.nextNode === null) { + return 'This value is not in the LinkedList.'; + } + curr = curr.nextNode; + } + return curr; + } + + remove(val) { + var curr = this.head; + if (this.head.data === val) { + this.head = this.head.nextNode; + this.counter --; + return val; + } + if (this.head === null) { + return 'Cannot remove vals from empty LinkedList.'; + } + while (curr) { + if (curr.nextNode === null) { + return 'Values not in the LinkedList cannot be removed.'; + } + else if (curr.nextNode.data === val) { + curr.nextNode = curr.nextNode.nextNode; + this.counter --; + return val; + } + curr = curr.nextNode; + } + } + + display() { + var start_paren = '('; + if (this.head === null) { + return '()'; + } + var curr = this.head; + while (curr) { + if (curr.nextNode === null) { + start_paren += curr.data + ')'; + return start_paren; + } + else { + start_paren += curr.data + ', '; + curr = curr.nextNode; + } + } + } +} + +module.exports = LL; + diff --git a/js-src/js-que.js b/js-src/js-que.js new file mode 100644 index 0000000..e69de29 diff --git a/js-src/js-stack.js b/js-src/js-stack.js new file mode 100644 index 0000000..959261b --- /dev/null +++ b/js-src/js-stack.js @@ -0,0 +1,28 @@ +"use strict"; + +const LL = require("./js-linked-list") +// import {LL} from "js-linked-list.js" + +class Stack { + constructor(iter=null){ + this.linked = new LL() + if(Array.isArray(iter)){ + iter.forEach(item => this.push(item)) + } + } + + size(){ + return this.linked.size() + } + + push(val){ + this.linked.push(val) + } + + pop(){ + return this.linked.pop() + } + +} + +module.exports = Stack \ No newline at end of file diff --git a/js-src/package.json b/js-src/package.json new file mode 100644 index 0000000..ee4e9bf --- /dev/null +++ b/js-src/package.json @@ -0,0 +1,17 @@ +{ + "name": "js-data-structures", + "version": "1.0.0", + "description": "", + "main": "js-linked-list.js", + "dependencies": { + "chai": "^4.1.2" + }, + "devDependencies": { + "mocha": "^4.0.1" + }, + "scripts": { + "test": "mocha" + }, + "author": "Ya bish", + "license": "ISC" +} diff --git a/js-src/test/js-doubly-linked-list.spec.js b/js-src/test/js-doubly-linked-list.spec.js new file mode 100644 index 0000000..2a22109 --- /dev/null +++ b/js-src/test/js-doubly-linked-list.spec.js @@ -0,0 +1,112 @@ +'use strict'; + +var DLL = require('../js-doubly-linked-list'); +var chai = require('chai'); +var expect = chai.expect; + +describe('js-doubly-linked-list.js tests', () => { + it('creating a new empty DLL', () => { + var testList = new DLL(); + expect(testList.size()).to.equal(0); + expect(testList.head).to.equal(null); + expect(testList.tail).to.equal(null); + }); + + it('passing iterable into new LinkedList and pushing.', () => { + var testList = new DLL([100, 200, 300, 400, 500]); + expect(testList.size()).to.equal(5); + expect(testList.head.data).to.equal(500); + expect(testList.tail.data).to.equal(100); + + }); + + it('testing push method changes head', () => { + var testList = new DLL(); + testList.push('yo'); + expect(testList.head.data).to.equal('yo'); + expect(testList.tail.data).to.equal('yo'); + }); + + it('test push method adds one to size.', () => { + var testList = new DLL(); + testList.push(1); + expect(testList.size()).to.equal(1); + }); + + it("test pop on non-empty list.", () => { + var testList = new DLL(); + testList.push(5); + testList.push(4); + testList.push(3); + expect(testList.head.data).to.equal(3); + expect(testList.pop()).to.equal(3); + expect(testList.head.data).to.equal(4); + }); + + it("size function works with push and pop", () => { + var testList = new DLL([1, 2, 3, 4, 5]); + expect(testList.size()).to.equal(5); + testList.push(0); + expect(testList.size()).to.equal(6); + testList.pop(); + expect(testList.size()).to.equal(5); + }); + + it('correct search method works', () => { + var testList = new DLL(); + testList.push(5); + testList.push(4); + expect(testList.search(5).data).to.equal(5); + }); + + it("remove method with item in head of list", () => { + var testList = new DLL(); + testList.push(1); + expect(testList.size()).to.equal(1); + expect(testList.remove(1)).to.equal(1); + expect(testList.size()).to.equal(0); + }); + + it("remove method with item in middle of list", () => { + var testList = new DLL([1, 2, 3, 4, 5]); + expect(testList.size()).to.equal(5); + expect(testList.remove(3)).to.equal(3); + expect(testList.size()).to.equal(4); + }); + + it("append method works on filled list", () => { + var testList = new DLL([1, 2, 3]); + expect(testList.size()).to.equal(3); + testList.append(4); + expect(testList.size()).to.equal(4); + expect(testList.tail.data).to.equal(4); + }); + + it("append method works on empty list", () => { + var testList = new DLL(); + expect(testList.size()).to.equal(0); + testList.append(100); + expect(testList.size()).to.equal(1); + expect(testList.tail.data).to.equal(100); + expect(testList.head.data).to.equal(100); + + }); + + it("shift method works on filled list", () => { + var testList = new DLL([1, 2, 3, 4, 5]); + expect(testList.shift()).to.equal(1); + }); + + it("shift method changes list length", () => { + var testList = new DLL([1, 2, 3, 4, 5]); + expect(testList.size()).to.equal(5) + expect(testList.shift()).to.equal(1); + expect(testList.size()).to.equal(4) + + }); + + it("display method", () => { + var testList = new DLL([1, 2, 3, 4, 5]); + expect(testList.display()).to.be.string('(5, 4, 3, 2, 1)'); + }); +}); diff --git a/js-src/test/js-linked-list.spec.js b/js-src/test/js-linked-list.spec.js new file mode 100644 index 0000000..0d2df40 --- /dev/null +++ b/js-src/test/js-linked-list.spec.js @@ -0,0 +1,86 @@ +'use strict'; + +var linkedList = require('../js-linked-list'); +var chai = require('chai'); +var expect = chai.expect; + +describe('linked_list.js tests', () => { + it('creating a new empty LinkedList', () => { + var testList = new linkedList(); + expect(testList.size()).to.equal(0); + expect(testList.head).to.equal(null); + }); + + it('passing iterable into new LinkedList and pushing.', () => { + var testList = new linkedList([100, 200, 300, 400, 500]); + expect(testList.size()).to.equal(5); + expect(testList.head.data).to.equal(500); + + }); + + it('testing push method changes head', () => { + var testList = new linkedList(); + testList.push('yo'); + expect(testList.head.data).to.equal('yo'); + }); + + it('test push method adds one to size.', () => { + var testList = new linkedList(); + testList.push(1); + expect(testList.size()).to.equal(1); + }); + + it("test pop on non-empty list.", () => { + var testList = new linkedList(); + testList.push(5); + testList.push(4); + testList.push(3); + expect(testList.head.data).to.equal(3); + expect(testList.pop()).to.equal(3); + expect(testList.head.data).to.equal(4); + }); + + it("size function works with push and pop", () => { + var testList = new linkedList([1, 2, 3, 4, 5]); + expect(testList.size()).to.equal(5); + testList.push(0); + expect(testList.size()).to.equal(6) + testList.pop(); + expect(testList.size()).to.equal(5); + }); + + it("search on list without searched value", () => { + var testList = new linkedList(); + testList.push(5); + expect(testList.search(2)).to.equal('This value is not in the LinkedList.'); + }); + + it('correct search method works', () => { + var testList = new linkedList(); + testList.push(5); + testList.push(4); + expect(testList.search(5).data).to.equal(5); + }); + + it("remove method with item in head of list", () => { + var testList = new linkedList(); + testList.push(1); + expect(testList.size()).to.equal(1); + expect(testList.remove(1)).to.equal(1); + expect(testList.size()).to.equal(0); + }); + + it("remove method with item in middle of list", () => { + var testList = new linkedList([1, 2, 3, 4, 5]); + expect(testList.size()).to.equal(5); + expect(testList.remove(3)).to.equal(3); + expect(testList.size()).to.equal(4); + expect(testList.search(3)).to.equal('This value is not in the LinkedList.'); + }); + + it("display method", () => { + var testList = new linkedList([1, 2, 3, 4, 5]); + expect(testList.display()).to.be.string('(5, 4, 3, 2, 1)'); + }); +}); + diff --git a/js-src/test/js-stack.spec.js b/js-src/test/js-stack.spec.js new file mode 100644 index 0000000..a86d2da --- /dev/null +++ b/js-src/test/js-stack.spec.js @@ -0,0 +1,51 @@ +'use strict'; + +var Stack = require('../js-stack'); +var chai = require('chai'); +var expect = chai.expect; + +describe('stack.js tests', () => { + it('creating a new empty Stack', () => { + var testStack = new Stack(); + expect(testStack.size()).to.equal(0); + expect(testStack.linked.head).to.equal(null); + }); + + it('passing iterable into new Stack and pushing.', () => { + var testStack = new Stack([100, 200, 300, 400, 500]); + expect(testStack.size()).to.equal(5); + expect(testStack.linked.head.data).to.equal(500); + + }); + + it('testing push method changes head', () => { + var testStack = new Stack(); + testStack.push('yo'); + expect(testStack.linked.head.data).to.equal('yo'); + }); + + it('test push method adds one to size.', () => { + var testStack = new Stack(); + testStack.push(1); + expect(testStack.size()).to.equal(1); + }); + + it("test pop on non-empty stack.", () => { + var testStack = new Stack(); + testStack.push(5); + testStack.push(4); + testStack.push(3); + expect(testStack.linked.head.data).to.equal(3); + expect(testStack.pop()).to.equal(3); + expect(testStack.linked.head.data).to.equal(4); + }); + + it("size function works with push and pop", () => { + var testStack = new Stack([1, 2, 3, 4, 5]); + expect(testStack.size()).to.equal(5); + testStack.push(0); + expect(testStack.size()).to.equal(6) + testStack.pop(); + expect(testStack.size()).to.equal(5); + }); +}); diff --git a/src/insertion_sort.py b/src/insertion_sort.py new file mode 100644 index 0000000..22cb88c --- /dev/null +++ b/src/insertion_sort.py @@ -0,0 +1,45 @@ +"""Insertion sort algorithm.""" + + +def insertion_sort(lst): + """Implementation of insertion sort in python.""" + if isinstance(lst, list): + for i in range(1, len(lst)): + curr = lst[i] + if not isinstance(i, int): + raise TypeError('Only integers can be in the list.') + while i > 0 and lst[i] < lst[i - 1]: + lst[i], lst[i - 1] = lst[i - 1], lst[i] + i -= 1 + i = curr + return lst + raise TypeError('Only lists can be used with Insertion Sort.') + +if __name__ == '__main__': # pragama: no cover + import timeit as ti + import random + best_case = [1, 2, 3, 4, 5] + worst_case = [5, 4, 3, 2, 1] + random = [random.randint(1, 100) for i in range(10)] + + time_1 = ti.timeit("insertion_sort(best_case)", + setup="from __main__ import best_case, insertion_sort") + time_2 = ti.timeit("insertion_sort(worst_case)", + setup="from __main__ import worst_case, insertion_sort") + time_3 = ti.timeit("insertion_sort(random)", + setup="from __main__ import random, insertion_sort") + print(""" +Insertion sort compares the vals of an index versus the index - 1, and +(if index is smaller) switches. Them it continues switching until index is +bigger than index - 1. + +Input:[1, 2, 3, 4, 5] +Sort time: {} + +Input:[5, 4, 3, 2, 1] +Sort time: {} + +Input:list(range(5, 0, -1)) +Sort time: {} + """.format(time_1, time_2, time_3)) + diff --git a/src/test_insertion_sort.py b/src/test_insertion_sort.py new file mode 100644 index 0000000..a31294b --- /dev/null +++ b/src/test_insertion_sort.py @@ -0,0 +1,60 @@ +"""Testing module for insertion sort algorithm.""" + +import pytest +from insertion_sort import insertion_sort +from random import randint + + + +def test_ins_sort_on_empty_lst(): + """Sort on empty list as input.""" + assert insertion_sort([]) == [] + + +def test_ins_sort_on_lst_len_1(): + """Test on a list with one thing in it.""" + assert insertion_sort([1]) == [1] + + +def test_ins_sort_on_lst_len_2(): + """Test ins sort on a list with two unordered nums.""" + assert insertion_sort([53, 2]) == [2, 53] + + +def test_ins_sort_on_dictionary(): + """Test ins sort with input of dictionary.""" + with pytest.raises(TypeError): + assert insertion_sort({'1': 1, '2': 2}) + + +def test_ins_sort_on_string(): + """Test insertion doesnt work with string input.""" + with pytest.raises(TypeError): + assert insertion_sort("this wont work") + + +def test_list_with_non_num_inside(): + """Test on a list with a string as one of the items.""" + with pytest.raises(TypeError): + assert insertion_sort([1, 2, 3, 4, 'five', 6, 7]) + + +def test_list_with_negatives(): + """Test on list with neg numbers.""" + assert insertion_sort([-32, 23, 1, 0, 900, -14]) == [-32, -14, 0, 1, 23, 900] + + +def test_list_on_randomized_lists(): + """Test using randomly generated lists.""" + for i in range(60): + lst = [randint(0, 1000) for i in range(30)] + sorted_lst = sorted(lst) + assert insertion_sort(lst) == sorted_lst + + +def test_list_on_randomized_long_lists(): + """Test using randomly generated lists.""" + for i in range(15): + long_lst = [randint(0, 1000) for i in range(100)] + sorted_long_lst = sorted(long_lst) + assert insertion_sort(long_lst) == sorted_long_lst diff --git a/src/test_trie.py b/src/test_trie.py index 6b7fbd5..d034a63 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -163,3 +163,33 @@ def test_size_decreases_with_removing_node(filled_2): assert filled_2.size() == 4 filled_2.remove('az') assert filled_2.size() == 3 + + +def test_trie_autocomplete_on_filled_tree_letter_h(filled_1): + """Autocomplete tests on filled tree.""" + a = filled_1.autocomplete('h') + assert next(a) == 'hello' + assert next(a) == 'helsinki' + assert next(a) == 'heckingoodboye' + with pytest.raises(StopIteration): + assert next(a) + + +def test_trie_autocomplete_on_filled_tree_letter_g(filled_1): + """Autocomplete tests on filled tree.""" + a = filled_1.autocomplete('good') + assert next(a) == 'goodbye' + assert next(a) == 'goodlord' + + +def test_trie_autocomplete_where_no_suggestions(filled_1): + """Autocomplete with a letter not in Trie tree, makes empty list.""" + a = filled_1.autocomplete('z') + assert a == [] + + +def test_trie_auto_with_non_string(filled_1): + """Autocomplete with a non string.""" + with pytest.raises(TypeError): + a = filled_1.autocomplete('z') + assert next(a) diff --git a/src/trie.py b/src/trie.py index 15d3d70..94e2a6f 100644 --- a/src/trie.py +++ b/src/trie.py @@ -64,3 +64,61 @@ def remove(self, word): if current.parent: current = current.parent self.tree_size -= 1 + + def trie_traversal(self, start=None): + """Depth-first traveral of Trie.""" + self.visited = [] + + if start: + curr = self.root + for char in start: + if char in curr.children: + curr = curr.children[char] + return 'Invalid starting string.' + return self._combo_gen(curr) + else: + return self._combo_gen(self.root) + + def _combo_gen(self, start): + """.""" + for child, child_node in start.children.items(): + self.visited.append(child) + for node in child_node.children: + self.visited.append(child_node.children[node].letter) + if child_node.children[node].end and not child_node.children: + continue + child_node.children = child_node.children[node].children + for let in self.visited: + yield let + + def _trie_gen(self, start): + """Generator for traversal function.""" + for child in start.children: + return self._recursive_depth(start.children[child]) + + def _recursive_depth(self, node): + """Recursive helper fn for generator.""" + self.visited.append(node.letter) + for child in node.children: + if child.end: + break + yield self._recursive_depth(node.children[child]) + + def autocomplete(self, start): + """Autocomplete using Trie Tree.""" + if isinstance(start, str): + curr = self.root + for letter in start: + if letter not in curr.children: + return [] + curr = curr.children[letter] + return self._auto_helper(curr, start) + raise TypeError('Autocomplete takes only strings.') + + def _auto_helper(self, node, start): + """Helper fn for autocomplete.""" + if node.end: + yield start + for letter in node.children: + for word in self._auto_helper(node.children[letter], start + letter): + yield word